Skip to content

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

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-20233-migrate-meta-tracker-free-stage-3
Sep 28, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-20233-migrate-meta-tracker-free-stage-3

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

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 meta prints every ADR-0087 semantic entry it crosses as one block: ⚠ [protocol N] SURFACE → REPLACEMENT, then why: (the entry's reason) and verify: (its acceptanceCriteria). 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- and system-: 132 sites → 0 in the three prose fields. None of the 26 entries carries a tracker id in surface (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.json and docs/protocol-upgrade-guide.md are regenerated from the entries (gen:migration-registry, gen:spec-changes, gen:upgrade-guide), never hand-edited. The stage-1 pin now holds engine-, ui-, plugin-, driver-, kernel- and system-.

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 each entry object literal it evaluates the string value of replacement, reason, acceptanceCriteria and (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 with git 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 / 9 surface sites — every figure equal to the stage-1 census. Tree measured: objectstack-ai/objectstack at 569d4d2dbf (this branch's base). Unevaluable fields: 0.

Controls, same run.

  • Lit: 17.aggregation-node-distinct-retired.ts reads 7 sites (replacement 1, reason 6), the reading stages 1 and 2 took.
  • Dark (comment lines): 794 // 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).
  • Dark (field boundary): the 7 surface sites left in the tree (other families) count 0 in the three-field total and 7 in the surface column.

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, #17635 and #3733). surface: 0 in all three. Whole tree: 300 entries, 843 sites, 7 surface sites.

After this PR: driver- 0, kernel- 0, system- 0; engine-, ui-, plugin- still 0; whole tree 843 → 711 sites; surface 7 (unchanged, other families).

entry sites (replacement / reason / acceptanceCriteria)
17.driver-aggregate-undeclared-key-aliases-removed 6 (0 / 6 / 0)
17.driver-capabilities-inert-bits-removed 4 (0 / 4 / 0)
18.driver-options-timeout-to-timeout-ms 1 (0 / 1 / 0)
17.driver-sql-distinct-bare-filter-typed 9 (0 / 9 / 0)
18.driver-sql-unresolvable-where-column-refused 14 (0 / 14 / 0)
18.driver-sql-upsert-cross-row-identity-merge-refused 9 (0 / 9 / 0)
18.driver-turso-config-local-path-wasm-retired 1 (0 / 1 / 0)
18.kernel-compatibility-matrix-estimated-migration-time-unit-in-key 5 (0 / 5 / 0)
18.kernel-context-preview-mode-retired 5 (1 / 4 / 0)
18.kernel-event-bus-retention-unit-in-key 3 (0 / 3 / 0)
18.kernel-health-check-and-hot-reload-durations-unit-in-key 7 (0 / 5 / 2)
18.kernel-package-lifecycle-durations-unit-in-key 3 (0 / 3 / 0)
18.kernel-plugin-health-report-durations-unit-in-key 3 (0 / 3 / 0)
18.kernel-plugin-security-durations-unit-in-key 4 (0 / 4 / 0)
18.kernel-runtime-config-timeout-unit-in-key 11 (0 / 11 / 0)
18.kernel-startup-orchestrator-durations-unit-in-key 3 (0 / 3 / 0)
18.system-cache-durations-unit-in-key 3 (0 / 3 / 0)
18.system-collaboration-durations-unit-in-key 4 (0 / 4 / 0)
18.system-failover-health-check-interval-unit-in-key 3 (0 / 3 / 0)
18.system-metrics-jsdoc-durations-unit-in-key 12 (0 / 12 / 0)
18.system-metrics-window-durations-unit-in-key 5 (0 / 3 / 2)
18.system-object-storage-durations-unit-in-key 3 (0 / 3 / 0)
18.system-registry-config-durations-unit-in-key 3 (0 / 3 / 0)
18.system-tracing-otel-exporter-durations-unit-in-key 5 (0 / 5 / 0)
18.system-tracing-span-duration-unit-in-key 3 (0 / 3 / 0)
18.system-worker-queue-rate-limit-duration-unit-in-key 3 (0 / 3 / 0)
total, 26 entries 132 (1 / 127 / 4)

Every 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.

cited what it decided (read) how the text now carries it
#3733 The pruned cached field key: measured, the parse succeeded and the removed key was dropped without a word; the orphan schema was deleted. "an earlier field-key prune measured exactly that — the parse succeeded and the removed key was dropped without a word" (health-check, OTel exporter)
#3821 The sharing-rule page: an unsortable query fell through to an empty page, and the driver fix made an unsortable query lose its ORDER BY, not its rows. "the unknown-column recovery ladder (an unsortable query loses its ORDER BY, not its rows)"; "the ladder's own premise — rows matter more than their order"; "the ladder's recoveries"
#4484 IDataDriver.findStream removed: no production caller, two of three implementations buffered the whole set, and no tombstone because nothing parses a driver object. "Retiring 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 the distinct entry
#4583 The datasource ledger's dead keys removed; capabilities.* went as a whole block (11 of 11 unread). "was retired separately, as a whole block nothing read"
#4634 Audit of all 34 DriverCapabilities bits: 3 live, 31 dead and tombstoned. the entry already states the audit ("the follow-up audit checked every bit"); the trailing id is dropped
#4914 Maintainer, 2026-08-04: remove manifest.loading and PluginHotReloadSchema; keep HotReloadConfigSchema, the side with an implementation (HotReloadManager), as the start point. "kept twice: as the hot-reload vocabulary that had an implementation when the manifest-side copy was removed, …"
#4984 An org-axis red-line gate read only aliases the schema rejects while its own fixtures spelt them: tests green, rule dead. "the family of the org-axis red-line gate that read only rejected aliases while its own fixtures spelt them, so its tests stayed green and the rule stayed dead"
#5181 Narrow the query parameter of IDataDriver's methods (DriverQuery, no redundant object). "neither the narrowing of IDataDriver's query parameters to DriverQuery nor the follow-through …"
#5499 Maintainer, 2026-08-05: freeze investment in driver-memory / driver-mongodb; fully lifted 2026-08-11 (comments 5249019855, 5252526378). "the maintainer's 2026-08-05 investment freeze on driver-memory, which was lifted on 2026-08-11"
#5540 Remove IStorageService.list(prefix): zero consumers, and the two adapters answered differently and both incompletely. "(the zero-consumer IStorageService.list, whose two adapters answered differently and both incompletely)"
#6011 Maintainer: close the ctx.user roles alias now. "(the ctx.user roles alias, closed at once on the maintainer's word rather than given a window)"
#6075 404 — see Acceptance notes. "the follow-through that brought five drivers' implementations in line"
#6320 distinct's third argument meant different things on memory and sql; the sql half was dispatched, the memory half held under the freeze. "(the measurement that found the two drivers reading this argument differently split the fix: the sql half is this entry, and the memory half was held back by that freeze)"
#6321 query.aggregate / agg.func are undeclared aliases whose only writers are driver fixtures; order: re-spell the fixtures, delete the aliases, then narrow the signature. "The removal ran in a fixed order — the fixtures re-spelt first, the two alias branches deleted second, the parameter narrowed to DriverQuery last — because the reverse order yields red nobody can explain."
#6404 (PR) Executed that order and narrowed aggregate's query parameter to DriverQuery. the same sentence
#7929 Maintainer, 2026-08-12, ruling B: driver-sql's filter refusal stops echoing $field operands, for every caller; the full diagnostic goes to the server log. "the same predicate-text disclosure shape the driver's field-reference filter refusals had already been made to stop echoing (the full diagnostic goes to the server log, never the response)"; "that disclosure shape closed on the last dialect"
#8371 Ruled option 2: a dotted filter key whose head is a relation, a formula or a scalar is refused at both doors; a structured head stays unjudged. "the axis owned by the dotted-filter verdict, which refuses a dotted key whose head is a relation, a formula or a plain column at the protocol and engine doors"
#8592 Measured on live MySQL: knex compiles the named conflict target away. stated by the entry ("knex drops the named keys before the statement leaves the process"); the trailing id is dropped
#8621 Option A: a pre-flight refusal when no unique index backs the caller-named conflict target. "Two earlier pre-flight refusals closed the half where no unique index backed a caller-named target …"
#8622 id becomes insert-only on the merge path: a merge on a non-primary conflict key was measured rewriting the existing row's primary key. "id is 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 Ruling option A: a pre-flight refusal when a second unique key could absorb a backed, caller-named target. "… and the half where a rival unique key could absorb a caller-named one"
#8790 Maintainer, 2026-08-15: refuse both halves with INVALID_FILTER / 400, naming the column. "Ruled by the maintainer on 2026-08-15: refuse BOTH halves …"; "Recover-both was excluded by the ruling's own argument"
#8807 Maintainer, 2026-08-15: an upsert must never modify a row whose identity the caller did not supply and whose conflict key it did not name; enforcement delegated, blanket refusal excluded. "Ruled by the maintainer on 2026-08-15, as a contract principle …" (the principle itself was already quoted verbatim)
#8926 Maintainer, 2026-08-16, option A: MySQL's spelling joins the one shared predicate (envelope and recoveries together). "Addendum 2026-08-16." — the paragraph already states option A
#9061 (PR) Implemented that option A. the same
#11825 Maintainer, 2026-08-25: retire the declarative AdvancedPluginLifecycleConfig container; the classes stay a host-driven library. "… and as a host-driven library when the declarative lifecycle config container was retired"
#11846 404 — see Acceptance notes. "maintainer ruling 2026-08-27 (Option A: remove)"; "(as the removal ruling recorded)"
#14478 Ruling B, 2026-09-02: a no-baseline gate plus an ADR-0087 rename of every offender (DriverOptions.timeout among 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. "Maintainer ruling B on duration units (2026-09-02, its population widened on 2026-09-05 to every authored and every runtime-emitted duration, bar the exemptions a schema declares on the key itself)"; "the duration-unit rule (…)"
#14519 The two tenant timeouts published a describe naming no unit (the unit sat in the JSDoc only); folded into the rename. "the unit-nowhere shape (no unit in the name or in the published describe, first measured on two tenant timeouts)"
#15626 (PR) Landed the gate and the seven founding renames, the tenant idleTimeout → idleTimeoutSeconds among them. "The tenant half was already renamed, in the same change that landed the duration gate itself"
#15678 kernel/: the 14 remaining duration keys carry their unit in the key name. "the kernel-directory duration renames" / "the kernel-directory round"; trailing ids dropped
#15679 system/: the 15 remaining duration keys carry their unit in the key name; size got an honest name. "the system-directory duration round"; trailing ids dropped
#15939 The gate did not read JSDoc. Ruled 2026-09-07: refuse the JSDoc / describe divergence. Ruled A 2026-09-11: remediate the population per file first, land the widened gate last. "Director-seat ruling A of 2026-09-11 on the JSDoc-channel finding … a duration key whose JSDoc names a unit its describe does not is refused, and the keys in that shape are remediated per file before that refusal lands"
#16024 Maintainer, 2026-09-06, per key: forward timeout, remove localPath and wasm. "ruled per key by the maintainer on 2026-09-06, once all three of this package's unread config keys had been measured"
#17635 (PR) The widened gate: refuse a duration key whose JSDoc names a unit its describe does not; landed last. "lands that widened gate last, into a tree already clean"
#18669 Ruling A, 2026-09-17: rename FileValue.duration and estimatedMigrationTime, each with an ADR-0087 entry; no new closed type, no narrowing of stored data. "Maintainer ruling A of 2026-09-17 on the last two duration keys no closed duration type could express …"
cloud#1651 Not readable from this session — see Acceptance notes. "cloud — a census closed 2026-08-26: OS_PREVIEW_MODE there is a routing-only switch …"

No call-shaped token moves: a name( census over registry.ts is 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, widened

COVERED_PREFIXES is now engine-, 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 — surface included — carries no # plus 4 or 5 digits. Anti-vacuity:

  • the REWRITTEN floor rises from 29 to 55 ids: the 26 entries of this stage (7 driver-, 9 kernel-, 10 system-) are added, and every covered prefix must still select at least one entry;
  • presence in stdout is asserted before cleanliness (the driver-sql-unresolvable-where-column-refused reason carries two blank-line paragraph breaks, and its block is found verbatim);
  • the detector is exercised on both sides first (lit on 4 and 5 digits, dark on 3, 6 and 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- block

From committed state, HEAD 1c0dc7ad54, with scripts/ablation-replace.mjs in wrap mode (it owns the restore trap) and scripts/ablation-dist-preflight.mjs gating each leg. The bundle is built from the generated registry.ts, so that is the file mutated.

  • Mutation. In registry.ts, the reason of driver-sql-upsert-cross-row-identity-merge-refused: anchor pre-flight refusals closed the half → pre-flight refusals (#8621) closed the half. The tool read anchor 1 → 0 and replacement 0 → 1, blob b41e1d44 → 8f8d226e.
  • Mutate leg (one lock turn: build, preflight, pin). Spec build exit 0. Preflight: marker present in 4 built files. Pin: red, 1 failed | 2 passed — driver-sql-upsert-cross-row-identity-merge-refused: the printed guidance cites a tracker id: expected '#8621' to be undefined.
  • Restore. Tool-proven: blob b41e1d44 == HEAD, git diff HEAD empty.
  • Restore leg. One lock turn, taken on the second try (the first waited out its 540 s budget, exit 99, NOT MEASURED, the tree already restored). Spec build exit 0. The --absent preflight found the marker in none of 222 built files, with the working tree clean against HEAD. Pin: green, 3 passed.

Verification

Final head 1c0dc7ad54 for every line below; each heavy run went through scripts/pm/os-verify-lock.sh (one turn, VERDICT command-exit 0, per-step exits recorded separately).

  • Build: pnpm exec turbo run build --concurrency=2 --filter='@objectstack/cli^...' gives Tasks: 55 successful, 55 total.
  • Pin and its neighbour: pnpm --filter @objectstack/cli exec vitest run --project integration --maxWorkers=2 test/migrate-meta-engine-guidance.test.ts test/migrate-meta-default-range.test.ts gives Test Files 2 passed, Tests 10 passed | 1 skipped (the skip is the default-range file's own pre-existing skipIf).
  • Spec tests that read these entries or the registry: pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/migrations src/kernel/preview-mode-retirement.test.ts scripts/build-schemas-check-mode.test.ts plus the 19 other spec test files that read MIGRATIONS_BY_MAJOR, the registry or an entry file: Test Files 24 passed, Tests 696 passed.
  • CLI unit: test/vitest-tiers-partition.test.ts and src/utils/spec-release-changes.test.ts: Test Files 2 passed, Tests 28 passed.
  • The call-spelling census that reads registry.ts: pnpm --filter @objectstack/driver-sql exec vitest run --maxWorkers=2 src/sql-driver-query-signature.test.ts gives 15 passed.
  • Typecheck: pnpm --filter @objectstack/spec typecheck exits 0 (test layer: 53 files / 255 errors held in its ledger); pnpm --filter @objectstack/cli typecheck exits 0 (test layer: 3 files / 28 errors held, unchanged).
  • Gate families: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands derives 89 families at 1c0dc7ad54 (after git fetch origin main). --ran over the recorded exit codes reads 89 derived, 89 run, 0 NOT-MEASURED, 0 UNRUN, all exit 0. They include check: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-registration and check:changeset-no-major.
    • check:dual-build-cjs-loads refused first with PREREQUISITE NOT MET (exit 3: twelve packages outside the CLI closure had no dist/). Those dist/ directories were written later in the same pass (04:48–04:49Z, inside the check:type-check-debt run, 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.
  • Lint (a proven narrowing, not the repo-wide run, which is CI's): eslint --no-inline-config --format json over the 28 changed .ts files reports 28 files, 0 errors, 0 warnings.
    • The population is read from eslint.config.mjs: **/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs} minus NEVER_LINTED, and all 28 are in it (no file-ignored warning).
    • Invariance: the config enables no type-aware linting (no parserOptions.project, no typed rules), so a text edit cannot move the verdict on a file it does not touch.
  • Mergeability: see Acceptance notes (driver-free merge-tree against 862b6ce869 exits 0).

Acceptance notes

  • Two dead ids, rewritten from the code on main. #6075 and #11846 answer 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 (commit d367f03) and sql-driver-query-signature.test.ts record what it did — the five drivers' implementations followed IDataDriver's DriverQuery narrowing. The sentence now says exactly that.
    • #11846 (preview mode): packages/spec/CHANGELOG.md (commit 0c2334f), packages/spec/src/kernel/context.zod.ts and preview-mode-retirement.test.ts record the 2026-08-27 ruling (Option A: remove), the three-repo zero-consumer measurement and the re-declare-fresh condition. Dropped because main does not state them: "decision-inbox batch 2" and "all four decision facets pointed the same way". "The RuntimeMode: 'preview' and the whole KernelContext.previewMode block 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.
  • One cross-repo id this session cannot read. cloud#1651 answers 403 here: objectstack-ai/cloud is not attached to this session (add_repo refused: no access). It is neither confirmed nor refuted, so its sentence was rewritten from what main records about it — packages/spec/CHANGELOG.md (0c2334f: closed 2026-08-26 with positive controls, RuntimeMode zero hits, ArtifactKernelFactory 20+ hits and never touching previewMode) and context.zod.ts (OS_PREVIEW_MODE there is routing-only). The cloud-side detail main does 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 own serve.ts (the one reader of OS_PREVIEW_MODE here only widens better-auth's trusted origins to preview-domain wildcards), which is measured on main.
  • Decision-batch numbers went too (invisible to the regex). Nineteen sites cited a decision batch as # 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 said Maintainer 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 #158 sentence (the agreement shape ruled an offence on 2026-09-18) is corroborated by .changeset/18075-agreement-shape-is-an-offence.md on main.
  • "issue NNNN" / "PR NNNN" spellings, checked by hand. No bare-number spelling exists in these 26 entries' author-shown text; the two PR citations (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 a Prime Directive #13 reference.
  • A citation whose page says something narrower than the text. kernel-health-check-and-hot-reload-durations-unit-in-key called shutdownTimeout's shape "the spec: tenant.zod.ts idleTimeout / 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.ts header: "no unit ANYWHERE (the spec: tenant.zod.ts idleTimeout / 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.
  • A comment that my text edit makes slightly stale. 18.system-metrics-window-durations-unit-in-key.ts carries 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.ts size needs 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.
  • Three "card" references re-anchored. Removing an id left "the same card" in the Turso entry pointing at nothing; it now says "the same measurement". The kernel entries' "renamed by this same card" carry no number and were not otherwise rewritten, so they are left.
  • Cross-PR check: no open PR adds or edits a driver-, kernel- or system- semantic entry. Read at 2026-09-28T04:0xZ: the 18 open PRs' file lists (GET /pulls/{n}/files) carry 0 files matching migrations/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.
  • main moved 4 commits past the base (862b6ce869: #20364, #20341, #20366, #20352); none touches packages/spec/src/migrations/ or the pin. A driver-free bare-clone merge-tree --write-tree of this head against 862b6ce869 exits 0 with no conflicted path, so registry.ts needs no merge, and main was not merged in.
  • Generated projections (spec-changes.json, docs/protocol-upgrade-guide.md) are regenerated, as in stages 1 and 2; their --check legs are green. Only the three driver- entries registered at protocol 17 appear in them, which is why those diffs are small.
  • No other test pins these entries' text. A git grep of 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's migrations.test.ts case 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.ts is 352, the two projections are 18 (spec-changes.json 12, the upgrade guide 6), the widened pin is 33 and the changeset is 21.


Generated by Claude Code

…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>
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Sep 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ⚠️ 1 changed file(s) yielded no anchor (packages/spec/spec-changes.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/spec-changes.json) — pages documenting those are invisible to this run
  • 3 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json c577e66635944b8c911e9d38adde34669e68400a → packageMentionDocs.

Which tree this was computed on

This run read content/docs from dcc4c2ef1453a0a6538691e7e39f610253c02e45 — the merge of head 1c0dc7ad546dfd04c6f252e51f16e91f138f6587 into base c577e66635944b8c911e9d38adde34669e68400a, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: 78/78 CONTRACT_REVIEW_TIER
Head-sha: 1c0dc7ad546dfd04c6f252e51f16e91f138f6587

Head unmoved across the review (re-read at the end: same sha, open). Base sha 569d4d2dbf (merge base with origin/main 70ce8022c4).

① Derived judgments

  1. Text only — holds, mechanically. Own TypeScript-AST instrument over base and head git archive trees: for each of the 26 changed entry files, every non-prose property's source text (id, surface, from/to, everything not replacement/reason/acceptanceCriteria), every // and /* */ comment token, every non-entry statement (imports) and the property set/order are byte-identical base→head. 26/26 files differ only in the three prose values; 0 untouched entry files differ. A raw-diff sanity pass finds no +/- line that is not a string-literal fragment or a reason:/replacement: label.
  2. Truth of each rewrite — holds; no lesson lost, softened or overstated. Read in full against single REST reads (36 in-repo ids + comments; #6075, #11846 404 on issues and pulls; cloud#1651 403):
    • 18.driver-sql-unresolvable-where-column-refused (14): #8790 maintainer 2026-08-15 「接受你的所有建议」 → refuse both halves INVALID_FILTER/400 naming the column, ladder keeps projection/ORDER-BY recoveries — text says exactly that. #7929 ruling B 2026-08-12: INVALID_FILTER stops echoing $field operands for every caller, full text to server log — text says exactly that. #8926 option A 2026-08-16 (MySQL joins the one shared predicate) — "Addendum 2026-08-16" plus the paragraph already stating it. #8371 Option 2: refuse relation/formula/plain-scalar head at both doors (protocol assertFilterFieldsExist + engine assertFilterIsMaterializable) — text matches. #3821 premise ("rows matter more than their order", unsortable query returns rows) is on main at sql-driver.ts:7168 and the recovery test header.
    • 18.system-metrics-jsdoc-durations-unit-in-key (12) and 18.kernel-runtime-config-timeout-unit-in-key (11): #15939 ruling A 2026-09-11 (batch Add comprehensive test coverage for ObjectStack spec protocols - 100% coverage achieved #65 direction stands: refuse JSDoc-names-unit-describe-does-not; 21 rows remediated per file first; PR #17635 lands last into a clean tree) — text matches. #14478 ruling B (recorded 5518649320) and population ruling B 2026-09-05 (5548763981: every authored + runtime-emitted duration, exempt only by declaration on the schema) — the 13 boilerplate entries now name both dates and the exemption clause correctly; the "unit in key name or unit-carrying value" gloss is the ruling's own wording. #15678/#15679 are the kernel-/system-directory rounds by title.
    • Six more, prose most changed: kernel-context-preview-mode-retired (dead #11846 → spec CHANGELOG 0c2334f, context.zod.ts, retired-key header: 2026-08-27 Option A remove, zero consumers; cloud#1651 → CHANGELOG records closed 2026-08-26, RuntimeMode 0 hits, ArtifactKernelFactory 20+ hits never touching previewMode; serve.ts:3938-3952 reads OS_PREVIEW_MODE only to push preview-domain wildcards onto better-auth trustedOrigins — measured). driver-aggregate-undeclared-key-aliases-removed (#4984 "tests green, rule dead" verbatim; #4484 no tombstone because nothing parses a driver object, 5158609619; #5540 "two adapters answered differently and both incompletely" verbatim; #6011 maintainer 「roles 别名现在就关闭」; #6321/#6404 fixed order, "reverse yields red nobody can explain" verbatim). driver-sql-distinct-bare-filter-typed (dead #6075 → driver-sql CHANGELOG d367f03: five drivers followed DriverQuery; #5499 freeze 2026-08-05, fully lifted 2026-08-11 5252526378; #6320 sql half dispatched, memory half frozen). driver-sql-upsert-cross-row-identity-merge-refused (#8621 option A, #8755 option A rival key, #8622 id insert-only after measured PK rewrite, #8807 maintainer 2026-08-15 principle). kernel-health-check-and-hot-reload (#3733 parse success/key dropped silently; #4914 2026-08-04 keep HotReloadConfigSchema with HotReloadManager; #11825 2026-08-25 container retired, classes host-driven; #14519 two tenant timeouts, unit in JSDoc only; #15626 landed gate + tenant rename). kernel-compatibility-matrix (#18669 ruling A 2026-09-17: last two keys no closed type could express, hours + fractional seconds, no new type, no narrowing; batch #158 → .changeset/18075-agreement-shape-is-an-offence.md on main). driver-turso (#16024 per-key ruling 2026-09-06 after three unread keys measured).
    • cloud#1651 is gone from all author-shown fields of the 26; the four remaining cloud# sites are in other families (boundary, below).
  3. Census — reproduced exactly with my own instrument (#\d{4,5}\b over evaluated replacement/reason/acceptanceCriteria, surface separate; 718 files, 300 entry object literals, 0 unevaluable; retired-key/def files export a string, so none are author-shown). Base: whole tree 843, driver- 44 (0/44/0, 25 distinct), kernel- 44 (1/41/2, 11), system- 44 (0/42/2, 6), surface 7. Head: whole tree 711, the three families 0/0/0, engine-/ui-/plugin- still 0, surface 7. Lit control 17.aggregation-node-distinct-retired: 7 (1/6/0) before and after. Dark control: 794 // lines with a tracker id before and after. Short #N{1,3} sites in the three families 0/9/10 → 0/0/0 (the 19 decision-batch sites).
  4. Widened pin — holds, nothing weakened. FAMILY is derived from the built MIGRATIONS_BY_MAJOR by the six prefixes; each block (surface + replacement + why + verify, laid out as meta.ts:523-525) is asserted present in stdout, then block.match(TRACKER_ID)?.[0] must be undefined — so any id returning to any covered entry's four fields fails the third it. Anti-vacuity: REWRITTEN 29 → 55 (all 26 ids of this stage present), every prefix must select ≥1 entry, detector lit/dark self-test unchanged. The three assertions are textually identical to base; only COVERED_PREFIXES and REWRITTEN widened.
  5. Generated artifacts — exact. Parsed registry.ts at base and head: 300 semantic entries both sides, exactly the 26 ids differ, all prose-only, 0 non-prose differences; all 300 head registry entries match the head entry files byte-for-byte on surface/reason/replacement/acceptanceCriteria. spec-changes.json (12 lines) and the upgrade guide (6) change only the three protocol-17 driver- rationales, mirrored from the entries. CI at head: 35 check-runs, 0 failures; Lint & Repo Gates (runs check:migration-registry, check:spec-changes, check:upgrade-guide, check:generated --reconcile-only, check:doc-authoring) success.
  6. Changeset — every sentence true. '@objectstack/spec': patch; Clause-②: no stands alone on its own line; "some of which no longer resolve" (two 404s), "ADR ids are kept", "text only", "generated … carry the same text" all verified above.

② Semver level

patch is correct: no accept set moves, no export or key changes, only printed guidance text and a test. Clause-②: no with no arm is the well-formed declaration per scripts/pm/clause2-line.mjs; AGENTS.md step 3 (a fix in a released package takes patch) fits. check:changeset-no-major and check:adr-0087-registration green in CI.

③ Boundary flags

Implemented-by: claude/issue-20233-migrate-meta-tracker-free-stage-3
Reviewed-by: session_01QcAS3qiYYZNezaxZxaUdMV

VERDICT: PASS

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants