Repository navigation
fix(spec)!: a page action row declares ActionSchema's operation and refuses onSuccess beside operation: 'update', with the action's text - #23425
Conversation
…fuses onSuccess beside operation: 'update' with the action's text Claude-Session: https://claude.ai/code/session_01D6F9axfoPzR7t8G1J7Ytzz Co-authored-by: Claude <noreply@anthropic.com>
…or the row operation Claude-Session: https://claude.ai/code/session_01D6F9axfoPzR7t8G1J7Ytzz Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D6F9axfoPzR7t8G1J7Ytzz Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D6F9axfoPzR7t8G1J7Ytzz Co-authored-by: Claude <noreply@anthropic.com>
…tion-row-operation
📓 Docs Drift CheckThis PR changes 1 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 142 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin ea35a797847a3417588e5c51c9670ae845c6d176 && git checkout ea35a797847a3417588e5c51c9670ae845c6d176
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 4f81f315266354e7b70adc589d6ee38a76d24f41 339604baf420a246fc5841dffd75d8a440346648 && git checkout -B drift-repro 4f81f315266354e7b70adc589d6ee38a76d24f41 && git merge --no-ff 339604baf420a246fc5841dffd75d8a440346648
node scripts/docs-audit/affected-docs.mjs --json 4f81f315266354e7b70adc589d6ee38a76d24f41
|
Contract reviewServed-tier: Inputs read: card #23332 (body, triage first grade Check-runs on the head. Completed and green: ① Derived judgmentsEvery accept-set and public-surface change the diff implies, each judged:
Nothing in the diff touches ② Semver levelChangeset Clause-②: the PM claim declared Cosmetic, not a gate: the PR title is ③ Boundary flags
Dev flags (
Implemented-by: VERDICT: PASS Adopted by Generated by Claude Code |
Fixes #23332
Clause-②: no (narrowing)
The page action rows (
action:button,action:icon, and eachaction:group/action:menumember) andActionSchemanow give one answer foroperation: 'update'plusonSuccess. Direction of record: triage6154972534(consistent with #23168's answer C,6148462268), within the PM's premised ruling.Premise check, done first:
ActionSchema's refusal is the right halfThe ruling's premise: no runtime consumer honors
onSuccessafter anoperation: 'update'write as a declared, tested behavior. It holds. Read at objectui's.objectui-shapin1fb1ced36243(the runner file is byte-identical at objectuimainb9814b3):ActionRunner.executereadsoperationbefore any executor (core/src/actions/ActionRunner.ts:1384-1388).executeUpdateOperationposts the write through the registeredscriptdispatch, and thenhandlePostExecutionruns. That function performs theonSuccesshop for every branch on success (:1639-1642). So the hop would fire after the write, but only because every branch shares the same post-execution path.onSuccess(:1620-1638) says the key is "refine-scoped totype: 'api'andtype: 'script'" and that the spec's declared meaning is its only meaning. objectui's metadata-admin form putsonSuccessinUPDATE_OPERATION_REFUSES(app-shell/src/views/metadata-admin/inspectors/ActionDefaultInspector.tsx:336-353), calls it "a navigation the write has no business doing", and clears it when an author switches an action into an update (commitOperation,:620-628).ActionRunner.onSuccessNavigation.test.tshas nooperationin it, andActionRunner.updateOperation.test.tshas noonSuccess. Of the 55 files that writeoperation: 'update', the 10 that also mentiononSuccesswere each read: none puts the two on one action.So the rule stays as
ActionSchemahas it, and the rows take it. ⛔ActionSchemawas not widened.What changes
1. A row's
operationisActionSchema's own enum, by reference (component.zod.ts,actionOperation()=ActionSchema.in.shape.operation.unwrap()). It is read the same wayactionOnSuccessBlock()readsonSuccess, so there is no second vocabulary: the one member is'update', and'delete'/'custom'get the action's own "bulk-def operation with no row-level form" refusal. Before this, the rows typed itz.unknown(). The runner testsoperation === 'update'and lets any other value fall through to the executor, where it did nothing.2. A row's
onSuccessbesideoperation: 'update'is refused whatever executor the row names (or none), asActionSchemarefuses it on everytype.refuseRowOnSuccessBesideExecutorreadsoperationfirst, the way the runner does, and gives ONE issue atonSuccess. Before this:actionType: 'api'or'script'or a host handler,operation: 'update',onSuccessonSuccess, with the update textoperation: 'update',onSuccessactionType: 'script'), which does not fit this shapeoperation: 'update'and noonSuccess, on any executor3. One refusal text, from one place. The new
onSuccessBesideUpdateOperationRefusal(subject)lives inaction-on-success-executor.ts, the module PR #23331 created for this purpose (relative import only, outside theuibarrel).ActionSchema'sDECLARATIVE_UPDATE_REFUSED_KEYSonSuccessentry and the rows both read it. The text says what the row does at run time, as the card asks:On
ActionSchemathe subject is "Anoperation: 'update'action'sonSuccess". The text used to open "onSuccessis not defined for anoperation: 'update'action yet". Its reach is unchanged, and the "DroponSuccess" prescription is kept.Accept-set narrowing (BREAKING,
minor, ADR-0087registered ui-page-action-row-operation-declared)Values a row accepted before and now refuses:
operationother than'update':'delete'and'custom'(with the action's prescription), and anything else (invalid_value);onSuccessbesideoperation: 'update', on any executor.Producers measured: none in this repository at
339604baf. I checkedexamples/(the only page action rows are inexamples/app-showcase/src/ui/pages/index.tsandsrc/ui/actions/index.ts, and neither carriesoperation), the CLI corpus (packages/cli,skills/) and the dogfood fixtures (packages/qa). In objectui at the pin1fb1ced36243and atmainb9814b3, the action renderers' tests carryoperation: 'update'rows withoutonSuccess(accepted, unchanged). No row carries anotheroperationvalue or the pairing. objectui's registration already publishesoperationasenum: ['update'](action-button.tsx:584-590), so itsregistry-inputs-spec-parityenum check gets a member the spec accepts. hotcrm: NOT MEASURED (no checkout in this container).Semantic migration entry
packages/spec/src/migrations/entries/semantic/18.ui-page-action-row-operation-declared.ts, beside the18.ui-page-action-row-*family. Changeset.changeset/23332-page-action-row-operation.md.Pins, each with a negative control
component-action-row-on-success-executor.test.ts§4), on all four rows:onSuccessbesideoperation: 'update'with no executor and withapi/script/run. The test asserts ONE issue atonSuccessplus the full first sentence.onSuccessparse.ActionSchema's message after its subject.ActionSchema.operationdeclares.'delete'is refused with the action's exact message,'create'getsinvalid_valueatoperation, and the'update'accepted control passes.ActionSchemaside (action-row-update.test.ts):onSuccessbesideoperation: 'update'(notypeand explicittype: 'script') gives exactly one issue, atonSuccess, and the same action without it parses.os validatedoor (packages/lint/src/validate-component-props-row-on-success.test.ts): onecomponent-props-invalidfinding at…onSuccessforapi+ update +onSuccess, nothing withoutonSuccess, and one finding at…operationfor'delete'.component-props-unknown-members.pin.test.tsdeletes theoperationline, because a typed member deletes its line.Pin sweep: I grepped the old message ("not defined for an
operation: 'update'") across this repo: one pin, flipped (action-row-update.test.ts:218). objectui at the pin and atmain: zero hits.Ablations (committed tree,
scripts/ablation-replace.mjs; every leg restored to blob == HEAD withgit diff HEADempty; spec tests importsrc, so no dist was involved):if (row.operation === 'update')changed toif (false as boolean)). Result: 20 red (16 REFUSAL + 4 ONE TEXT), and the negative controls stayed green.ActionSchema'sonSuccessentry was deleted. Result: 6 red (bothActionSchemapins + 4 ONE TEXT). My first attempt used an empty--replacement, which the tool refused as a no-op (count 0 to 0). It was re-run with--delete.z.unknown(). Result: 9 red (8 operation refusals + NO SECOND VOCABULARY).Verification at
339604baf(after mergingorigin/main5656c5638)@objectstack/spec:--project local: 672 of 673 files green before the ledger fix. The 3 failures were all incomponent-props-unknown-members.pin.test.ts(the typedoperationmember's line), and that file is now 69/69.test:repo: 59 files, 1011 passed.typecheck(tsc, scripts, test-typecheck): exit 0.check:generated: green aftergen:docs(content/docs/references/ui/component.mdxregenerated).@objectstack/lint: 138 files, 6706 passed;typecheck: exit 0.ComponentPropsMapparsers):cloud-connectionandmcpcanonical-expression-envelopes.test.ts16 and 11 passed;platform-objectspages/canonical-expression-envelopes.test.ts15 passed.dispatch-gates.mjs --commands: 111 families, all 111 run on339604baf, every one exit 0.--ranreconciliation: "111 run, 0 NOT-MEASURED (a DERIVED zero — all 111 recorded an exit code and none of them is 3)".check:nul-bytesexit 0.Acceptance notes
ActionSchemaon the declarative-update mixing rule): the rows carry none ofActionSchema's otheroperation: 'update'refusals. These were measured on the built spec:patchwithoutoperation,targetbeside update, a non-script executor beside update, update with nothing to write, andlist_toolbarbeside update each parse onaction:buttonand are refused byActionSchema. Reported to the seat for the family's closing card. Not fixed here.packages/spec/src/ui/page.zod.ts(page: a stored page component'sdatacan still carry the retired viewapisource — the props gate runs at the CLI doors only, so the runtime save door and boot accept it, and the renderer still sends it with its stored headers #23357). The sentence atcomponent.zod.ts:6030("a stored page still saves and loads") is far from this diff and still true until feat(lint)!: the two page component rules run on the runtime publish door (ruling B) #23418 lands, so it was left alone. The new migration entry's sentence "A page'spropertiesis not parsed on the metadata save or load path" follows its family's current text, and feat(lint)!: the two page component rules run on the runtime publish door (ruling B) #23418 rewrites that text in the sibling entries. Whichever PR lands later should align this entry too.Clause-②: the PM's claim declaredyes, conservatively and conditionally ("if measurement moves the rule on ActionSchema's side, which would widen it"). Measurement did not move it, so this PR declaresno (narrowing), as triage's direction records for this outcome.Generated by Claude Code