Repository navigation
feat(spec)!: refuse excludeSubmitter: true beside onEmptyApprovers: 'auto_approve' on an approval node - #25700
Conversation
…auto_approve' on an approval node An approval node that removes the submitter from its slate and auto-approves an emptied slate would approve a self-submitted request whose submitter was the only approver. The pair is now refused at the parse with one issue at onEmptyApprovers, prescribing 'admin_rescue', 'fail' or 'fallback' (ruling 6386627950, letter A). The key ships in no release yet. Claude-Session: https://claude.ai/code/session_01PC4NPXPgPmpDRG9DdDnyXT Co-authored-by: Claude <noreply@anthropic.com>
…for the refused pair Claude-Session: https://claude.ai/code/session_01PC4NPXPgPmpDRG9DdDnyXT Co-authored-by: Claude <noreply@anthropic.com>
…clude-submitter-auto-approve-refusal
📓 Docs Drift CheckThis PR changes 1 package(s): 4 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 — 144 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 1aad63f8d0623425884ca79a4ee1c8306e4ed4cf && git checkout 1aad63f8d0623425884ca79a4ee1c8306e4ed4cf
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin b77bb37eb2c738c40093acb04f19fa8fc2dd605a 3cee74ecf009d339aaf97ba19c83c061df7e0414 && git checkout -B drift-repro b77bb37eb2c738c40093acb04f19fa8fc2dd605a && git merge --no-ff 3cee74ecf009d339aaf97ba19c83c061df7e0414
node scripts/docs-audit/affected-docs.mjs --json b77bb37eb2c738c40093acb04f19fa8fc2dd605a
|
席位核对ACCEPT · #25635 ·
Generated by Claude Code |
Contract reviewServed-tier: ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS
Generated by Claude Code |
Fixes #25635
Clause-②: no (narrowing)
Metadata-protocol: narrowed
Ruling-ref: 6386627950
What changes
An approval node's config now refuses
excludeSubmitter: truebesideonEmptyApprovers: 'auto_approve'at the parse. This is letter A of ruling6386627950(batch #12 item 1, maintainer 「25635 25647 25675 同意」):packages/spec/src/automation/approval.zod.ts: a new refinement,checkExcludeSubmitterPolicyPairing, follows the shape ofcheckFallbackApproversPairingand is wired the same way, as a second.superRefineonApprovalNodeConfigSchema. It raises onecustomissue whenexcludeSubmitter === trueandonEmptyApprovers === 'auto_approve', and nothing otherwise.onEmptyApprovers, not onexcludeSubmitter. The ruled fix is to choose a different policy, soonEmptyApproversis the key the author has to edit. The message names both keys and offers the three ruled policies:'admin_rescue'(the default),'fail', or'fallback'withfallbackApprovers. Unlike the fallback pairing, it does not offer "remove the other key" as an equal fix. RemovingexcludeSubmitterdrops the control the author asked for, and an author or an AI told that either key will do usually makes the cheaper edit.truetriggers it. WithexcludeSubmitterset tofalseor left out,auto_approvekeeps its meaning. Every other policy still parses besidetrue.excludeSubmitterdescribe said an emptied slate "never passes silently", but did not mention thatauto_approveis refused beside it. That said less than the code now enforces. It gains one sentence naming the refused value and the three policies to choose instead. The[PLANNED — not yet enforced]marker stays, because the runtime exclusion (plugin-approvals: afieldorpositionapprover step has no "not the submitter" guard, so a manager who submits their own request can approve it #25573) is still to come. Theauto_approvebullet in theonEmptyApproversTSDoc now points at the new rule.content/docs/references/automation/approval.mdxis regenerated (gen:docs, one row).fieldorpositionapprover step has no "not the submitter" guard, so a manager who submits their own request can approve it #25573 keeps its scope, and no otheronEmptyApproversvalue is touched.Release-window reading (no published surface narrows)
The ruling requires this to merge before the next objectstack release. Reading taken 2026-10-24:
npm view @objectstack/spec:latest18.1.0,next18.0.0-next.5,rc17.0.0-rc.6. In the 18.1.0 tarball (npm pack),excludeSubmitterappears in 0 files. Controls in the same tarball:onEmptyApproversin 28 files,fallbackApproversin 30.packages/spec/CHANGELOG.mdat3cee74ecf0: 0 hits forexcludeSubmitter. The key's own changeset,.changeset/25609-approval-exclude-submitter.md, is still pending, so it will ship in the same release as this one.git grep excludeSubmitterat3cee74ecf0hits only the spec schema, its test, the generated reference, theauthorable-surfaceshard, theliveness/flow.jsonnote and the two changesets. No example, platform object, plugin or service writes it, so there is nothing to migrate. Control:onEmptyApproversis written byexamples/app-showcaseflows.Absent from every release, the refusal narrows nothing a published version accepted. The changeset is
'@objectstack/spec': minor, because anarrowedkind takes at leastminor. It carries the three declaration lines above, and its ADR-0087 marker readsnot-required (no-migration-prescription)on those facts (check-adr-0087-registrationexit 0, classifying it[clause-②-narrowing+metadata-protocol-narrowed]).Consumers
plugin-approvalsparsesApprovalNodeConfigSchemawhen it executes. A stored node with the pair would now fail at execute, but no producer writes the key, so no such row exists.packages/lintandmetadata-protocol: no code change. Their full suites are green at3cee74ecf0(below).FlowSchemareports it atnodes.N.config.onEmptyApprovers, and the build judgeflowNodeConfigRefusalsreports it asnode-config-refused-by-contractononEmptyApprovers. Both are pinned in §6.automation/ApprovalNodeConfigkeeps its existingdropped-refinementssite"". That ledger is unchanged andcheck:generatedis green.Tests and gates (all readings at
3cee74ecf0unless stated)approval-exclude-submitter-25609.test.tsgains §6 and one §5 assertion. Run before the implementation:Tests 6 failed | 22 passed (28). The six were exactly the new refusal pins and the describe pin; the controls were already green.node scripts/ablation-replace.mjs --deleteremoved the wiring.superRefine(checkExcludeSubmitterPolicyPairing)(anchor 1 → 0, blob8c310f2bea7b→a85bce9b6733). Predicted direction: the five §6 refusal pins go red and the controls stay green. Observed:Tests 5 failed | 23 passed (28). Restored: blob equals HEAD8c310f2bea7b,git diff HEADempty.pnpm --filter @objectstack/spec test:Test Files 719 passed (719),Tests 21800 passed | 1 todo (21801).pnpm --filter @objectstack/spec test:repo(at4c615790bf, before the merge oforigin/main):Test Files 79 passed (79),Tests 1269 passed (1269). Not re-run after the merge. CI'sTest Coreruns it on the merged head.pnpm --filter @objectstack/spec typecheck: exit 0 (check:test-typecheckOK, ledger held).plugin-approvals96 passed (96)/1259 passed.lint161 passed (161)/7269 passed.metadata-protocol263 passed | 3 skipped (266)/21475 passed | 19 skipped. These packages have notest:reposcript.(narrowing)arm and the!title):pnpm --filter @objectstack/dogfood testunder the lock, after building@objectstack/dogfood^.... Result:Test Files 295 passed | 3 skipped (298),Tests 2520 passed | 18 skipped (2538),VERDICT command-exit 0.pnpm --filter @objectstack/spec check:generatedafter the merge: all 14 artifacts up to date.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 113 commands. All 113 exit 0. Four needed a re-run: three ran while the dogfood closure build was rewritingdist/(check:exported-any3,check:generated1,check:dual-build-cjs-loads1), andcheck:type-check-debthit a lock queue-timeout (3). Each re-run after the build exited 0.--ranreconciliation:113 derived famil(ies) accounted for — 113 run, 0 NOT-MEASURED (a DERIVED zero — all 113 recorded an exit code and none of them is 3).node scripts/docs-audit/affected-docs.mjs --json origin/mainnamed 7 docs. Four are hand-written (automation/approvals.mdx,automation/flows.mdx,capabilities/approvals.mdx,getting-started/common-patterns.mdx) and three are release-owned (not touched). None of the hand-written pages mentionsexcludeSubmitter, and theirauto_approvesentences stay true, so no hand-written sentence is made false.Acceptance notes
approval-expression-no-empty-policyhint lists'auto_approve'among the policies to declare, whateverexcludeSubmittersays. An author withexcludeSubmitter: trueand an expression approver who follows it verbatim is refused at once by this rule, with the prescription. That is loud, and nobody writes the key today. carrier: none.escalation.action: 'auto_approve'is a different key, a timed policy that notifies the submitter (approval.sla_breached). The ruling coversonEmptyApproversonly, so it is untouched. carrier: none.Clause-②: nofor a pre-release landing. The PM's claim carriesClause-②: no (narrowing), and those lines are copied verbatim as the claim prescribes.Metadata-protocol: narrowedalone already classifies the changeset as breaking for the ADR-0087 gate, so the arm changes no gate verdict here. The release-window reading above is the evidence that no published surface narrows.Generated by Claude Code