Repository navigation
fix(spec): os migrate meta guidance for the driver-*, kernel-* and system-* migration entries states each lesson in words, not tracker numbers (stage 3) - #20384
Conversation
…h lesson in words, not tracker numbers (stage 3) The ADR-0087 semantic entries of the driver-, kernel- and system- families carried 132 tracker-id sites in the reason / replacement / acceptanceCriteria text that `os migrate meta` prints to the author. Each site now states what the cited ruling, measurement or fix decided; ADR ids stay. Decision-batch numbers in the rewritten sentences are dropped too. No entry id, surface, from / to or matcher moves. registry.ts, spec-changes.json and the protocol upgrade guide are regenerated by their generators. The CLI guidance pin now covers the three families. Claude-Session: https://claude.ai/code/session_01QcAS3qiYYZNezaxZxaUdMV Co-Authored-By: Claude <noreply@anthropic.com>
…chema declares on the key The widened 2026-09-05 population carries two schema-declared exemptions (epoch instants, external-vocabulary mirrors); the rewritten sentence now says so rather than reading as every duration without exception. registry.ts regenerated. Claude-Session: https://claude.ai/code/session_01QcAS3qiYYZNezaxZxaUdMV Co-Authored-By: Claude <noreply@anthropic.com>
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. What this run could not see
Coarse fallback — 136 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 dcc4c2ef1453a0a6538691e7e39f610253c02e45 && git checkout dcc4c2ef1453a0a6538691e7e39f610253c02e45
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin c577e66635944b8c911e9d38adde34669e68400a 1c0dc7ad546dfd04c6f252e51f16e91f138f6587 && git checkout -B drift-repro c577e66635944b8c911e9d38adde34669e68400a && git merge --no-ff 1c0dc7ad546dfd04c6f252e51f16e91f138f6587
node scripts/docs-audit/affected-docs.mjs --json c577e66635944b8c911e9d38adde34669e68400a |
Contract reviewServed-tier: 78/78 Head unmoved across the review (re-read at the end: same sha, ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
Part of #20233
Clause-②: no
Stage 3 of a staged card. The card stays open for later stages; this PR carries no closing keyword. Text only: no entry id,
surface,from/to, conversion or matching logic moves, and the chain rewrites exactly what it rewrote before.What this does
os migrate metaprints every ADR-0087 semantic entry it crosses as one block:⚠ [protocol N] SURFACE → REPLACEMENT, thenwhy:(the entry'sreason) andverify:(itsacceptanceCriteria). AGENTS.md's runtime-string rule applies to all of it: 「Runtime strings — refusal prose, prescriptions, anything an author is shown — carry no tracker number (pnpm check:doc-authoring): the lesson goes into the text.」 Form D of ruling C+D on the parent card sets the shape: the lesson in words, and no number, dead or alive.This stage covers the next three families by site count,
driver-,kernel-andsystem-: 132 sites → 0 in the three prose fields. None of the 26 entries carries a tracker id insurface(ruling A of the stage-1 ACCEPT,5858839916, is checked and has nothing to do here). Each site now says what the cited ruling, measurement or fix decided. ADR ids stay.registry.ts,spec-changes.jsonanddocs/protocol-upgrade-guide.mdare regenerated from the entries (gen:migration-registry,gen:spec-changes,gen:upgrade-guide), never hand-edited. The stage-1 pin now holdsengine-,ui-,plugin-,driver-,kernel-andsystem-.Census — tracker ids in the author-shown fields
Instrument. The stage-2 AST instrument, unchanged: a TypeScript-AST walk over every
packages/spec/src/migrations/entries/**/*.ts. For eachentryobject literal it evaluates the string value ofreplacement,reason,acceptanceCriteriaand (counted separately)surface, joining string literals with+, then counts#followed by 4 or 5 digits at a word boundary. Validated first by reproducing the stage-1 readings on the stage-1 tree (443b2f4fdc, extracted withgit archive):driver-7 entries / 44 sites (0 / 44 / 0, 25 distinct),kernel-9 / 44 (1 / 41 / 2, 11 distinct),system-10 / 44 (0 / 42 / 2, 6 distinct),engine-5 / 67, whole tree 266 entries / 1,016 sites / 9surfacesites — every figure equal to the stage-1 census. Tree measured:objectstack-ai/objectstackat569d4d2dbf(this branch's base). Unevaluable fields: 0.Controls, same run.
17.aggregation-node-distinct-retired.tsreads 7 sites (replacement 1, reason 6), the reading stages 1 and 2 took.//lines in entry files carry a tracker id, and none is counted. Comment lines belong to the sibling card, and ⛔ this PR touches none (794 before and after).surfacesites left in the tree (other families) count 0 in the three-field total and 7 in thesurfacecolumn.Re-measured on the base, matching the stage-1 census:
driver-7 entries, 44 sites (replacement 0 / reason 44 / acceptanceCriteria 0), 25 distinct ids;kernel-9 entries, 44 (1 / 41 / 2), 11 distinct;system-10 entries, 44 (0 / 42 / 2), 6 distinct. 37 distinct ids across the three (the families share#14478,#15939,#17635and#3733).surface: 0 in all three. Whole tree: 300 entries, 843 sites, 7surfacesites.After this PR:
driver-0,kernel-0,system-0;engine-,ui-,plugin-still 0; whole tree 843 → 711 sites;surface7 (unchanged, other families).17.driver-aggregate-undeclared-key-aliases-removed17.driver-capabilities-inert-bits-removed18.driver-options-timeout-to-timeout-ms17.driver-sql-distinct-bare-filter-typed18.driver-sql-unresolvable-where-column-refused18.driver-sql-upsert-cross-row-identity-merge-refused18.driver-turso-config-local-path-wasm-retired18.kernel-compatibility-matrix-estimated-migration-time-unit-in-key18.kernel-context-preview-mode-retired18.kernel-event-bus-retention-unit-in-key18.kernel-health-check-and-hot-reload-durations-unit-in-key18.kernel-package-lifecycle-durations-unit-in-key18.kernel-plugin-health-report-durations-unit-in-key18.kernel-plugin-security-durations-unit-in-key18.kernel-runtime-config-timeout-unit-in-key18.kernel-startup-orchestrator-durations-unit-in-key18.system-cache-durations-unit-in-key18.system-collaboration-durations-unit-in-key18.system-failover-health-check-interval-unit-in-key18.system-metrics-jsdoc-durations-unit-in-key18.system-metrics-window-durations-unit-in-key18.system-object-storage-durations-unit-in-key18.system-registry-config-durations-unit-in-key18.system-tracing-otel-exporter-durations-unit-in-key18.system-tracing-span-duration-unit-in-key18.system-worker-queue-rate-limit-duration-unit-in-keyEvery citation read, and what the text now says
I read each cited issue or PR myself with single-card REST reads: the body, and the comments where a ruling or a measurement lives. Ids are in code spans so this body posts no cross-references. All 36 bare ids were resolved against this repository, because every sentence that cites one is about this repository's code; the one cross-repo id is
cloud#1651.#3733cachedfield key: measured, the parse succeeded and the removed key was dropped without a word; the orphan schema was deleted.#3821#4484IDataDriver.findStreamremoved: no production caller, two of three implementations buffered the whole set, and no tombstone because nothing parses a driver object.IDataDriver.findStream(it had no production caller, and two of its three implementations read the whole result set into memory …)"; "(IDataDriver.findStream, removed with no tombstone because nothing parses a driver object)"; by entry id in thedistinctentry#4583capabilities.*went as a whole block (11 of 11 unread).#4634DriverCapabilitiesbits: 3 live, 31 dead and tombstoned.#4914manifest.loadingandPluginHotReloadSchema; keepHotReloadConfigSchema, the side with an implementation (HotReloadManager), as the start point.#4984#5181IDataDriver's methods (DriverQuery, no redundantobject).IDataDriver's query parameters toDriverQuerynor the follow-through …"#5499driver-memory/driver-mongodb; fully lifted 2026-08-11 (comments5249019855,5252526378).#5540IStorageService.list(prefix): zero consumers, and the two adapters answered differently and both incompletely.IStorageService.list, whose two adapters answered differently and both incompletely)"#6011ctx.userrolesalias now.ctx.userrolesalias, closed at once on the maintainer's word rather than given a window)"#6075#6320distinct's third argument meant different things on memory and sql; the sql half was dispatched, the memory half held under the freeze.#6321query.aggregate/agg.funcare undeclared aliases whose only writers are driver fixtures; order: re-spell the fixtures, delete the aliases, then narrow the signature.DriverQuerylast — because the reverse order yields red nobody can explain."#6404(PR)aggregate's query parameter toDriverQuery.#7929driver-sql's filter refusal stops echoing$fieldoperands, for every caller; the full diagnostic goes to the server log.#8371#8592#8621#8622idbecomes insert-only on the merge path: a merge on a non-primary conflict key was measured rewriting the existing row's primary key.idis insert-only on the merge path (made so once a merge on a non-primary conflict key was measured rewriting the existing row's primary key)"#8755#8790INVALID_FILTER/ 400, naming the column.#8807#8926#9061(PR)#11825AdvancedPluginLifecycleConfigcontainer; the classes stay a host-driven library.#11846#14478DriverOptions.timeoutamong the seven named); ruling B again, 2026-09-05: the population is every authored and every runtime-emitted duration, minus exemptions declared on the schema.#14519#15626(PR)idleTimeout→idleTimeoutSecondsamong them.#15678kernel/: the 14 remaining duration keys carry their unit in the key name.#15679system/: the 15 remaining duration keys carry their unit in the key name;sizegot an honest name.#15939#16024timeout, removelocalPathandwasm.#17635(PR)#18669FileValue.durationandestimatedMigrationTime, each with an ADR-0087 entry; no new closed type, no narrowing of stored data.cloud#1651No call-shaped token moves: a
name(census overregistry.tsis identical before and after (297 distinct tokens), so textual call-spelling ratchets read the same.Pin —
packages/cli/test/migrate-meta-engine-guidance.test.ts, widenedCOVERED_PREFIXESis nowengine-,ui-,plugin-,driver-,kernel-,system-. The pin still spawns the real CLI (os migrate meta --from 16 --to 18) once, locates each covered block verbatim in stdout, and asserts the printed block —surfaceincluded — carries no#plus 4 or 5 digits. Anti-vacuity:REWRITTENfloor rises from 29 to 55 ids: the 26 entries of this stage (7driver-, 9kernel-, 10system-) are added, and every covered prefix must still select at least one entry;driver-sql-unresolvable-where-column-refusedreason carries two blank-line paragraph breaks, and its block is found verbatim);ADR-0112).The file keeps its stage-1 name; the header lists the six covered families.
Ablation — the widened pin can fail on a
driver-blockFrom committed state, HEAD
1c0dc7ad54, withscripts/ablation-replace.mjsin wrap mode (it owns the restore trap) andscripts/ablation-dist-preflight.mjsgating each leg. The bundle is built from the generatedregistry.ts, so that is the file mutated.registry.ts, the reason ofdriver-sql-upsert-cross-row-identity-merge-refused: anchorpre-flight refusals closed the half→pre-flight refusals (#8621) closed the half. The tool read anchor 1 → 0 and replacement 0 → 1, blobb41e1d44→8f8d226e.1 failed | 2 passed—driver-sql-upsert-cross-row-identity-merge-refused: the printed guidance cites a tracker id: expected '#8621' to be undefined.b41e1d44== HEAD,git diff HEADempty.--absentpreflight found the marker in none of 222 built files, with the working tree clean against HEAD. Pin: green,3 passed.Verification
Final head
1c0dc7ad54for every line below; each heavy run went throughscripts/pm/os-verify-lock.sh(one turn,VERDICT command-exit 0, per-step exits recorded separately).pnpm exec turbo run build --concurrency=2 --filter='@objectstack/cli^...'givesTasks: 55 successful, 55 total.pnpm --filter @objectstack/cli exec vitest run --project integration --maxWorkers=2 test/migrate-meta-engine-guidance.test.ts test/migrate-meta-default-range.test.tsgivesTest Files 2 passed,Tests 10 passed | 1 skipped(the skip is the default-range file's own pre-existingskipIf).pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/migrations src/kernel/preview-mode-retirement.test.ts scripts/build-schemas-check-mode.test.tsplus the 19 other spec test files that readMIGRATIONS_BY_MAJOR, the registry or an entry file:Test Files 24 passed,Tests 696 passed.test/vitest-tiers-partition.test.tsandsrc/utils/spec-release-changes.test.ts:Test Files 2 passed,Tests 28 passed.registry.ts:pnpm --filter @objectstack/driver-sql exec vitest run --maxWorkers=2 src/sql-driver-query-signature.test.tsgives 15 passed.pnpm --filter @objectstack/spec typecheckexits 0 (test layer: 53 files / 255 errors held in its ledger);pnpm --filter @objectstack/cli typecheckexits 0 (test layer: 3 files / 28 errors held, unchanged).node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsderives 89 families at1c0dc7ad54(aftergit fetch origin main).--ranover the recorded exit codes reads 89 derived, 89 run, 0 NOT-MEASURED, 0 UNRUN, all exit 0. They includecheck:doc-authoring("16466 customer-facing string(s) across 1135 spec sources clean"),check:issue-citations,check:migration-registry("registry.ts is current (300 semantic, 219 retired-key, 199 retired-def)"),check:spec-changes,check:upgrade-guide,check:generated("All 15 generated artifacts are up to date"),check:duration-unit-keys,check:nul-bytes,check:adr-0087-registrationandcheck:changeset-no-major.check:dual-build-cjs-loadsrefused first withPREREQUISITE NOT MET(exit 3: twelve packages outside the CLI closure had nodist/). Thosedist/directories were written later in the same pass (04:48–04:49Z, inside thecheck:type-check-debtrun, whose re-measure builds them); re-run at the same head it exits 0 (104 entries / 66 packages / 659 CJS files). The reconciled list takes that latest run.eslint --no-inline-config --format jsonover the 28 changed.tsfiles reports 28 files, 0 errors, 0 warnings.eslint.config.mjs:**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}minusNEVER_LINTED, and all 28 are in it (no file-ignored warning).parserOptions.project, no typed rules), so a text edit cannot move the verdict on a file it does not touch.merge-treeagainst862b6ce869exits 0).Acceptance notes
main.#6075and#11846answer 404 on both the issues and the pulls endpoint, re-probed with a 200 control (#14478).#6075(distinct's "never reached it" sentence):packages/drivers/driver-sql/CHANGELOG.md(commitd367f03) andsql-driver-query-signature.test.tsrecord what it did — the five drivers' implementations followedIDataDriver'sDriverQuerynarrowing. The sentence now says exactly that.#11846(preview mode):packages/spec/CHANGELOG.md(commit0c2334f),packages/spec/src/kernel/context.zod.tsandpreview-mode-retirement.test.tsrecord the 2026-08-27 ruling (Option A: remove), the three-repo zero-consumer measurement and the re-declare-fresh condition. Dropped becausemaindoes not state them: "decision-inbox batch 2" and "all four decision facets pointed the same way". "TheRuntimeMode: 'preview'and the wholeKernelContext.previewModeblock are declared, documented as an auth bypass, and enforced by nothing #11846 card records the measurement" (objectui leg) now reads "zero consumers, measured when the removal was ruled", which is what the changelog and the test header say.cloud#1651answers 403 here:objectstack-ai/cloudis not attached to this session (add_reporefused: no access). It is neither confirmed nor refuted, so its sentence was rewritten from whatmainrecords about it —packages/spec/CHANGELOG.md(0c2334f: closed 2026-08-26 with positive controls,RuntimeModezero hits,ArtifactKernelFactory20+ hits and never touchingpreviewMode) andcontext.zod.ts(OS_PREVIEW_MODEthere is routing-only). The cloud-side detailmaindoes not state —previewMode"only as a local variable" whose effect is adding wildcards to "CSRF" trusted origins — is dropped; the parenthesis that replaces it describes this repository's ownserve.ts(the one reader ofOS_PREVIEW_MODEhere only widens better-auth's trusted origins to preview-domain wildcards), which is measured onmain.#plus two or three digits (#43×13,#115×4,#151×1,#158×1). They are numbers an author is shown and cannot follow, so each is dropped. One consequence worth naming: 13 entries saidMaintainer ruling B on #14478 (2026-09-02, decision batch #43), which fused two rulings on the same card — B of 2026-09-02 (the gate and the no-baseline rename) and B of 2026-09-05, decided in that batch (the population: every authored and every runtime-emitted duration, minus schema-declared exemptions). The sentence now names both dates. The#158sentence (the agreement shape ruled an offence on 2026-09-18) is corroborated by.changeset/18075-agreement-shape-is-an-offence.mdonmain.PRcitations (PR #6404,PR #9061) were#-spelled, so the instrument saw them and they are gone. The only#left in these 26 files is on//comment lines (sibling card's surface), including aPrime Directive #13reference.kernel-health-check-and-hot-reload-durations-unit-in-keycalledshutdownTimeout's shape "the spec:tenant.zod.tsidleTimeout / sessionTimeout publish a describe with no unit, while the JSDoc one line above says seconds #14519 unit-nowhere shape".#14519's keys carried their unit in the JSDoc; "unit nowhere" is the gate's name for it (check-duration-unit-keys.tsheader: "no unit ANYWHERE (the spec:tenant.zod.tsidleTimeout / sessionTimeout publish a describe with no unit, while the JSDoc one line above says seconds #14519 shape)"), because the gate did not read JSDoc. The sentence now says what the shape is — no unit in the name or in the published describe — and that it was first measured on two tenant timeouts.18.system-metrics-window-durations-unit-in-key.tscarries a//comment saying its acceptanceCriteria sentence "is [#14478 stack 4/6]system/: the 15 remaining duration keys carry their unit in the key name — ADR-0087 conversions with readers;metrics.zod.tssizeneeds an honest name, not the mechanical one #15679's, left word for word". That sentence now says "that JSDoc-channel gap is filed as a finding of its own" where it said "is finding: check:duration-unit-keys reads .describe() but not JSDoc — a duration key documenting its unit only in JSDoc never enters the population, and one card already recorded a wrong reason because of it #15939": same content, no number. The comment is the sibling card's surface (comment lines), so it is untouched here.driver-,kernel-orsystem-semantic entry. Read at 2026-09-28T04:0xZ: the 18 open PRs' file lists (GET /pulls/{n}/files) carry 0 files matchingmigrations/entries/semantic/NN.(driver|kernel|system)-*. The Version Packages PR (#17076) lists more than 1,000 files; the 1,100 rows read carry no entry file, and it is the bot-generated release PR. Nothing in flight will be held by the widened pin on arrival.mainmoved 4 commits past the base (862b6ce869:#20364,#20341,#20366,#20352); none touchespackages/spec/src/migrations/or the pin. A driver-free bare-clonemerge-tree --write-treeof this head against862b6ce869exits 0 with no conflicted path, soregistry.tsneeds no merge, andmainwas not merged in.spec-changes.json,docs/protocol-upgrade-guide.md) are regenerated, as in stages 1 and 2; their--checklegs are green. Only the threedriver-entries registered at protocol 17 appear in them, which is why those diffs are small.git grepof test files for the 26 entry ids finds one (preview-mode-retirement.test.ts), which names the entry in a comment and reads no prose; a grep of tests for the 37 cited numbers finds only comment lines. So no test needed re-pinning this stage (stage 2'smigrations.test.tscase has no counterpart here).Line budget
Entry files: 352 changed lines (+228 / −124) across 26 files, against the stage-1 ≈400 budget. The whole diff is 776 lines (+516 / −260) in 31 files. Of the rest,
registry.tsis 352, the two projections are 18 (spec-changes.json12, the upgrade guide 6), the widened pin is 33 and the changeset is 21.Generated by Claude Code