fix(spec): author-visible refusals and prescriptions state each decision in words instead of a tracker number (stage 3) - #21521
Conversation
…ion in words instead of a tracker number The five spec sources whose refusal and prescription text an author meets at parse, lint or query time (field-key guidance, the DriverCapabilities tombstones, the datasource readOnly guidance, the retired filter operators and the legacy apiMethods strip warning) no longer cite tracker numbers. Each sentence states what was decided, or drops a citation it already explained. Text only. One test pin and one docs quote move with the text. Claude-Session: https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM Co-authored-by: Claude <noreply@anthropic.com>
…apability tombstones check:generated --fix, the one artifact it proved stale (check:docs). Claude-Session: https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM Co-authored-by: Claude <noreply@anthropic.com>
…d prescription text Claude-Session: https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check2 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. What this run could not see
Coarse fallback — 138 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 8e5a93012882bf571f3d4d16a93dc1e3d60e2ad3 && git checkout 8e5a93012882bf571f3d4d16a93dc1e3d60e2ad3
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin c205b6c350a2cf5c8efb614c70dc0e5b8efc36ee 77a448f8cc4b8cc66e79153a792b9d25291fdadc && git checkout -B drift-repro c205b6c350a2cf5c8efb614c70dc0e5b8efc36ee && git merge --no-ff 77a448f8cc4b8cc66e79153a792b9d25291fdadc
node scripts/docs-audit/affected-docs.mjs --json c205b6c350a2cf5c8efb614c70dc0e5b8efc36ee |
Contract reviewServed-tier: ① Derived judgmentsInputs. Read on GitHub: card #20749 (body and every comment, the stage-3 claim The 13 rewritten messages, each against the cited card's decision. All 13 state the decision truly, keep the message's meaning and prescription, and carry no tracker number (every added line of the diff was swept for a
Text only. Every hunk of the five sources is read: only string-literal text and Census class-(a) boundary. Sound. The 13 are the texts printed at the moment of a refusal, a strip or a parse error, and each sink is verified on ② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
ACCEPT — PR #21521 @
|
Part of #20749
Clause-②: no
Stage 3 of the
domain:speclane's share under the maintainer's A / A ruling (5902360492):packages/spec's runtime strings, which sit outside the prose-id ledger. A fresh census first, then the first group, the author-visible refusals and prescriptions, in form D. 13 messages with 14 tracker-number occurrences in fivepackages/spec/src/datasources. The card stays open for the later stages, so this PR carries no closing keyword. Text only: no key, schema shape, condition, code path, error code or status moves (AST skeleton proof below, 6 of 6 SAME).The census (at
85e29b8858)packages/spec/srcis outsidescripts/doc-authoring-prose-id.baseline.json, so the census is this lane's instrument there. It re-implements the #20513 dev round's instrument from its stated semantics (dev report 5900801368): a TypeScript-AST walk over every non-test source ofpackages/spec/src(test files counted apart) that folds a message before matching (a maximal chain of string pieces joined by+, a template literal, parentheses, a string array joined with.join(sep), and strings nested in a span, in a call or in a conditional that is an operand of the chain, all folded into the outermost message). The match is the gate's own id pattern. Hit lines come from the string leaf that carries the id, never from the raw span, so comments are never read.authoring-key-lint.ts:104(single line) reads 1 message;driver.zod.ts:299-301(a three-line+chain, the id on its third line) reads ONE message;api/error-code-ledger.zod.ts:1773-1776reads ONE message with four ids on two lines.//comment with an id inside the frozenFIELD_KEY_GUIDANCEtable (authoring-key-lint.ts:100-101) and the docblock atdriver/common.zod.ts:48-53(two ids) read 0 hits; over the whole tree, 0 hit lines fall on a comment line. Parse diagnostics 0.check-doc-authoring.mjs --census's own per-literal leg, pointed atpackages/spec/srcin a scratch copy (its root and exclusion constants changed, nothing else): 19 files, 441 id occurrences, 0 per-file differences from this census.PR/issue/cardplus a number): every hit also carries a#id; none is extra.Non-test sources: 230 messages, 441 id occurrences, in 19 files. Classified by audience:
data/authoring-key-lint.ts4,data/driver.zod.ts4,data/filter.zod.ts2,data/object.zod.ts2,data/driver/common.zod.ts1. This PR.conversions/registry.ts, thesummaryof each conversion, printed indocs/protocol-upgrade-guide.mdand inos migrate meta --json'sspecChangesdata/field.zod.ts1 (a.meta()description),api/plugin-rest-api.zod.ts2 (route descriptions)api/error-code-ledger.zod.ts14 (theSTANDARD_SYNONYM_WAIVERSandPROVENANCE_WAIVERSreasons),kernel/public-auth-features.ts3migrations/registry.ts(#20234's stage 11)Test files, counted apart (class (e)): 1803 messages, 1919 id occurrences, in 425 files: about 1702 messages (1812 ids) in test titles and 101 (107) elsewhere.
Two class (c) facts a later stage needs:
packages/lint/src/validate-empty-combinators.test.ts:275SELECTSFILTER_LOGIC_CASESbynote.includes('#5322')(4 notes, 7 ids:filter-logic-conformance.ts:420,:426,:432,:438); andpackages/services/service-analytics/src/__tests__/icontains-dialect-sql.test.ts:369pins one case NAME verbatim, id included (filter-text-conformance.ts:300,#8934).The census as a whole, file:line, id and class, is in this stage's dev report on #20749. The card's census (175 messages at
36d043be17, envelope 26, prose 149) classified by syntax; this one classifies by who reads the text, so the two do not map one to one. For instance, the 14 waiverreasons inerror-code-ledger.zod.ts, which a syntactic instrument files as refusal envelopes, are contributor-facing ledger rationale here, class (f).What this does: the 13 class (a) messages
These are the texts an author meets at the moment something is refused or rewritten: the unknown-field-key guidance that
os validateand the authoring lint print, the parse errors for retiredDriverCapabilitieskeys, the guidance forreadOnlywritten inside a datasource driver'sconfig, theINVALID_FILTERrefusal every driver face prints for$regex/$options, and the warningenable.apiMethodsprints when it strips a retired legacy value. In form D, as stages 1 and 2 applied it, the number goes; where the sentence did not already say what was decided, it now does. Every cited card was read through REST, body and every comment.authoring-key-lint.ts:104,indexguidanceindexwent in the follow-up sliceauthoring-key-lint.ts:107,indexedguidanceindexflag built no index and was removed for it"authoring-key-lint.ts:123,dataQualityguidanceDataQualityRulesschema export, as the four sibling keys were) and landing comment 5098751722 on #3733 (both orphans deleted in one PR, route 1)DataQualityRulesschema was deleted from the public API too"authoring-key-lint.ts:133,cachedguidanceComputedFieldCachedeleted; caching returns only with a consumer, the ADR-0049 enforce side)ComputedFieldCacheschema was deleted with it; nothing read it, and it returns only together with a runtime consumer"driver.zod.ts:301,:305,:309, thebulkCreate/bulkUpdate/bulkDeletetombstonesbfa3c3fd59(thetransactionalBatchbit)transactionalBatchbit, derived from the live composition so a client negotiates instead of probing"driver.zod.ts:361, thefullTextSearchtombstone$searchcompiles to$icontains;$containsstays case-sensitive, untouched)$containsitself stays case-sensitive"driver/common.zod.ts:60,READ_ONLY_BELONGS_ON_DATASOURCEfilter.zod.ts:3262, the$regexrefusal$regexretired under ADR-0049 with a loud refusal naming the replacement,$icontainsadded; a real regex on all five backends rejected)filter.zod.ts:3277, the$optionsrefusalobject.zod.ts:57, therestorestrip prescriptionenable.trashremoved, no runtime reader); 3146 is open and labelledstatus:parked(a platform recycle bin, not scheduled)enable.trashwas retired because no runtime ever read it); it returns only with a real recycle bin, and that soft-delete work is parked"object.zod.ts:58, thepurgestrip prescriptionenable.trashwas retired because no runtime ever read it)"No occurrence was left in place: no cited decision was unclear.
Quoted elsewhere
content/docs/deployment/validating-metadata.mdx:320-322quoted theindexedguidance verbatim,(#2377)included; the quote is updated in this PR.content/docs/references/data/driver.mdx,driver-sql.mdxanddriver-nosql.mdxcarry theDriverCapabilitiestombstones; they are generated, andcheck:generated --fixregenerated them (the one artifact it proved stale,check:docs).skills/**: no quote of a changed message.validation-rules.mdx, theapimethods-legacy-to-primitives.mjscodemod's docblock, source comments), not quotes; untouched.Changeset
.changeset/20749-spec-strings-stage3-state-the-decision.md:patchfor@objectstack/spec, carryingClause-②: no. After the build,packages/spec/distcarries the new sentences and none of the replaced id-bearing fragments; the(#2377, ADR-0049)and(#3146, parked)hits left indistare source comments and TSDoc that the build keeps, not these strings.Text-only proof
Stage 1's tool, unchanged except the path it loads TypeScript from: a TypeScript-AST skeleton of each changed source in which every string literal and template text is a placeholder, a
+chain is flattened and a run of adjacent string operands is one string (only its embedded expressions are kept, in order), identifiers, numbers and regex literals keep their text, every child is visited, and comments are never read. A second leg compares the TEXT of every string group in order: each group that changed must have carried a tracker id before and carry none after, and every other group must be byte-identical.85e29b8858against the head: 6 of 6 SAME on both legs (authoring-key-lint.ts,driver.zod.ts,driver/common.zod.ts,filter.zod.ts,object.zod.ts,object.test.ts), token counts identical per file, 14 groups changed (4, 4, 1, 2, 2, 1), all id-bearing before and id-free after, parse diagnostics 0/0.Controls on scratch copies of
object.zod.ts(head version), each mutation counted on disk first (1 anchor hit, replacement present, anchor gone, file differs): a function renamed reads DIFF (exit 1);===flipped to!==reads DIFF (exit 1); one literal re-split into two+operands reads SAME with no extra group changed (exit 0); a text change in a string that never carried an id reads SAME on the skeleton and VIOLATION on the text leg (exit 1). No repo file was mutated for the controls.Pins
packages/spec/src/data/object.test.ts:2545("restore/purge strip carries the retired-trash guidance") assertedtoContain('#2377'); it now assertstoContainof "enable.trashwas retired because no runtime ever read it", the same strength on the words that replaced the number.$icontains,$regexandRETIRED, which stay, or compare againstRETIRED_FILTER_OPERATORS[op].whyby reference.Tests
All builds and tests through
scripts/pm/os-verify-lock.sh, eachVERDICT command-exit 0.pnpm --filter @objectstack/spec build(it has no workspace dependencies, so the closure is the package itself); thenpnpm turbo run build --concurrency=2 --filter=./packages/* --filter=./packages/*/*("Tasks: 71 successful, 71 total") for the dist-reading gates. The tree was clean after both builds.@objectstack/spectest(thelocalproject),vitest run --project local --maxWorkers=2: "Test Files 602 passed (602) / Tests 17787 passed | 1 todo (17788)".repoproject: the 11 files that read the changed sources or the regenerated docs, plus thesrc/dataones, "Test Files 11 passed (11) / Tests 258 passed (258)". 38 of the other 40 were run too, and 561 of their tests had passed with 0 failed when a 420 s wall-clock cut stopped the run in the slowest file,scripts/build-schemas-check-mode.test.ts; the twopublish-smokefiles were not run. That remainder is not measured here and is CI's.@objectstack/spectypecheck:tsc --noEmitexit 0,check:scripts-typecheckexit 0, and "check:test-typecheck: OK — @objectstack/spec's test layer compiles under packages/spec/tsconfig.test.json; 52 file(s) / 246 error(s) / 135 pinned signature(s) held".eslint --no-inline-config --format json: 6 files, 0 errors, 0 warnings. That narrowing is complete for them: ESLint's own config resolves for each (none ignored), and this repo's config enables no type-aware linting (parserOptions.projectandprojectServiceabsent), so these edits cannot move any untouched file's verdict. The repo-widepnpm lintrun is CI's.Gates
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands(no paths) at77a448f8cc, change set 11 paths against the merge base85e29b885: 110 commands, run one at a time from the worktree, each exit code written before any pipe.--ran: "110 derived famil(ies) accounted for — 110 run, 0 NOT-MEASURED (a DERIVED zero — all 110 recorded an exit code and none of them is 3)". All 110 exit 0; the dist-reading ones (check:generated,check:docs-transcript-drift,check:dts-closure,check:dual-build-cjs-loads,check:lean-entry-closure,check:published-files,check:published-readme-links,check:sourcemap-no-sources-content) after the full build.check-changeset-fixed,check:meta-url-spelling,check:spec-changes,check:authz-resolver,check:error-code-casing,check:filter-alias-parity,check:error-code-provenance,check-published-list-mirrors,check:published-readme-exports).@objectstack/speccheck:generatedexit 0 ("All 15 generated artifacts are up to date");pnpm check:doc-authoring(self-test and run) exit 0 ("17287 customer-facing string(s) across 1246 spec sources clean" and "sibling-package prose ids hold the baseline — 72 pinned site(s) across 21 file(s) ... no growth, no burn-down unrecorded");pnpm check:nul-bytesexit 0;check-adr-0087-registrationexit 0 ("this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen)");check:empty-changesetexit 0;check-changeset-fixedexit 0;check-changeset-no-majorexit 0 on the plain run and when fed this body as apull_requestevent;check:partof-closing-keywordon this body exit 0.check-issue-citations: "no issue citations added against 85e29b8 (5 file(s) read)". The prose-id ledger is untouched:packages/specis outside it, and no gate is added or loosened.mainmoved three commits past the merge base while this ran (2ee8383f4e,25797a16e1,f9a8eb889e); none touchespackages/spec,content/docsor any file of this PR, so no merge was made.Acceptance notes
Noted, not filed:
migrations/registry.ts(48 messages, 215 ids) on packages/spec/src: 1,277 comment lines still cite 170 deleted tracker numbers (1,295 sites) — the staged remainder of ruling C+D on #19123, measured by PR #20226 #20234's stage 11.check:doc-authoring's spec leg read green onmainover all 13 of these messages. It recognises aretiredKey()prescription and a builder whose return feeds one, but not a string passed as an ARGUMENT to such a builder (retiredKey(capRemoved(key, '...'))), and its hoisted-const pass is per module, so a guidance table consumed from another module (READ_ONLY_BELONGS_ON_DATASOURCE, thewhyofFIELD_KEY_GUIDANCEandRETIRED_FILTER_OPERATORS) sits outside it.LEGACY_API_METHOD_GUIDANCEis not one of these: it is defined and consumed in its own module (object.zod.ts:52and:123), and why the leg passed it is not measured here. With this PR the population of those shapes is empty; the gap stays for the next one. No gate is changed here, by ruling.Generated by Claude Code