Move the hand-written-docs list off the governed surface, injected at invocation instead of inlined - #14085
Merged
Merged
Conversation
…rkflow instead of inlining it The default scope moves out of the sandboxed workflow body into a generated, non-governed artifact the caller hands in as args.handwritten. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Msg17tAHJ3jVTYFgHydCm2
… left .claude/ Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Msg17tAHJ3jVTYFgHydCm2
…ected scope Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Msg17tAHJ3jVTYFgHydCm2
Contributor
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
This was referenced Sep 1, 2026
os-zhuang
approved these changes
Sep 1, 2026
os-zhuang
marked this pull request as ready for review
September 1, 2026 06:34
os-zhuang
enabled auto-merge
September 1, 2026 06:34
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #13591
Moves the hand-written-docs list
ALL_HANDWRITTENoff the governed surface. It becomes anon-governed, generated single-source artifact; the workflow's caller reads it and hands
it to the
node:vmbody at invocation time, so the sandbox stays exactly as FS-blind asbefore.
check:docs-audit-scopekeeps enforcing two-way equality against that same singlesource, so the "one subject, one hand-kept list" bill stays paid.
Verified at
8a82109c4.The ruling this implements
Maintainer, 2026-09-01, decision batch B, verbatim 「同意」 on option 2. Both halves,
untranslated:
GOVERNED_SURFACESis character-for-character unchanged —docs/adr/**·.claude/**·
skills/**·AGENTS.md·CLAUDE.md, exactly as before. Nothing here narrows thegoverned register.
⛔ The exit toll: this PR is itself governed — DRAFT ONLY, human merge
Removing the inline list is one last edit under
.claude/**, which forks the whole PR. Theruling names this and accepts it: "the diff will itself touch
.claude/**to remove theinline list — governed, draft-only, human merge; that one last governed edit is the exit
toll." Measured on this PR's own file list:
⛔ No seat flips this ready, enqueues it, or arms auto-merge.
The measurement the ruling asked for: what CAN be injected
The workflow runner is the Claude Code Workflow tool, which is not in this repo and is
not modifiable here. Its documented invocation surface offers exactly one channel into the
sandbox: the
argsglobal, "the value passed as Workflow'sargsinput, verbatim", besidelog/phase/console/budget/timers andagent/parallel/pipeline/workflow. Codegeneration is disabled and there is no filesystem.
⇒ Injection is feasible, and
argsis the whole of it. The fallback to option 1 was NOTtaken. What the earlier reading missed is that the body does not have to do the reading:
argsis delivered from the invocation, so the file read happens in the caller, outside thesandbox, and the list arrives as data. The sandbox's constraints are untouched.
One shape was considered and rejected: having the workflow ask an agent to read the
artifact (agents do have a filesystem). That would make the scope a self-report with nothing
to reconcile it against — a truncated answer would produce a "FULL audit" over fewer docs
with nothing able to notice, which is #4851 re-created and unverifiable.
argsis the honestchannel: the caller supplies data, the body validates its shape and refuses ambiguity.
What changed
scripts/docs-audit/handwritten-docs.jsonreadmeblock states that, names both consumers, and records that it sits outside every governed glob by ruling..claude/workflows/docs-accuracy-audit.jsALL_HANDWRITTENarray and itsgenerated:markers are gone (190 path literals removed). Scope now comes fromargs.handwritten(full set) orargs.docs(caller's narrowing).scripts/docs-audit/check-audit-scope.mjscheckScopeInjection.scripts/pm/check-governed-merges.mjsscripts/pm/check-governed-queue-guard.mjs,scripts/docs-audit/README.md,.github/workflows/lint.yml,scripts/docs-audit/affected-docs.mjsThere is deliberately no "omit
argsand audit everything" invocation any more, and therecannot be: with nothing handed in the body does not know what "everything" is, and the only
shapes it could invent are a silent audit of nothing and a stale list — both report success.
It refuses by name and says what to pass.
GENERATED_SURFACE_EXCEPTIONScarried a row (#10277 Option C, 「10277 同意 C」 2026-08-20,re-confirmed 「A:按方案 2(最新裁定)」 2026-08-22) that lifted the governed fork for
.claude/workflows/docs-accuracy-audit.jswhen its bytes equalled the generator's recomputedsplice. That row is removed, because the 2026-09-01 ruling removes its subject: there is
no generated artifact at that path any more. Keeping it would be actively unsafe — a row
still naming that path could only ever be asked to certify hand-authored instruction prose,
which is the thing the fence exists to stop. Removing it is a narrowing: nothing that was
governed became clear, and no glob moved. The retirement is asserted, not remembered:
The generic machinery (
generatedExceptionFor,applyGeneratedExceptions,recomputeProvenanceFor,sinkGeneratorVerdict, the #11084 co-edit fence) is untouched andstill serves the two #11705 rows; only the splice-specific recompute, which had that one row
as its sole caller, goes with it.
The outcome, measured
The property the card was filed about, before and after — same simulated page-adding PR:
Reverse verification
The gate still detects drift in BOTH directions. Run from a committed state; each mutation
proven on disk by an anchored observation before measuring, each restore proven by blob hash
against the HEAD blob and by an empty
git diff HEAD— never by an exit code.And the NEW guard fails when it should. Moving the list out creates one failure mode that
did not exist before: the body could stop consuming what it is handed, and the artifact would
still be perfectly in sync.
checkScopeInjectionruns the real workflow against stub agents.Mutating the body to ignore
args.handwritten:Note the first two lines stayed green while the third went red — which is the whole point:
an artifact in sync with
content/docs/proves nothing about a body that stopped reading it.--self-testcarries four such mutants (the injected list is ignored,a silent default replaces the no-scope refusal,the FULL-audit line stops naming the scope source,the malformed-list guard is removed); an unapplied anchor fails as loudly as a green mutant.Gates
Derived union after the last commit —
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsat8a82109c4, 39 commands, exit codes captured beforeany pipe. 35 green, 4 NOT MEASURED, 0 findings:
check:docs-audit-scope,check:pm-governed-merges,check:pm-dispatch-gates,check:required-contexts,check:cross-package-test-inputs,check:type-check-coveragecheck-test-completeness.mjsturbo run testlog; none named). NOT MEASURED.check-governed-queue-guard.mjsGITHUB_EVENT_PATH(reads a workflow event payload). NOT MEASURED; its--self-testpasses, 129 cases.check:doc-formula-expressions@objectstack/formulanot built). NOT MEASURED.check:type-check-debt--re-measureneeds 56 built dependency closures). NOT MEASURED. Its self-test and the coverage half both passed.Beyond the union, because this diff edits gate scripts:
check-audit-scope.mjs --self-test(32 cases, up from 24),
check-governed-merges.mjs --self-test(241 assertions),check-governed-queue-guard.mjs --self-test(129 cases),check:pm-governed-prose(green —2 instruction surfaces still name all 5 registered surfaces and claim no others).
Lint — a declared narrowing, not a skip.
pnpm lintis a whole-repo scan CI owns; thisrun was scoped to the diff, and the narrowing excluded nothing:
ESLint#isPathIgnored,not guessed: 5 of the 8 changed paths are in scope, 3 (
.yml,.md,.json) are ignoredby config;
errorCount=0 warningCount=0;eslint.config.mjs's own header records that this repo"never enables type-aware linting (no
parserOptions.project, no typed@typescript-eslintrules) for ANY file", with a positive control — so no edit in thisdiff can move the verdict on a file it does not touch.
Changeset
None: this PR publishes nothing. The diff is confined to
.claude/**,.github/workflows/and
scripts/**— no workspace package (packages/*,apps/*,examples/*) is touched.skip-changesetis applied.Review
@os-zhuang @hotlong — governed surface, human merge.
.github/CODEOWNERSalso routesscripts/pm/check-governed-merges.mjs.Generated by Claude Code
Generated by Claude Code