Uh oh!
There was an error while loading. Please reload this page.
refactor(spec)!: strip internal tracker ids from the three customer-facing text populations, and widen check-doc-authoring Rule 3 to hold them - #13019
Conversation
…spec text populations Rule 3 held the zod `message:` population alone and named three neighbouring ones as deliberately out of scope pending a ruling. That ruling arrived as ruling-inheritance rather than a new decision: the founding rationale is "the ban follows the audience, not the file type", and it transfers unchanged to - strictObject unknown-key error-map options (surface/history/aliases/ guidance/guidanceSets/retiredForms) — printed verbatim at the refusing author, same audience and same moment as a refusal message; - retiredKey() tombstone prescriptions — the prescription IS the parse error; - .describe() prose — projects into content/docs/references/** and the generated skill artifacts. ADR ids and migration commands are KEPT: AGENTS.md positively requires a tombstone to carry a durable reference, and an ADR id is customer-resolvable where #NNNN is not. No exemption mechanism is added; the rule stays exemption-free by design, exactly as its own header prescribed. Positions alone were not enough. The guidance maps — and a share of the refusal messages — are hoisted into named consts and referenced from the sink, so a matcher reading only a literal's own position stops at the VariableDeclaration. collectTextSinkConsts() closes that with a per-file sink-alias fixed point. It is not a hypothetical about the new buckets: the hoisted spelling was hiding five ids from the `message:` rule itself, which had reported that population clean since the day it landed. The `seen` floor is now PER BUCKET rather than on the total, for the same reason #4932's floor is per-ROOT: .describe() alone would hold a total positive forever while the guidance matcher rotted unseen. Measured on this base: green before the widening (535 message strings, 0 violations), red after it with 584 violations — strictObject 262, describe 187, tombstone 130, message 5. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4
… text — mechanical pass
479 of the 584 literals the widened Rule 3 reports, across 84 files. Driven by
the gate's own violation list, so the edited ranges are exactly the ruled
population; every edit is inside a string literal, none touches code.
The rules, in the order they fire:
197 a parenthetical whose whole content is ids — it goes with them
166 a parenthetical carrying ids beside customer-resolvable content — the
ids go, an ADR id / protocol version / phrase stays, and a fragment
left holding an open connective ("widened by") is dropped whole rather
than stranded
110 the `history:` idiom — "Until #N [批 NN] closed this shape" states the
closure as a fact instead of citing the work that performed it
15 bare-prose idioms, each deleting the governing preposition with the id;
a stranded `since` is a worse sentence than the citation was
ADR ids, protocol versions, error codes and migration commands are kept
throughout — they are the durable references AGENTS.md requires, and they are
customer-resolvable where #NNNN is not. 485 insertions / 485 deletions: the
line count is unchanged because nothing but literal text moved.
The remaining 105 need sentence repair rather than excision and are hand-edited
in the following commit.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4…ences The 105 sites the mechanical rules could not take without rewriting the sentence around them, across 37 files. Two further mechanical clusters were folded into the rule set first (a parenthetical whose ids sit at the end of one concatenated operand while its content continues in the next; a `.describe()` opening with a bracketed `[#N]` tag), leaving 50 read and repaired one by one. Each repair states in words what the id used to point at — "the gap this key exists to close", "the console release that restores this flag", "the same silent-drop bug one level down" — rather than deleting the clause and leaving a sentence that no longer says anything. ADR ids, protocol versions, error codes and `os migrate` commands are untouched throughout. Verified beyond the gate, because an excision from mid-parenthetical can take the closing bracket with it and still parse: every concatenated string expression in all 88 touched files was compared against the branch base for bracket balance — 11,065 expressions, balance unchanged on every one. That check caught one real defect (a dropped `)` in `protocol.zod.ts`) that reads almost right and compiles fine. `check:doc-authoring` is now GREEN on the widened rule: 13,306 customer-facing strings across 670 spec sources, 0 violations, with every bucket populated (message 778 · strictObject 3282 · tombstone 751 · describe 8495) so the per-bucket blindness floor is satisfied rather than merely unbreached. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4
… negative pins
12 spec test files asserted on wording this PR reworded. Each pin now asserts
the NEW sentence rather than being deleted — `toContain('#3810')` becomes
`toContain('match-everything write')`, `toContain('#5058')` becomes
`toContain('open gap')` — so the pin still holds the fact it was written to
hold. Nine were moved mechanically and verified against the post-strip message
text (the old fragment must be gone from the corpus and the proposed new one
must actually occur in it); the remaining pins asserted only "the message
carries id #N", which has no replacement substring, and were re-pointed by hand
at the surviving clause.
Where a pin existed to hold a tracker id, it is replaced by the NEGATIVE pin
the gate's own failure text prescribes: the message must not match `#\d{3,5}`.
That keeps the assertion load-bearing instead of trivially true — a pin merely
deleted would let the id return.
Also adds the changeset: `@objectstack/spec` patch, FROM -> TO at bucket level.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4… strip The `.describe()` bucket projects into the generated reference docs, so the strip lands there too: 64 files, 772 insertions / 772 deletions, line for line. Regenerated with `check:generated --fix` on the committed merge (never in MERGE state, where `gen:schema` would roll the authorable-surface anchor back to the old fork point) and narrowed to the one artifact the check proved stale. Verified: every regenerated path is under `content/docs/references/`, `content/docs/releases/` is untouched, and no `+` line in the diff carries a tracker id. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4
The first pin pass moved the pins whose expected text still existed in the
post-strip sources. This one covers the rest, found by running the suite rather
than by pattern-matching: 92 tests across 40 files were asserting on wording
this PR reworded.
Scope was held narrow in two directions, deliberately:
- only ids this branch actually REMOVED from non-test spec sources, read out
of the diff rather than guessed — a pin on an id that still exists must
keep working, and silently weakening it would be worse than leaving it;
- only literals in an ASSERTION ARGUMENT. Test TITLES and COMMENTS keep their
ids on purpose: the gate excludes test bodies because that reader HAS the
tracker, and the ban follows the audience. An earlier line-based attempt
would have stripped 482 literals across 134 files, most of them titles.
Three shapes of repair:
- an id used as a DISCRIMINATOR between sibling refusals — `driver-credential-
refusal.test.ts` filtered issues with `message.includes('#9041')` vs
`('#9147')` — now discriminates on the remedy clause that actually differs
("the URL's own userinfo" vs "add `username` to `config`"). That helper is
why ten tests there read as "the refusal did not fire": it fired, and the
filter could not see it.
- an id that was one term of a larger expected string or regex — the id term
is dropped, the rest of the pin stands.
- an id that was the WHOLE expected value — repointed at a distinctive
surviving phrase of the same message, or, where a sibling assertion already
covers that, replaced by the negative pin the gate's own failure text
prescribes (`.not.toMatch(/#\d{3,5}/)`), so the pin stays load-bearing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4… stale Iterated against the suite rather than against a pattern: 92 -> 46 -> 13 -> 4 -> 0. Two corrections worth recording, because both produced confidently wrong states that still read as progress. **Reverted the pins that were never stale.** A bulk phrase swap repointed pins whose ids are still in the sources — in populations OUTSIDE the four ruled buckets: conversion-registry summaries, migration entries, TSDoc, and refusal prose built inside `error: () =>` callbacks (filed as a separate finding). Those tests were passing; the swap broke them. Ten sites are restored to the id they legitimately still assert. The rule the reverts encode: a pin may only lose an id this branch actually removed FROM THAT MESSAGE, which is narrower than "an id this branch removed somewhere". **Restored a regex the repair had eaten.** The regex cleaner stripped `\(\)` to tidy emptied groups and took the literal `` `find\(\)` `` in `driver.test.ts` with it. That failure was briefly, and wrongly, diagnosed as pre-existing on main — the phrase really was absent from every revision, because the mangled regex was what was being compared. The evidence that settled it was `git diff` against the branch base, not a grep for the phrase. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4
The last stale pin. `#4722` on the `areas[].requiredPermissions` tombstone was first repointed at a phrase from the SIBLING `visible` guidance, which is a different message on the same surface — it matched neither. It now pins "re-checked in the shell", the clause that states how the gate is actually enforced, which is what the citation stood in for. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4
…rip-tracker-ids # Conflicts: # packages/spec/src/ui/view.zod.ts
… merge deferral) `origin/main` moved 21 commits during the run. One conflict, in `packages/spec/src/ui/view.zod.ts` — the cross-wave file declared at claim time: main rewrote the `visibleWhen` describe to document `current_user` resolution, this branch had stripped two ids from the older sentence. Resolved by taking main's newer prose whole and applying this branch's strip to it, so neither the new content nor the convention is lost. Asserted mechanically before staging: no tracker id survives in the resolved line. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4
📓 Docs Drift CheckThis PR changes 1 package(s): 62 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 6 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 126 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 3d17803a99afe1fa6e42ee1009ed00f8a738f029 && git checkout 3d17803a99afe1fa6e42ee1009ed00f8a738f029
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin df1c75c4b027e6b7be2c911108cdb8ca8f745e61 5ebfe8ba0581ff2506fbc75b3510ef4c46f53eb8 && git checkout -B drift-repro df1c75c4b027e6b7be2c911108cdb8ca8f745e61 && git merge --no-ff 5ebfe8ba0581ff2506fbc75b3510ef4c46f53eb8
node scripts/docs-audit/affected-docs.mjs --json df1c75c4b027e6b7be2c911108cdb8ca8f745e61
|
…erand scars CI was red on Test Core with 8 failures in @objectstack/metadata. Cause: the pin iteration ran against the spec suite only, and packages OUTSIDE spec pin spec messages too. This round sweeps every tracked test file in every package and repairs two defect classes the spec-only pass could not see. **Stale consumer pins (4 sites, 4 packages).** Same narrow rule as before — a pin may only lose an id this branch removed FROM THAT MESSAGE: - metadata/plugin-artifact-forward-conversion: `…17 (#12497, ADR-0049)` -> `…17 (ADR-0049)`; the surviving ADR id carries the pin. - metadata/stored-envelope: `toContain('#5309')` had no replacement fragment, so it becomes the negative pin the gate prescribes, beside the existing `storage bookkeeping` assertion which stays the positive half. - objectql/engine-unknown-option: two pins — a regex term `#8057, ADR-0049` trimmed to `ADR-0049`, and `/#4286, ADR-0049/` likewise. - service-automation/screen-nodes: `/#4343/` repointed at `` /`script.config.actionType` was removed/ `` plus a negative id pin. **Cross-operand scars in the prose itself (13 sites, 10 spec sources).** An excision that spans two concatenated operands leaves a scar neither a per-literal tidy nor `git diff` review can see, because each literal is individually well-formed — it appears only once the `+` chain is joined, which is exactly what the customer reads: '… sees a body ' + '(#5309), so …' -> '… sees a body ' + ', so …' reads as "a body , so" 12 were space-before-punctuation / double-space at an operand boundary, found by a joined-chain scan and repaired by trimming the left operand. The 13th was a stranded possessive: `RECORD_NOT_FOUND (#7867's not-found gate)` had become `( 's not-found gate)`, now `(the by-id not-found gate)`. Verified by running suites, since substring probes proved unreliable in BOTH directions here — they missed objectql/#8057 and service-automation/#4343 (the id survives elsewhere in spec) and wrongly cleared metadata/#5309 (it survives elsewhere in that package). Ten packages, 27,704 tests, all green: spec 11675 · objectql 4268 · runtime 2898 · metadata-protocol 2010 · lint 2300 · plugin-auth 1695 · service-automation 1091 · core 1015 · metadata 674 · plugin-dev 58. All 14 spec artifacts regenerated and current. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4
Uh oh!
There was an error while loading. Please reload this page.
…nt in the http-cache example The seventh site of the card's slice, held back on the first pass because packages/spec/src/api was inside a declared in-flight surface (#12522). That claim has since closed (merged PR #13019), and the re-run check finds the directory's only current claim (#13040) declared file-precisely at endpoint-publish-gate.ts with 'Nothing else' -- disjoint from this file, and its PR touches exactly that one file. No open PR touches http-cache.zod.ts. Only the plural type segment moves. The /metadata prefix is a separate axis the card does not scope, and the sibling example at :188 uses it too. Part of #11042 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CPrUz21stTFhJRUirdc4yw
Fixes#12522
Strips internal issue-tracker ids from the three adjacent customer-facing text
populations in
packages/spec, and widenscheck:doc-authoringRule 3 so theystay stripped.
This is ruling-inheritance, not a new ruling. The founding rationale is "the
ban follows the audience, not the file type", and re-checking it against each
bucket answers the card without a new maintainer decision:
strictObjectguidance and
retiredKey()prescriptions are printed verbatim at the refusingauthor — same audience, same moment as a refusal message — and
.describe()prose projects into
content/docs/references/**and the generated skillartifacts, where a customer cannot resolve a bare id either.
What was stripped, and what was deliberately kept
strictObjectguidance / history / aliasesUntil #4001 closed this shape these were dropped silentlyUntil this shape was closed these were dropped silentlyretiredKey()+*_RETIRED_KEY_GUIDANCEremoved in @objectstack/spec 17 (#3894) — use `skills`.removed in @objectstack/spec 17 — use `skills`..describe()docs proseParsed but no runtime consumer yet (liveness #1878/#1893).Parsed but no runtime consumer yet.message:— the hoisted spellinga pair that cannot work as written (#9041).a pair that cannot work as written.KEPT throughout: ADR ids, protocol and package versions, error codes, and the
os migrate meta --from PROTOCOLmigration commands. AGENTS.md positivelyrequires a tombstone prescription to carry a durable reference; those are the
forms a customer can actually resolve, so an issue id riding beside an ADR id was
the strippable half — never the ADR id itself.
No accept/reject behaviour changes anywhere: no schema takes or refuses anything
it did not before, and no key, default or error
codemoved. Message wording andgenerated docs only.
The rule widening — with its before/after evidence
Rule 3 recognised two positions (
message:, positional validator messages). Itnow recognises five, and there is still no exemption mechanism — widening
means widening the recognised positions, exactly as the rule's own header
prescribed.
The green line is a census, not a silence: all four buckets are populated, so
the per-bucket
seenfloor is satisfied rather than merely unbreached.The hoisted-const spelling — why positions alone were not enough
A position-only matcher stops at the first
VariableDeclarationit climbs into,and this tree overwhelmingly hoists its guidance maps (
const TOOL_RETIRED_KEY_GUIDANCE = {…}, referenced asguidance: …) and some of itsrefusal messages (
message: CREDENTIALS_REF_MONGO_URL_NO_USER_REFUSED). SocollectTextSinkConsts()runs a per-file sink-alias pass to a fixed point.That is not a hypothetical about the new buckets: the hoisted spelling was hiding
five ids from the
message:rule itself, which had reported that populationclean since the day it landed. Those five are the
messagerow above.The
seenfloor also moved from the total to per bucket —.describe()alone would hold a total positive forever while the
guidancematcher rottedunseen, which is the same argument the per-ROOT floor already rests on.
Fork condition — one member
Charter item 4: a tombstone the strip would leave with no durable reference at
all is reported, never stripped silently and never given an invented reference.
All 132 changed tombstone prescriptions were audited against the three accepted
forms — 96 carry an ADR id, 83 the migration command, 59 a FROM to TO mapping,
128 a version anchor.
One member has none of them:
Its only reference was the issue id: no ADR, no
os migratecommand, and —unlike every other member — no version anchor either. The prescription is
still complete and actionable ("drop the key", plus what to do instead), so this
is a missing citation, not a missing instruction. No replacement has been
invented; the removal version is a fact for the maintainer to supply, and it is
reported on the card for that decision.
Verification beyond the gate
Bracket balance. An id excised from mid-parenthetical can take the closing
bracket with it — the result still compiles and still reads almost right, and no
gate sees it. Every concatenated string expression in all 88 touched sources was
compared against the branch base: 11,065 expressions, balance unchanged on
every one. That check caught one real defect (a dropped bracket in
protocol.zod.ts).Sentence quality. The transform was rule-based over ranges the gate itself
listed, so nothing outside the ruled population was edited; 479 sites were taken
mechanically and 105 were read and repaired by hand, because deleting the
clause would have left sentences that no longer said anything. Repairs state in
words what the id pointed at ("the gap this key exists to close", "the console
release that restores this flag"). A dangling-connective guard drops a
parenthetical rather than stranding it —
(#8778, widened by cloud#1395)becomesnothing, never
(widened by).Pin tests moved with the strings (charter item 3), iterated against the suite
rather than against a pattern: 92 to 46 to 13 to 4 to 0 failures. Each pin now
asserts the new sentence instead of being deleted, and where a pin existed only
to hold a tracker id it is replaced by the negative pin the gate's own failure
text prescribes (a
not.toMatchon the id shape), so the assertion staysload-bearing.
Two corrections along the way are worth a reviewer's attention, because both
produced states that read as progress while being wrong:
ids are still in the sources — in populations outside the four ruled buckets
(conversion registries, migration entries, TSDoc, and refusal prose built
inside
errorcallbacks). The rule the reverts encode: a pin may only lose anid this branch removed from that message, which is narrower than "an id
this branch removed somewhere".
driver.test.ts. That failure was briefly diagnosed as pre-existing onmain—the phrase really was absent from every revision, because the mangled regex was
what was being compared.
git diffagainst the branch base is what settled it.Test titles and comments keep their ids on purpose: the gate excludes test
bodies because that reader has the tracker, and the ban follows the audience. An
earlier line-based attempt would have stripped 482 literals across 134 files,
most of them titles — it was discarded for an AST pass scoped to assertion
arguments.
Gates
Re-derived from the real changeset with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstackrather than from the dispatch brief.The union below was run on the FINAL commit,
dd7c13d57— after the secondmerge of
mainand its regeneration, so it is a verdict on the tree this PRactually proposes:
check:i18nis NOT MEASURED here, not green: it refuses withPREREQUISITE NOT MET — the workspace CLI is not builtand says so explicitly rather thanreporting a clean scan. Nothing in this diff touches a translation bundle; CI
builds the workspace and runs it there.
origin/mainwas merged viascripts/pm/os-regen-merge.shand the generated chain regenerated on thecommitted merge — 64 files under
content/docs/references/, 772 insertions /772 deletions, no added line carrying a tracker id,
content/docs/releases/untouched.
Out-of-scope finding
#13002 — a fourth customer-facing refusal population, built inside
errorcallbacks (28 literals / 32 ids across 8 files), is structurally unreachable by
Rule 3 even widened. Same audience as everything stripped here, but the census on
this card counted it in
otherand the charter named three buckets, so strippingit would widen a ruling rather than apply one. Filed, not fixed.
Cross-wave note
Declared at claim time and repeated here: this PR's edits are prose-only and
disjoint from the semantic regions of the gated drafts on
ui/view.zod.ts,contracts/*anddata/aggregation-conformance.ts.data/field.zod.tsis atrigger file of an on-hold card — re-pricing input only, no rider work. The
second merge of
mainconflicted once, inui/view.zod.ts: resolved by takingmain's newer
visibleWhenprose whole and applying this branch's strip to it.Generated by Claude Code