Repository navigation
docs(spec): lead the deletion-gate anchor description with what it must NOT be used for - #16199
Merged
huangyiirene merged 1 commit intoSep 6, 2026
Conversation
…st NOT be used for `authorable-surface.base.json` already called itself an anchor. The warning answered the wrong question: it explains what the file is FOR (the #4650 deletion gate) and never what it must not be USED for, so a reader arriving with "is this key authorable today?" reads "a verbatim copy of the keys in authorable-surface/" and takes it as the live set. The description now leads with the negative, names the live ratchet, and names `scripts/docs-audit/affected-docs.mjs` as the reference consumer for the union read. No count is stated: the gate prints the current delta on every run, and a hard-coded number in a warning about staleness is the same defect one level up. The string lives in `build-schemas.ts` — the committed file is byte-compared against `serializeSurfaceBase()`, so both halves move together or the gate is fatal. `baseRev` and `keys` are untouched, and no consumer's input changes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T6HeZvT9wdSJD1ZxJb5Eno
Contributor
📓 Docs Drift Check
What this run could not see
Coarse fallback — 130 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
This was referenced Sep 6, 2026
huangyiirene
marked this pull request as ready for review
September 6, 2026 11:15
huangyiirene
enabled auto-merge
September 6, 2026 11:16
huangyiirene
deleted the
claude/issue-14612-authorable-surface-anchor-warning
branch
September 6, 2026 11:48
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 17, 2026
… of a number (objectstack-ai#17795) Clause-②: no Closes objectstack-ai#16200 ## What this changes Two pieces of prose stated the `authorable-surface.base.json` anchor's lag behind the live ratchet as a bare count, with no revision and no date attached, so a reader took each as a statement about the tree in front of them: - `scripts/docs-audit/README.md` — "measured on this tree it lagged the ratchet by 532 keys" - `scripts/docs-audit/affected-docs.mjs`, the `AUTHORABLE_KEY_SUFFIX_RE` docblock — the same sentence, plus "an exact-match lookup would silently drop all 103 of them" Both now name **`check:authorable-surface`** as the thing that prints the current delta on every run. A pointer to a live instrument replaces a number that decays. ## Why not simply write today's number in **A citation of a past measurement** ("measured on 2026-09-02 at `abc1234`: 532") and **a live claim** ("it lags by 532") are two different kinds of sentence with two different repairs. Both carriers were written as the second and read as the first. Re-measuring and writing the fresher figure in would manufacture the second kind all over again — it buys a few days and reproduces the defect. ## The irony, which is itself the argument for this shape PR objectstack-ai#16199 landed a warning about a **stale artifact** and deliberately stated **no count**, for exactly this reason: *"No count is stated: the gate prints the current delta on every run, and a hard-coded number in a warning about staleness is the same defect one level up."* The two live counterexamples sat in the very file that PR names as the **reference consumer** for the union read. The convention already existed in this codebase, was chosen deliberately by the adjacent card, and these two sentences predate it. That is the whole case for pointing at the instrument rather than pinning a figure to a rev: the correct form was already here, one directory away, in prose about this exact artifact. ## The argument the sentences support is untouched — and stronger The claim is that the anchor's lag is real, load-bearing and growing. It survives verbatim, stated in the terms that cannot rot: `data/Object:editMode` and every key of `security/OrgScopingEntitlement` and `api/ProvenanceWaiver` are absent from the anchor and present in `authorable-surface/`, and the class grows with every key authored after `baseRev`. Those are the same facts `--self-test` pins behaviourally, against the live artifacts, on every run. ## Re-measurement (historical citations, not new live claims) Measured at `952b9c5e59b7` on **2026-09-12**, by reading the same two artifacts the tool reads and stripping the annotation with the tool's own `AUTHORABLE_KEY_SUFFIX_RE`: | figure | as written | card, `d5d8d50db`, 2026-09-06 | this PR, `952b9c5e59b7`, 2026-09-12 | |:--|--:|--:|--:| | live-ratchet keys absent from the anchor (annotation stripped) | 532 | 604 | **863** | | annotated (`[RETIRED]`-class) keys the ratchet carries | 103 | 206 | **209** | | shard files under `packages/spec/authorable-surface/` | 11 | 14 | **14** | Ten days moved the headline figure by 62%. ⛔ None of these numbers is written into the tree by this PR; they are here as the evidence for removing them. **The instrument was verified, not assumed.** `pnpm --filter @objectstack/spec check:authorable-surface` prints, on this tree: ``` ℹ️ authorable-surface.base.json trails the baseline at 952b9c5: it mirrors the older 53ef057, and they differ by 970 key(s) only that baseline has, 1034 only the anchor has ``` Those two counts reproduce byte-exactly from the raw (unstripped) key sets — `970` live-only, `1034` anchor-only — so the gate really does answer the question the deleted sentences were answering, and it answers it for whatever tree it runs on. ## The third carrier, and why it is in this diff and not a follow-up The `--self-test` battery carried a **verbatim copy** of the same `103` sentence beside the `[RETIRED]` case (`affected-docs.mjs`, the `buildContainerSurface.isAuthorable` tombstone check). Leaving it would have left one carrier in the same file still asserting a figure this PR removes from the docblock twelve hundred lines up — a contradiction created by this change rather than inherited. Mechanical, identical repair, same file, same gate family, no new verification surface. ## 验收备注 ### Reverse-read: other bare numbers of the same kind in `scripts/docs-audit/` The discriminating test used: *does the sentence read as current state, and does the quantity decay with the tree?* A number bound to a named commit, a named card or a dated ruling is a citation of a past measurement and is not this defect. **One finding, and it is outside this card's scope by triage's own boundary:** the README states the hand-written corpus as **178 pages** in five places (lines ~188, ~192, ~493, ~627), unanchored. `check:docs-audit-scope` prints `195 hand-written doc(s)` on this tree. Every one of those sentences sits inside a historical measurement narrative bound to an episode or a card (objectstack-ai#4162, objectstack-ai#4851, objectstack-ai#6893, the guard-design round), which is the weaker reading that keeps them defensible — but "the anchor derivation reads the same 178-page corpus the old one did" is written as present tense. Noted, **not filed**: triage ruled explicitly that a repo-wide hunt for hard-coded counts "is a different card with its own population", and the population card is the PM's to open, not a rider on this one. Successor: the `domain:devx` PM seat. **Zeros, reported as readings rather than as silence:** - **Other `532` / `103` references anywhere in the repo — zero.** `git grep` for `532 keys`, `103 of them`, `lagged the ratchet`, `lagged the live ratchet` over the whole tree returns nothing after this diff, so no sibling file quotes the figures these carriers held. - **Hard-coded shard count (`11`) in `scripts/docs-audit/` — zero.** The card's third figure came from objectstack-ai#14612's triage comment, never from a carrier in this directory; there was nothing here to repair. - **`"measured on this tree"` live-claim phrasing inside `scripts/docs-audit/` — two other sites, both sound.** `affected-docs.mjs` line ~1344 ("zero commands declare `static topic`, and the one `static id` (`init.ts` to `'init'`) agrees with its path") was re-verified against `packages/cli/src/commands/` and is **still true** — zero `static topic`, and `init.ts:912` carries `static override id = 'init'`. It is also a claim about a *zero* whose failure direction the docblock names, not a decaying magnitude. Line ~6019 already carries its rev (`f5a7f9c88`, 7 warm runs) and is the correct form. - **Anything this diff falsifies elsewhere in the tree — zero.** No pin, fixture, gate, workflow or doc reads these sentences; the repo-wide grep above is the reading. Two sites outside `scripts/docs-audit/` use the same "measured on this tree" phrasing (`scripts/check-bash32-floor.mjs` ~121, `scripts/pm/dispatch-gates.mjs` ~8307; the second carries a count, "one of the six"). Out of scope here by the same triage boundary; they belong to the population card above. ### Verification All **34** gate families `scripts/pm/dispatch-gates.mjs` derives for this change set were run in this worktree and every one exited `0`; reconciled with `--ran` carrying exit codes: ``` ✓ dispatch-gates --ran: 34 derived famil(ies) accounted for — 34 run, 0 NOT-MEASURED (a DERIVED zero — all 34 recorded an exit code and none of them is 3). ``` The load-bearing ones for this diff, with the verdict lines they printed: - `pnpm check:docs-audit-scope` — `✓ affected-docs self-test: 585 cases pass.` / `✓ check-audit-scope self-test: 32 cases pass.` - `node scripts/docs-audit/check-affected-docs.mjs` — exit `0` (this is the discoverable name for `affected-docs.mjs --self-test`; no `*.test.ts` in the tree names `affected-docs`, so this wrapper is the script's whole suite) - `pnpm --filter @objectstack/spec run check:authorable-surface` — exit `0`, and the source of the delta line quoted above - `pnpm check:nul-bytes` — exit `0`; plus a direct control-character sweep over both edited files, clean Repo-wide `pnpm lint` was **not** run — it is the CI-owned run, and no narrowing is claimed here in its place. No changed file carries executable behaviour. ### Changeset: `skip-changeset` Measured rather than assumed. `scripts/docs-audit/` is repo tooling: the root manifest is `private: true`, and no non-private package's `files[]` names a path that reaches `scripts/` (checked across every `packages/**/package.json`; positive control — `packages/spec`'s `files[]` resolves to real published paths such as `dist`, `json-schema`, `api-surface`, so the zero is a measurement and not an empty scan). Nothing published moves ⇒ the `skip-changeset` **label** is applied to this PR. ### Clause-② `no`. The diff changes explanatory prose in repo tooling only. It touches no spec schema, no published `exports`, no wire shape and no authorable key — nothing a contract review has a surface to review. The card-side carrier the PM placed reads `no`; this PR-body carrier agrees and was self-verified with `readClause2Line()`. ### Governance No governed surface is touched (`docs/adr/**`, `.claude/**`, `skills/**`, `AGENTS.md`, `CLAUDE.md` are all absent from the file list). Left as a **draft** and **not** armed for auto-merge — arming is the PM's act. --- _Generated by [Claude Code](https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU)_ Co-authored-by: Claude <noreply@anthropic.com>
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 #14612
The anchor already called itself an anchor. Triage's sharpest point holds, and I verified it against the file before writing: the
descriptionexplains what the file is for (the #4650 deletion gate) and never what it must not be used for, so a reader arriving with "is this key authorable today?" meets "a verbatim copy of the keys inauthorable-surface/" first and takes it as the live set. This adds the missing warning, not a warning.What landed
descriptionnow leads with the negative, in the ruled order:⛔ NOT the live surface — a pinned anchor for the deletion gate; the live surface is authorable-surface/*.json. It then says what the file must not answer, and only afterwards what it is.docs/spec-generated-artifact-sharding.md, which is the only doc naming both artifacts side by side, in the bullet where a reader actually meets the anchor. It answers "which artifact answers is this key authorable today": the live per-category ratchet, never the anchor.scripts/docs-audit/affected-docs.mjsis recorded as the reference consumer for the union read, in both places, with its--self-testpins on the lag and the[RETIRED]stripping named. Prose says do not use the anchor alone; the reference says here is how to read both correctly.keysandbaseRevare untouched (the JSON diff is 1 line changed, 1 line added). No consumer's input changes. No rename.Measured, because the dispatch asked me to falsify it
The file is generated, and the edit therefore belongs at its generator — which is why
packages/spec/scripts/build-schemas.tsis in this diff and was not in the claimed file surface.SURFACE_BASE_DESCRIPTIONthere is the only producer of that string, andreadCommittedSurfaceBase()byte-compares the committed file againstserializeSurfaceBase(doc.baseRev, doc.keys)— so the JSON alone cannot be edited at all. Both halves move together in one reviewed diff, orcheck:authorable-surfaceis fatal.A re-anchor is NOT required for that, and the constant's own doc comment said it was. The canonical form is computed from the committed
baseRevandkeys, so rewriting both halves to the same bytes leaves the anchor authentic and the gate green. The comment is corrected in place and now records the measurement instead of the assumption. The clause it deliberately deferred — thatWritten only by gen:schemaunder-states--update-base— is left deferred, as that comment asks, for the diff that next re-anchors.Plain
gen:schemadoes not rewrite this file. Since #5358 only--update-basewrites it. Evidence: a full@objectstack/specbuild (which runsgen:schema) leftgit statusempty on this branch.The card's 532-key lag has already rotted, so no number is stated anywhere in the new prose. Re-measured at
d5d8d50db, four days after the card: 604 keys live-only after annotation stripping, against the card's 532. The gate prints the current delta itself on every run, and the prose points at that instead. The triage comment's "11 shard files" has moved to 14 for the same reason.Consumer census re-run rather than trusted, and it holds.
git grep -l authorable-surface.basegivespackages/spec/scripts/build-schemas.ts,check-generated.ts,lib/sharded-artifacts.ts,scripts/pm/dispatch-gates.mjs,scripts/regen-artifacts.mjs,scripts/docs-audit/affected-docs.mjsplus its README, three test files,.gitattributes,packages/spec/package.json,docs/spec-generated-artifact-sharding.md, the spec CHANGELOG and.claude/agents/os-dev.md. No live-decision consumer beyond the union read.dispatch-gates.mjscarries a standing instruction to re-point one self-test case only if this file is removed — it is not, so that pin is unmoved.Reverse verification — two ablations, both committed-then-mutated, both restored and proven
Expected direction stated before each run, and both turned red:
check:authorable-surfaceexit 1,❌ authorable-surface.base.json does not match its generated form (#5235).Mutation proven on disk by marker counts (injected 1, removed 0) and by a blob hash differing from HEAD; restore proven by the blob returning to the HEAD hash andgit diff HEADempty.affected-docs.mjs→--self-testexit 1 on exactly the pin the prose names:✗ a key added since the anchor commit is still authorable — the live ratchet is read too: data/Object:editMode → expected true, got false. Same on-disk and restore proofs.Gates
Derived against the real diff with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands(3 paths, merge based5d8d50db, three-dot): 52 families derived, 50 run green, 2 declared narrowing.--ranreconciliation named 3 UNRUN; one of those,check:doc-formula-expressions, was cleared by building the two packages its own refusal named (turbo run build --filter=@objectstack/formula --filter=@objectstack/lint) and then re-running it — green.Declared narrowing, CI-owned:
check:dts-closureandcheck:dual-build-cjs-loadsboth exit 3 — PREREQUISITE NOT MET, not a finding and not a pass: each reads built output and no workspace package has adist/in a fresh worktree. Clearing them needs a whole-repo build, which is the farm's run, not this card's.Also green:
pnpm lintwhole-repo, 75s, at4ad3eba2a(no narrowing needed, so none is claimed);pnpm --filter @objectstack/spec typecheckincludingcheck:scripts-typecheck, whosetsconfig.scripts.jsonnamesbuild-schemas.tsexplicitly;check:pm-dispatch-gates(1511 cases, 513s);affected-docs.mjs --self-test(585 cases).Tests. Rule for a gate-script edit is its own suites: every
*.test.tsinpackages/spec/scripts/namingbuild-schemas, plus thegit grephits elsewhere in the package — 19 files, 541 tests, all green. The load-bearing one isscripts/build-schemas-check-mode.test.ts, which seeds its fixtures with the description read off the committed file and runs this generator against them, so it is red for any one-sided edit.Affected-package narrowing, declared.
turbo ls --affectedfrom the branch point lists 75 packages, because@objectstack/specis the dependency-graph root and everything reaches it — not because this diff moved anything they consume.packages/spec's publishedfilesarray carries neitherscripts/norauthorable-surface.base.json, and nosrc/file changed, so no package's inputs moved. Their suites are CI's.@objectstack/spechas zero workspace dependencies, so the closure buildpnpm --filter '@objectstack/spec^...' buildmatched no projects — reported here because a zero-match pnpm filter exits 0 and reads like a pass.Changeset
skip-changeset: this PR publishes nothing.packages/spec'sfilesarray isdist,json-schema,liveness,prompts,llms.txt,README.md,src/**/*.zod.ts,CHANGELOG.md,api-surface,spec-changes.json— none of the three changed paths is in it, and the generated output that IS published is byte-unchanged (a full spec build leftgit statusempty).Generated by Claude Code
Generated by Claude Code