Repository navigation
docs(spec): document typed approval decision outputs as enforced at the decision, not a widget hint - #29371
Conversation
The DecisionOutputDefSchema docblock, its `required` docblock and the approval node's `decisionOutputs` docblock said `type`/`multiple` only shape the input widget and the runtime accepts any value shape. Rewrite them, and the approvals guide, to the enforced contract recorded by ADR-0104's 2026-10-30 amendment: picker outputs judged by record id or position machine name, `multiple` judged as list-vs-single with no duplicates, a mismatch refused 400 naming `outputs.KEY` before any write. Claude-Session: https://claude.ai/code/session_01ADHkmP8ELNFNfBHmk387wA Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): ⛔ 1 release-owned page(s) name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 168 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 2fa5e780730045c7975f2be515a014d2298b3acc && git checkout 2fa5e780730045c7975f2be515a014d2298b3acc
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 1b6709b8ebc37d6ce1f73470fbebe7569d4dd650 fc024e801bc41c6b576f90fad4a84d887a285faf && git checkout -B drift-repro 1b6709b8ebc37d6ce1f73470fbebe7569d4dd650 && git merge --no-ff fc024e801bc41c6b576f90fad4a84d887a285faf
node scripts/docs-audit/affected-docs.mjs --json 1b6709b8ebc37d6ce1f73470fbebe7569d4dd650
|
…held Seat 5's correction on the card, read against the server half: `multiple` judges the shape of every typed output, `text` included (judged by shape only), and a `position` value is judged by its holders, so a machine name nobody holds is refused. Drop the "text is not judged" sentences. Claude-Session: https://claude.ai/code/session_01ADHkmP8ELNFNfBHmk387wA Co-authored-by: Claude <noreply@anthropic.com>
…rs example
The server half shape-checks every decision output, bare keys included:
absent `multiple` takes one value. Say so in the three docblocks and the
guide, and give the guide's "previous approver picks the next step's
approvers" example `{ key: 'next_reviewers', multiple: true }`, since the
list it sends would be refused under a bare key.
Claude-Session: https://claude.ai/code/session_01ADHkmP8ELNFNfBHmk387wA
Co-authored-by: Claude <noreply@anthropic.com>
|
domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS Head-sha: 维护者速读
席位意见:可落地,等契约复审与 CI。 席位核对 — ACCEPT ·
|
|
domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS Contract-review-claim: session_01ADHkmPi25XXKx92Xmg4viS · head fc024e8 · 2026-11-02T13:57Z |
domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS
Requested by Steve · project thread
Fixes #29340
Clause-②: no
Metadata-protocol: none
维护者速读
type/multiple在决策时强制校验」,删掉「只影响输入控件」的旧说法。userdecision output is checked for presence only — an id that resolves to no user records the decision, then answers 500 RESUME_FAILED and strands the run #28658 裁决 A 要求运行时按声明校验决策输出;旧文字与 ADR-0104 2026-10-30 修订矛盾,且服务端 PR(席 5)要等这条文字先落地。packages/spec/src/automation/approval.zod.ts(DecisionOutputDefSchema 注释块)、content/docs/automation/approvals.mdx(typed declaration 段落)。git grep -n "only shape" packages/spec/src/automation/approval.zod.ts content/docs/automation/approvals.mdx应无输出。What changes
packages/spec/src/automation/approval.zod.ts, three docblocks (no code, no.describe()string):DecisionOutputDefSchema: replaces "typeandmultipleonly shape the INPUT WIDGET, never the accepted value" with the enforced contract: a picker output is judged by record id (user,department,team) or by machine name (position), through the lookup the matchingexpressionapprover'sresolveAsuses;multiple: truetakes a list with no entry twice, absent/false takes one value; a mismatch is refused with400 VALIDATION_FAILEDnamingoutputs.KEYbefore any write, the request staying pending;multiplebinds every entry, text and bare keys included; text is judged by shape only.required: "Unliketype/multiple— which only shape the input widget" becomes "type/multiplejudge the SHAPE of a supplied value; this one judges PRESENCE".decisionOutputson the approval node config (a third copy the card did not name): "The type describes the INPUT WIDGET only — the runtime accepts whatever value shape arrives … verbatim" is rewritten to the same contract.content/docs/automation/approvals.mdx: the typed-declaration paragraph drops "The type shapes only the input widget" and gains the enforced rule (a short list), and therequiredparagraph no longer contrasts against an unenforcedtype/multiple.content/docs/automation/approvals.mdxnext_reviewersexample now declares{ key: 'next_reviewers', multiple: true }(the bare key fed a list, which approvals: a typeduserdecision output is checked for presence only — an id that resolves to no user records the decision, then answers 500 RESUME_FAILED and strands the run #28658's server PR refuses)..changeset/29340-decision-output-enforced-docs.md:@objectstack/specpatch (the docblocks ship in the publishedsrc/**/*.zod.ts).No
.describe()string carried the contradiction (checked: thetype,multipleanddecisionOutputsdescribes only say what the approver answers with), so nothing regenerates. Forced test edits: none.Ordering: the runtime enforcement is #28658's
domain:serverPR (branchclaude/project-thread-rsqu0h), which merges after this one; the text is written as the contract, in the present tense, as ADR-0104's amendment is.Acceptance notes
VALIDATION_FAILED, not the card'sVALIDATION_ERROR. The approvals door answers400 VALIDATION_FAILEDfor a malformed decision (the guide's own error-code table; the existingrequiredrefusal throwsVALIDATION_FAILED:), and the enforcement branch for approvals: a typeduserdecision output is checked for presence only — an id that resolves to no user records the decision, then answers 500 RESUME_FAILED and strands the run #28658 (head88c96ad6) throwsVALIDATION_FAILEDnaming eachoutputs.KEY.VALIDATION_ERRORis the action-params gate's code from ADR-0104 D2. The text names the code the door answers.plugin-approvals/src/approval-service.tscarries the same "only shape the input widget" comment beside therequiredcheck; that file is the server half's, so it is left to that PR.Verification
Run on head
fc024e80(round 2: two text-only commits over821642ea).pnpm --filter @objectstack/spec build && pnpm --filter @objectstack/spec check:generated:✓ All 14 generated artifacts are up to date, lock VERDICT command-exit 0.node scripts/check-changeset-no-major.mjs --base origin/main --event FILEexit 0,✓ LEVEL AXIS(clause-② no, metadata-protocol none: a non-protocol change at patch). The card'sClause-②: no (narrowing)was also measured: the gate accepts it too (exit 0), but reads the arm as BREAKING, which a text-only diff is not, so this body declares barenoas the seat's claim does.dispatch-gates --commands, 96 commands): 96/96 exit 0 after a fullturbo run build --filter=./packages/**(lock VERDICT command-exit 0);dispatch-gates --ran:96 derived famil(ies) accounted for — 96 run, 0 NOT-MEASURED.pnpm --filter @objectstack/spec typecheck && pnpm --filter @objectstack/spec test:Test Files 791 passed (791),Tests 23391 passed | 1 todo, lock VERDICT command-exit 0.Human Minimum
packages/specholds no runtime logic.pnpm lintpasses (CI).main, one pull request.packages/spectouched: its generated artifacts are regenerated and committed (none stale).content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.Generated by Claude Code