fix(spec): one D3 entry per major-18 retirement family — the census and the 25 missing entries (#20201) - #20255
Conversation
…slessness (#20201) Ruling B (ADR-0087 D3): every retirement family carries one D3 entry, even when a lossless D2 conversion repairs its data. Corrects the step-17 docblock's stale 'semantic list is deliberately empty' fact, step 18's tenancy.organizationField rationale sentence, and the single-entry sites in conversions/registry.ts, the cacheTtl retired key, the metadata-endpoints entry comment and the migration-registry generator docblock. Claude-Session: https://claude.ai/code/session_01CiCTczDo7tGhafXjf61dUJ Co-authored-by: Claude <noreply@anthropic.com>
…issing one WIP toward #20201's census: one D3 entry per D2-backed retirement family that had none (ruling B, ADR-0087 D3). Claude-Session: https://claude.ai/code/session_01CiCTczDo7tGhafXjf61dUJ Co-authored-by: Claude <noreply@anthropic.com>
Completes one D3 entry per D2-backed major-18 retirement family (25 new entries), and pins in the registry's own test that from protocol 18 on every graduated conversion is named by a D3 entry of its step. Claude-Session: https://claude.ai/code/session_01CiCTczDo7tGhafXjf61dUJ Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CiCTczDo7tGhafXjf61dUJ Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CiCTczDo7tGhafXjf61dUJ Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CiCTczDo7tGhafXjf61dUJ 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 — 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 bdaa8e7d90060567fc2e2bae285462fcaf5acde0 && git checkout bdaa8e7d90060567fc2e2bae285462fcaf5acde0
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 3f86dc52f22668dd92de00f604148e04d3d048da a930cacea56720b2ef6a2b88729be3aca662e85a && git checkout -B drift-repro 3f86dc52f22668dd92de00f604148e04d3d048da && git merge --no-ff a930cacea56720b2ef6a2b88729be3aca662e85a
node scripts/docs-audit/affected-docs.mjs --json 3f86dc52f22668dd92de00f604148e04d3d048da |
Contract reviewServed-tier: Read: card #20201 and comments 5854110917 / 5854456343 / 5854164872 / 5855991609 / 5857585649, ruling B 5615360777 / 5634031140 / 5559778263, PR #20255 object, body, 32 files, 6 commits, full diff vs merge base ① Derived judgments(a) Census: re-derived at merge base (b) Entries: all 25 read. Each names its family and D2 id, states what D2 repairs, states a judgment the consumer still owes, carries the five (c) Prose: the step-17 docblock is true (the (d) Pin: it lives in the existing (e) Generated regions: every non-header line of the 25 entries and of the edited ② Semver level
③ Boundary flagsBlocking: the required context Non-blocking: Table 1 row 16 misplaces CI at this head: 34 check-runs after convergence: 31 success, 3 skipped, 1 failure ( Implemented-by: VERDICT: FAIL |
…mmits; the census pin says what to add Four new D3 entries carried issue numbers that no longer resolve on the board (deleted, not transferred). Each now names the commit in this repository's history that retired its family, and says what that commit decided. The pin's assertion message names the unnamed conversions and the remedy; its logic and scope are unchanged. Claude-Session: https://claude.ai/code/session_01CiCTczDo7tGhafXjf61dUJ Co-authored-by: Claude <noreply@anthropic.com>
…-entry-per-family
…-entry-per-family
Contract reviewServed-tier: Delta from ① Derived judgments(a) The four re-anchored citations. Every named sha is an ancestor of (b) The pin's assertion message. (c) Merges and regions. (d) Nothing else moved. A normalised interdiff (index lines dropped, hunk offsets normalised) of ② Semver level
③ Boundary flagsBlocking: none. Non-blocking: (1) The body's sentence " Merge risk now. Landed since the last review: #20279 ( The PR body. Table 1 row 16 lists only the four permission records and CI at this head: 42 check-runs, all completed: 37 success, 5 skipped, 0 failure. All seven pinned required contexts ( Implemented-by: VERDICT: PASS |
…amily After #20255 landed, its view-item D3 entry still said a flattened overlay keeps its own owner/hidden. The overlay pair is a separate family with its own D2 view-overlay-owner-hidden-removed and D3 view-overlay-owner-hidden-retired. The overlay pin now reads another entry naming the conversion as a cross-reference that must point at this family's record. Claude-Session: https://claude.ai/code/session_01CiCTczDo7tGhafXjf61dUJ Co-authored-by: Claude <noreply@anthropic.com>
…s (ADR-0049) (objectstack-ai#20286) Fixes objectstack-ai#20230 Clause-②: no (narrowing) ## What this does Retires the flattened view overlay's `owner` and `hidden` keys under ADR-0049 enforce-or-remove. Triage direction on the card (comment 5856621469), verbatim: 「follow objectstack-ai#20085's disposition for the same key pair」. PR objectstack-ai#20227 retired the same pair on the view item record; this PR retires it on the other door, with the same prescription texts. The overlay door is the lean `PUT /api/v1/meta/view/:name` body with no `config`: members 3 and 4 of the `view` union (`VIEW_METADATA_MEMBERS.listOverlay` / `.formOverlay`), built from `flattenedViewOverlayFields()` in `packages/spec/src/ui/view.zod.ts`. It declared both keys, the write door accepted them, and `saveMetaItem` stored them verbatim. Nothing read either one. After this PR, every door that parses an overlay refuses both keys with the prescription. A stored overlay row that holds either one is stripped on read: - a row with other view keys is valid again and re-saves; - a hide-only row (`{ object, viewKind, hidden: true }`) is left identity-only, which the door refuses. It is badged invalid, refused on a whole-row re-save, and reported `failed` by `os migrate meta --stored --apply` until it is deleted or given the setting its author meant. The D2 docblock, the D3 entry and the changeset all state this, and it is pinned. ## Stop valve: the writer census, taken first Taken before any edit, each reading with a lit control on the same ref. No real writer was found, so the retirement proceeds. | where | ref | writers of overlay `owner` / `hidden` | lit control | |---|---|---|---| | objectui at the `.objectui-sha` pin | `f8a9d0fb` | 0. All 12 view write call sites were read one by one (the `persistViewPatch` toolbar path, `updateView` / `updateViewConfig` / `createView` in the data adapter, `viewEnvelope` saves, and the two `PublicFormsPage` saves). None writes either key. The toolbar's overlay keys are `VIEW_OVERLAY_OWNED_KEYS` = `rowHeight`, `sort`, `hiddenFields`, `columnState`, `inlineEdit`. The switcher writes `label` and `isPinned`. | 8 `persistViewPatch` call sites writing the owned keys; 2 `updateView` row-key writes | | objectui `main` | `6cf5999` | 0 (same 12 call sites, same reading) | 7 `persistViewPatch` call sites; 2 row-key writes | | objectstack `packages/**`, `examples/**` | `e46218674` | 0. Examples author no `viewKind` at all, and no view-level `hidden` / `owner` in 10 view files. No framework source writes an overlay body with either key. | `label:` 88 times in the same 10 example view files | | HotCRM | `2f7b2326` (= its remote `main`) | 0. No view write call, and no view-level `hidden` / `owner` in 14 view files. | `label:` 159 times in those files | | cloud | not reachable | NOT MEASURED. REST read answered 403 and `add_repo` was refused for this session. objectstack-ai#20227's census at cloud `48d70663` recorded no code writer, and one test double that pins a lean `{hidden:true}` PUT as accepted. That is a test fixture, not a writer. It goes red on cloud's next spec bump only if it parses through the spec schema. | — | Readers, re-checked: `.hidden` / `.owner` reads on a view in `rest-server.ts` = 0/0 and in `metadata-manager.ts` = 0/0. The 5 `.hidden` reads in `metadata-protocol/src/protocol.ts` are all field-level. Control: `.order` is read 2 / 1 / 2 times in the same three files. ## Dispatch assumptions, measured 1. The two keys were at `view.zod.ts:5284-5285` on `e46218674`, and `flattenedViewOverlayFields(kind)` takes a `kind` argument. **Held.** Only those two keys move. 2. The `retiredKey()` tombstone applies. **Held.** Both overlay members `.strip()`, and a `z.never()` member refuses loudly instead of stripping: the pins below assert issue code `invalid_type` at path `[key]`, carrying the prescription. 3. A D2 conversion is owed. **Held, and the view-item entry does not cover it.** `view-item-owner-hidden-removed` skips any body without a `config` dict, and its own fixture pinned an overlay's `hidden: true` as kept. This PR adds a separate entry, disjoint by `config`. 4. Other `view.zod.ts` regions were not touched: no edit in `FormViewSchema.layout`, `ViewMetadataParsed` or `diagnoseViewMetadata`. ## The route - **Tombstones.** `owner: retiredKey(VIEW_ITEM_OWNER_RETIRED)` and `hidden: retiredKey(VIEW_ITEM_HIDDEN_RETIRED)` in `flattenedViewOverlayFields()`. These are the view item's own constants, so both doors answer with the same text, as the order asked. A pin asserts the overlay's issue message is byte-equal to the view item's. - **D2 `view-overlay-owner-hidden-removed`** (`toMajor: 18`, `retiredFromLoadPath: true`, lossless `stripKeys`). Scope: the flattened spelling, meaning a body with no `config` and no container slot. It walks `views` (stack sources, and every stored row, which `convertStoredItem` replays before serving or badging) and `viewItems` (the assembled channel). It does NOT require `viewKind`: a flat row stored before the objectstack-ai#7741 binding has none until the write path heals it in, and then the save would refuse the key it still held. It is wired into `MIGRATIONS_BY_MAJOR[18]`, and the step rationale is extended. - **Why the D2 matters at runtime, and what it cannot do.** objectui's `updateView` is a read-merge-write, and `buildPersistedViewBody` re-sends a saved view whole. A stored row served WITH `hidden` would make the next toolbar toggle a 422, so the read path strips first. - For a content-bearing row, that is the whole story. - For a hide-only row, the strip leaves identity only, and the door refuses that (the identity precondition: only identity fields). The badge turns invalid, a whole-row re-save or a rename (`label` is identity) answers 422, and `--apply` reports `failed` and leaves the row as stored. A toggle that adds a real key saves. - Remedy: delete the row, or add the setting its author meant. - **D3 `view-overlay-owner-hidden-retired`** (ruling B on objectstack-ai#17152). It names its conversion by id in `reason`, which is the shape objectstack-ai#20255's census pin reads. That pin is now live on `main` and green here. Its `acceptanceCriteria` state both classes, the hide-only row included. The view item's pair is a separate family with its own D2 and its own D3 (`18.view-item-owner-hidden-retired.ts`, from objectstack-ai#20255). This PR corrects that entry's one stale sentence (amendment `5859181450`). - **`RETIRED_KEYS_BY_MAJOR[18]`**: `ui/ViewMetadata:owner` and `ui/ViewMetadata:hidden`. The overlay members are not exported. `ui/ViewMetadata` is the exported door they are reached through, and it is listed in `unemitted-schemas.baseline.json`, so these rows are declared, not judged. The retirement test pins them. - **No liveness row.** The `view` ledger walks the container keys only (`name`, `label`, `object`, `list`, `form`, `listViews`, `formViews`), so a row would be an ORPHAN. `check:liveness` is green without one. - **Generated artefacts.** `check:generated`: all 15 were current, and there was nothing to regenerate. The four surface ratchets are byte-identical, which is expected on this route: the def is unemitted. `spec-changes.json` and the upgrade guide project up to protocol 17, so no major-18 entry shows there either (the same reading as PR objectstack-ai#20227). - **Forms / examples / skills / docs.** No form offers either key. There are zero authorings in `examples/`, `skills/` and `content/docs/`. The tree-scoped pin below holds that. - **Changeset.** `@objectstack/spec: minor`, `**BREAKING**`, FROM → TO, the one-line fix, `Clause-②: no (narrowing)`, ADR-0087 disposition `registered view-overlay-owner-hidden-removed, view-overlay-owner-hidden-retired`. ## Pins The new file is `packages/spec/src/ui/view-overlay-owner-hidden-retirement.test.ts` (in-package, local project): - Both overlay members refuse each key at its path: `invalid_type`, the path, and the prescription. The `view` door (`getMetadataTypeSchema('view')`) refuses with `invalid_union`, the prescription surfaces as the union's message, and the claimed member locates the key. The assembled channel refuses too. - CONTROL: the same overlays without the keys pass every door, with `isDefault` / `order` / `scope` intact and no key grown. The view item door refuses the pair as well, so the family is closed on both doors. `defineView` is the container door, not an overlay door. - D2: a stored row rehydrates clean and then parses at the door, while the unconverted row is refused. A `viewKind`-less flat row is stripped. The `viewItems` channel is reached, with each door's key stripped by its own entry. Containers are left alone. Idempotence: the second replay has 0 notices and returns the same reference. Load path: a live author is refused, not rewritten. - Registration: the two keys, the chain id, and one D3 record for the conversion. Any other entry naming the conversion must also name `view-overlay-owner-hidden-retired`, so it is a pointer, never a second record. - **Hide-only residue** (patch round 1): - A stored `hidden`, `owner` or both row strips to identity only. The door refuses it with the identity precondition's own text, as one custom issue at the root rather than the prescription. - A rename (`label`) is refused. - Controls `isDefault` / `order` / `columnState` save. The ADR-0112 envelope is pinned at the door that produces it. `packages/metadata-protocol/src/protocol.save-union-issues.test.ts` adds a describe block over the existing stub-engine harness (no new double). For each key and each family, `saveMetaItem` rejects with `code` `INVALID_METADATA` and `status` `422`, persists 0 rows, and carries an issue located at the key with the prescription. CONTROL: the same bound overlays without the keys save, 1 row each. Patch round 1 adds two pins here: - **Save door:** a whole-row PUT of the stripped hide-only row answers `INVALID_METADATA` / 422, with 0 rows and "only identity fields". The same row plus `isDefault` saves. - **Read path:** `getMetaItem` over a seeded row serves a stored overlay without `owner` / `hidden`. `_diagnostics` is valid when the row carries content and invalid when it is hide-only. The existing harness gains an optional seed; its default is unchanged. ## Flipped pins: repo-wide sweep, each one load-bearing The sweep grepped every test file that spells `viewKind` beside `hidden` / `owner`, in all packages. | pin | before | after | |---|---|---| | `spec/ui/view-item-owner-hidden-retirement.test.ts` BOUNDARY | an overlay with the keys parses | refused, with the SAME prescription | | same file, conversion test | overlays left alone | the overlay key is stripped by `view-overlay-owner-hidden-removed`, the record key by the view-item entry (asserted as pairs) | | same file, tree-scoped matcher | record spelling only | both spellings (`viewKind` + a retired key); anti-vacuity cases for an overlay (TS, YAML) and a container | | `spec/conversions/registry.ts` view-item fixture | overlay neighbour kept `hidden: true` | the neighbour carries neither key, which keeps the fixtures disjoint once the overlay entry replays | | `spec/ui/view-metadata-schema.test.ts` | `a hide PUT` accepted; `identity + hidden` accepted | the hide PUT is refused at the member with the prescription (not by the precondition); identity + a live key is accepted, identity + `hidden` refused | | `spec/ui/view-union-diagnostics.test.ts` | `overlay.list.aux` (with `hidden`) and `put.hidden` ACCEPTED | moved to REFUSED, plus `put.owner`; a new test asserts those rows are refused BY the tombstone (the prescription, `invalid_type` at the key) | | `spec/conversions/view-spelling-walk.test.ts` | the overlay's `owner` survives conversion | `owner` stripped, with the notice under the overlay entry; every binding key still survives | | `metadata-protocol/src/metadata-diagnostics.union-issues.test.ts` | `{hidden, object, viewKind}` badged `valid: true` | badged invalid, with the prescription at `hidden`; a live key is badged valid | ## Verification **Patch round 1, final head `a05b32f8b`**, merged with `origin/main` at `4e0f72e8d`, which carries objectstack-ai#20238, objectstack-ai#20255 and objectstack-ai#20244 (dev report `5860055944`): - spec `--project local`, full: 553 files / 16341 tests. - The touched pins plus `migrations.test.ts`, with the census pin shown verbosely: 6 / 530. - The repo view-item pin: 18/18. - `turbo build rest^...`: 24/24. - metadata-protocol save-door + diagnostics: 2 / 40. - Typecheck spec + metadata-protocol: exit 0. - `check:generated`: 15/15 current. - Gates: 88 derived, 86 run and exit 0, 2 NOT-MEASURED (`check:dual-build-cjs-loads`, `check:type-check-debt`: exit 3, PREREQUISITE NOT MET), 0 UNRUN. - Ablation (round 1, at `e38a8027b`): the overlay strip replaced by `return view` → 6 red (the residue, stored-row, `viewKind`-less and `viewItems` pins) / 18 green. The restore was proven by blob == HEAD and an empty `git diff HEAD`. The round-0 readings below are at `2a40c104c`. Round 0: final head **`2a40c104c`**. That is after merging `origin/main` at `17bd3187`, which carried objectstack-ai#19920's `view.zod.ts` / `assembled-views.zod.ts` type change. Heavy runs went through `scripts/pm/os-verify-lock.sh`, and every exit code was written to disk before its log was read. The box was shared, with lock waits of 3–9 min, so wall-clock readings are contended. | run | head | reading | |---|---|---| | `turbo run build --filter='@objectstack/rest^...'` (spec + the consumer closure) | `2a40c104c` | exit 0, 24/24 tasks | | `pnpm --filter @objectstack/spec check:generated` | `2a40c104c` | exit 0, all 15 artifacts current; nothing regenerated | | spec `--project local`, full | `2a40c104c` | 553 files / 16275 tests passed | | spec `--project repo`, `view-item-owner-hidden-retirement.test.ts` (tree-scoped pin) | `2a40c104c` | 18/18 passed | | metadata-protocol, the edited files + `view-write-path-identity.test.ts` | `2a40c104c` | 3 files / 41 tests passed | | typecheck: spec (`tsc` + scripts + `check:test-typecheck`), lint, metadata-protocol | `2a40c104c` | exit 0 ×3 | | consumers, full: metadata-protocol / lint / metadata; objectql and rest (their 24 / 13 view files) | `cbc81c574` | 189 files / 2720 tests (3 skipped) · 110 / 4262 · 54 / 821 · 24 / 380 · 13 / 191, all exit 0 | **Reverse verification** (a one-shot probe removed by an EXIT trap, verified absent afterwards): `packages/lint/src/zz-issue20230-dts-probe.ts` typed `{ object, viewKind: 'list', hidden: true }` as `ViewMetadata`, against the REBUILT spec `.d.ts`. `@objectstack/lint` `tsc --noEmit` exited 2: `src/zz-issue20230-dts-probe.ts(2,14): error TS2322: Type '{ object: string; viewKind: "list"; hidden: boolean; }' is not assignable to type 'ViewMetadata'.` With the probe removed, `git status` showed 0 lines and `lint typecheck` exited 0. Predicted direction: red. Observed: red. **Ablation** (`scripts/ablation-replace.mjs`, on committed state, wrap mode). The mutation swapped the overlay's `hidden: retiredKey(VIEW_ITEM_HIDDEN_RETIRED),` for `hidden: z.boolean().optional(),`: anchor 1 → 0, blob `1f93b520` → `e9ad3dec`. Three spec files then read 10 failed / 139 passed, and the 10 are exactly the overlay `hidden` pins: both members, the same-text pin, the door, the assembled channel, the hide-PUT refusal, the identity pin, and the three union-diagnostics rows. The `owner` pins stayed green, as they should. The restore brought the blob back to HEAD `1f93b520`, with `git diff HEAD` at 0 bytes and `git status --porcelain` at 0 lines. Predicted direction: red. Observed: red. (The metadata-protocol save-door pins resolve spec through `dist/`, so they were not part of this ablation.) **Gates.** `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at `2a40c104c` derived 88 commands. All 88 ran with the exit code captured before any pipe, and were reconciled with `--ran`: **88 derived, 86 run, 2 NOT-MEASURED, 0 UNRUN**. All 86 measured commands exited 0. That includes `check-adr-0087-registration` (`registered view-overlay-owner-hidden-removed, view-overlay-owner-hidden-retired (new here …)`, `[BREAKING+bang+clause-②-narrowing]`), `check-changeset-no-major`, `check-empty-changeset`, `check:nul-bytes`, `check:cross-package-test-inputs`, `check:doc-authoring`, and the spec `check:*` family (`check:authorable-surface`, `check:liveness`, `check:migration-registry`, `check:spec-changes`, `check:upgrade-guide`, `check:api-surface`, `check:docs`). NOT MEASURED (exit 3, `PREREQUISITE NOT MET`; each reads built output of the whole workspace, which was not built locally): `pnpm check:dual-build-cjs-loads`, `pnpm check:type-check-debt`. This diff touches no package entry point, export or tsconfig. CI's build lanes measure both. Also owned by CI: `pnpm lint`, the remaining objectql / rest suites, and the lanes `dispatch-gates` lists outside the derived total. The CLI `integration` tier does not apply (no `packages/cli` change). ## Acceptance notes (observed, not fixed here) _The seat updated this body at 2026-09-27T21:40Z after patch round 1, per dev report `5860055944`. Reviews: `5859174998` (FAIL at `2a40c104c`)._ 1. **One family or two for D3, and the overlap with PR objectstack-ai#20255.** PR objectstack-ai#20255 (objectstack-ai#20201, not merged when this opened) adds `18.view-item-owner-hidden-retired.ts` as the view item family's D3 entry, and a census pin requiring every major-18 conversion to be named by a D3 entry of its step. This PR's conversion is separate, disjoint by `config`, so it carries its own D3 entry naming it. That keeps one record per conversion and no second file under objectstack-ai#20255's filename, which would be an add/add collision. objectstack-ai#20255 has since landed (`f415bcf18`), and the census pin is green here at `a05b32f8b`. Its sentence 「A flattened view overlay keeps its own `owner` and `hidden` …」 is replaced in this PR (amendment `5859181450`): the overlay pair is a separate family, with its own D2 `view-overlay-owner-hidden-removed` and D3 `view-overlay-owner-hidden-retired`. 2. **This PR supersedes one sentence of objectstack-ai#20227's pending changeset.** `.changeset/view-item-owner-hidden-retired.md` says an overlay "still parses". It is left as landed, because `check-empty-changeset` refuses an edit to another PR's release note. This PR's changeset states the supersession instead. The release compiler should read the two together. 3. **Cloud is NOT MEASURED** (above). If its mock-protocol double parses `{hidden:true}` through the spec, it goes red at cloud's spec bump. That is a fixture edit there. Carrier: cloud, at its next `@objectstack/spec` bump. 4. **The assembled channel's refusal loses the branch diagnostics** (objectstack-ai#20227's acceptance note 4, pre-existing): `AssembledViewArtifactSchema` is a plain `z.union`. Carrier: none. --- _Generated by [Claude Code](https://claude.ai/code/session_01CiCTczDo7tGhafXjf61dUJ)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #20201
Clause-②: no
What
Ruling B on #17152 (director
5615360777, restated5634031140, on the maintainer's #15954 authority5559778263): every retirement family carries ONE ADR-0087 D3 (semantic) entry, even when a lossless D2 conversion repairs its data; D2 carries the mechanical repair only. This PR takes the major-18 family census the card asks for, adds the 25 D3 entries it found missing, corrects the prose that justified their absence, and pins the census in the registry's own test.packages/spec/src/migrations/entries/semantic/18.*.ts, one per D2-backed family that had none. Each names its family and its D2 conversion id, says what D2 already repairs, and says what judgment the consumer still owes (thereason), with anacceptanceCriteriathe consumer can check. None is a placeholder: every one states a residue specific to its family (a unit only the author knows, a belief the platform never honoured, a shape the conversion deliberately leaves alone, code the chain cannot reach).5854110917and5854456343, plus step 18's rationale and step 17's docblock fact (list below).packages/spec/src/migrations/migrations.test.ts(registry integrity). No new check script.MIGRATIONS_BY_MAJOR[18].semantic186 → 211 entries at the census base; after mergingmain(four sibling D3 entries landed meanwhile, none with a D2 conversion) the generated region holds 215.The census (the card's main deliverable)
Tree.
objectstack-ai/objectstackat3cb84d084(this branch's fork point; it already contains #20227's view-item retirement). Major-18 population there: 185retired-keys, 146retired-defs, 186semanticentry files, and 36 D2 conversions graduated into step 18. (The card measured 181 / 130 / 175 atd7c024133e.)Grouping rule (ruling B's unit, held constant). Where a D2 conversion exists, the conversion is the family: every retired key or def it repairs belongs to it, and a D3 entry may cover more than one conversion only where one judgment covers them (the pre-existing
element-filter-and-form-node-refusedcoverselement-filter-removedandelement-form-removed). Every new entry here covers exactly one conversion. Where no conversion exists, the records are grouped by the D3 entry that names them.Method.
semantic/entry files that name its id as a whole id (comment or field); for each retired key, the conversion its own comment names, else the major-18 entries naming it ascat/Def:key,Def.path/Def:path, or the def name plus the leaf key; for each retired def, the entries naming the def.metric-filters-removedwas classed missing althoughanalytics-authorable-unknown-keys-refusednames it); (b) 14 records matched by more than one entry, each placed by its own comment (for examplekernel/PluginStartupResult:plugin, which goes tostartup-orchestrator-retired); (c) 9 records whose comment names no conversion but which belong to a D2 family (integration/DeclarativeConnectorEntry:connectionTimeoutMsand:errorMapping, the three error-mapping defs, the four responsive-shape defs), plusintegration/Connector:connectionTimeoutMs, whose comment names two conversion ids and belongs toconnector-connection-timeout-ms-removed(it names the permission conversion only as a comparison; round 1's Table 1 placed it wrongly, corrected in patch round 1); (d) 5 theme sub-block defs, named by the theme family's entry as its sub-blocks.Control. The pairing sees a family that has its entry: 11 of the 36 conversions pair with a pre-existing entry, among them
cube-join-sql-and-relationship-removedwithcube-join-sql-and-relationship-retired(which the pin's own control test also asserts), and the whole-id matcher refuses a prefix (record-chatter-position-vocabularyis a prefix of its entry's own id and matches only the entry's real citation). Evaluated at the base with the pin's logic: 24 conversions named by no major-18 entry; the reading pass addsmetric-filters-removedfor 25. Evaluated at this head: 0.Result. 331 records (185 keys + 146 defs) plus 36 conversions:
The card's grep for 「lossless」 found the
tenancy.organizationFieldsite and step 17. The census finds 25 major-18 families, most of them with no 「lossless」 wording at all.Table 1 — D2-backed families (step 18
conversionIds, in order)field-malformed-scale-precision-removedfield-scale-precision-integer-refusedrecord-chatter-position-vocabularyrecord-chatter-position-vocabulary-convergedelement-input-target-variable-removedui/ElementRecordPickerProps:targetVariable,ui/ElementTextInputProps:targetVariableelement-input-target-variable-retired(new)element-filter-removedui/ElementFilterProps:aria,ui/ElementFilterProps:fields,ui/ElementFilterProps:layout,ui/ElementFilterProps:object,ui/ElementFilterProps:showSearch,ui/ElementFilterProps:targetVariableelement-filter-and-form-node-refusedelement-form-removedui/ElementFormProps:aria,ui/ElementFormProps:fields,ui/ElementFormProps:mode,ui/ElementFormProps:object,ui/ElementFormProps:onSubmit,ui/ElementFormProps:submitLabelelement-filter-and-form-node-refusedfield-column-lists-canonicalizedfield-inline-and-related-list-columns-closed(new)metric-filters-removeddata/Metric:filtersanalytics-authorable-unknown-keys-refused)cube-metric-filters-retired(new)cube-sub-day-granularities-removedtime-update-interval-sub-day-retiredcube-join-sql-and-relationship-removeddata/CubeJoin:relationship,data/CubeJoin:sqlcube-join-sql-and-relationship-retiredrecord-highlights-field-icon-removedui/RecordHighlightsField:iconrecord-highlights-field-icon-retired(new)mapping-lookup-params-removedmapping-lookup-params-retired(new)translation-component-submit-label-removedtranslation-component-submit-label-retired(new)page-component-responsive-removedui/PageComponent:responsive,ui/BreakpointColumnMap,ui/BreakpointName,ui/BreakpointOrderMap,ui/ResponsiveConfigpage-component-responsive-retired(new)object-grid-default-sort-removedui/ObjectGridProps:defaultSortobject-grid-default-sort-retired(new)object-kanban-quick-add-removedui/ObjectKanbanProps:quickAddobject-kanban-quick-add-retired(new)permission-allow-restore-purge-removedsecurity/EffectiveObjectPermission:allowPurge,security/EffectiveObjectPermission:allowRestore,security/ObjectPermission:allowPurge,security/ObjectPermission:allowRestorepermission-restore-purge-bits-retired(new)form-view-option-default-removedform-view-option-default-retired(new)field-reference-to-aliasfield-reference-to-spelling-retired(new)connector-error-mapping-removedintegration/Connector:errorMapping,integration/DeclarativeConnectorEntry:errorMapping,integration/ConnectorErrorCategory,integration/ErrorMappingConfig,integration/ErrorMappingRuleconnector-error-mapping-retired(new)connector-connection-timeout-ms-removedintegration/Connector:connectionTimeoutMs,integration/DeclarativeConnectorEntry:connectionTimeoutMsconnector-provider-context-connection-timeout-ms-retiredhook-timeout-to-timeout-mshook-timeout-unit-in-key(new)job-timeout-to-timeout-mssystem/Job:timeoutjob-timeout-unit-in-key(new)api-endpoint-cache-ttl-to-cache-ttl-secondsapi/ApiEndpoint:cacheTtlapi-endpoint-cache-ttl-unit-in-key(new)dashboard-refresh-interval-to-refresh-interval-secondsui/Dashboard:refreshIntervaldashboard-refresh-interval-unit-in-key(new)connector-health-and-trigger-durations-unit-in-keyintegration/CircuitBreakerConfig:monitoringWindow,integration/ConnectorTrigger:intervalconnector-resilience-durations-unit-in-key(new)memory-persistence-auto-save-interval-to-msdata/AutoPersistenceConfig:autoSaveInterval,data/FilePersistenceConfig:autoSaveIntervalmemory-persistence-auto-save-interval-unit-in-key(new)turso-config-timeout-to-timeout-msdata/TursoConfig:timeoutturso-config-timeout-unit-in-key(new)view-page-mount-removedui/ListView:pageName,ui/ObjectListView:pageNamelist-view-page-mount-retired(new)list-view-sort-string-clause-to-arraylist-view-sort-string-clause-retired(new)page-assigned-profiles-removedui/Page:assignedProfilespage-assigned-profiles-audience-to-permission-setchart-config-aria-removedui/ChartConfig:aria,ui/ReportChart:ariachart-config-aria-retired(new)dashboard-widget-chart-config-structure-removedui/DashboardWidgetChartConfig:series,ui/DashboardWidgetChartConfig:type,ui/DashboardWidgetChartConfig:xAxis,ui/DashboardWidgetChartConfig:yAxisdashboard-widget-chart-config-structure-refusedtranslation-per-app-settings-removedtranslation-per-app-settings-platform-onlyobject-tenancy-organization-field-removeddata/TenancyConfig:organizationFieldobject-tenancy-organization-field-retired(new)page-component-filter-record-to-rule-arrayelement-data-source-and-object-block-filter-rule-array,object-grid-default-filters-rule-arrayview-item-owner-hidden-removedui/ViewItemWire:hidden,ui/ViewItemWire:owner,ui/ViewItem:hidden,ui/ViewItem:ownerview-item-owner-hidden-retired(new)Table 2 — D2-less records, grouped by the existing D3 entry that names them
advanced-plugin-lifecycle-config-retiredkernel/AdvancedPluginLifecycleConfig,kernel/GracefulDegradation,kernel/PluginUpdateStrategyai-conversation-analytics-duration-unit-in-keyai/ConversationAnalytics:durationapi-error-retry-after-unit-in-keyapi/EnhancedApiError:retryAfterapi-runtime-config-durations-unit-in-keyapi/DataLoaderConfig:cacheTtl,api/RouteDefinition:timeoutautomation-flow-list-route-retiredapi/FlowSummary,api/ListFlowsRequest,api/ListFlowsResponseautomation-runs-cursor-retiredapi/ListRunsRequest:cursorbranded-identifier-schemas-retiredshared/AppName,shared/FieldName,shared/FlowName,shared/ObjectName,shared/RoleName,shared/ViewNamechange-management-duration-keys-retiredsystem/ChangeImpact:downtime.durationMinutes,system/ChangeRequest:implementation.steps.estimatedMinutes,system/RollbackPlan:steps.estimatedMinuteschange-management-family-retiredsystem/ChangeImpact,system/ChangePriority,system/ChangeRequest,system/ChangeStatus,system/ChangeType,system/RollbackPlancli-command-contribution-retiredkernel/CLICommandContributioncloud-subpath-retiredcloud/defsdata-file-value-duration-unit-in-keydata/FileValue:durationdata-nosql-query-options-timeout-unit-in-keydata/NoSQLQueryOptions:timeoutdevice-request-response-interval-unit-in-keyapi/DeviceRequestResponse:intervaldriver-options-timeout-to-timeout-msdata/DriverOptions:timeoutepoch-instant-keys-renamedapi/SimplePresenceState:lastSeen,api/WebSocketEvent:timestamp,kernel/HealthStatus:timestamp,kernel/KernelContext:startTime,kernel/TenantRuntimeContext:startTimeesignature-config-deadline-keys-retireddata/ESignatureConfig:expirationDays,data/ESignatureConfig:reminderDaysevent-name-schema-retiredshared/EventNameexport-job-family-retiredapi/,automation/defshot-reload-inert-state-strategies-retiredkernel/DistributedStateConfighot-reload-watch-placeholder-retiredkernel/HotReloadConfig:watchPatternsidentity-api-key-schema-retiredidentity/ApiKeyincident-response-deadline-keys-retiredsystem/IncidentNotificationMatrix:escalationTimeoutMinutes,system/IncidentNotificationRule:regulatorDeadlineHours,system/IncidentNotificationRule:withinMinutes,system/IncidentResponsePhase:targetHours,system/IncidentResponsePolicy:retentionDays,system/IncidentResponsePolicy:triageDeadlineHoursincident-response-family-retiredsystem/Incident,system/IncidentCategory,system/IncidentNotificationMatrix,system/IncidentNotificationRule,system/IncidentResponsePhase,system/IncidentResponsePolicy,system/IncidentSeverity,system/IncidentStatuskernel-compatibility-matrix-estimated-migration-time-unit-in-keykernel/CompatibilityMatrixEntry:estimatedMigrationTimekernel-context-preview-mode-retiredkernel/KernelContext:previewMode,kernel/PreviewModeConfig,kernel/TenantRuntimeContext:previewModekernel-event-bus-retention-unit-in-keykernel/EventPersistence:retention,kernel/EventSourcingConfig:retentionkernel-health-check-and-hot-reload-durations-unit-in-keykernel/HotReloadConfig:debounceDelay,kernel/PluginHealthCheck:interval,kernel/PluginHealthCheck:timeoutkernel-package-lifecycle-durations-unit-in-keykernel/MultiVersionSupport:rollout.duration,kernel/PackageDependencyResolutionResult:resolvedIn,kernel/UpgradePlan:estimatedDurationkernel-plugin-health-report-durations-unit-in-keykernel/PluginHealthReport:metrics.responseTime,kernel/PluginHealthReport:metrics.uptimekernel-plugin-security-durations-unit-in-keykernel/KernelSecurityPolicy:auditLog.retention,kernel/KernelSecurityPolicy:authentication.tokenExpiration,kernel/PluginSecurityManifest:vulnerabilityDisclosure.responseTimekernel-runtime-config-timeout-unit-in-keykernel/RuntimeConfig:resourceLimits.timeout,kernel/SandboxConfig:process.timeoutkernel-startup-orchestrator-durations-unit-in-keykernel/PluginStartupResult:duration,kernel/StartupOptions:timeout,kernel/StartupOrchestrationResult:totalDurationlist-view-navigation-view-retiredui/NavigationConfig:viewlogging-durations-unit-in-keysystem/HttpDestinationConfig:batch.flushInterval,system/HttpDestinationConfig:retry.initialDelay,system/HttpDestinationConfig:timeout,system/LoggingConfig:buffer.flushIntervalmetadata-changed-event-payload-retiredkernel/MetadataChangeOperation,kernel/MetadataChangedEventPayloadmetadata-customization-protocol-retiredapi/,kernel/defsmetadata-manager-config-cache-ttl-unit-in-keykernel/MetadataManagerConfig:cache.ttlmetadata-manager-config-inert-cache-keys-retiredkernel/MetadataManagerConfig:cache.enabled,kernel/MetadataManagerConfig:cache.maxSize,kernel/MetadataManagerConfig:cache.ttlSecondsmetadata-plugin-additional-types-retiredkernel/MetadataPluginConfig:additionalTypespackage-rollback-response-retiredapi/PackageRollbackResponsepackages-list-pagination-retiredapi/ListInstalledPackagesRequest:cursor,api/ListInstalledPackagesRequest:limitplugin-auto-restart-never-reinitialisedkernel/PluginHealthCheck:autoRestart,kernel/PluginHealthCheck:maxRestartAttempts,kernel/PluginHealthCheck:restartBackoffplugin-manifest-contributes-dead-members-retiredkernel/Manifest:contributes.actions,kernel/Manifest:contributes.commands,kernel/Manifest:contributes.drivers,kernel/Manifest:contributes.events,kernel/Manifest:contributes.fieldTypes,kernel/Manifest:contributes.functions,kernel/Manifest:contributes.menus,kernel/Manifest:contributes.themes,kernel/Manifest:contributes.translationsplugin-manifest-contributes-routes-retiredkernel/Manifest:contributes.routesplugin-manifest-dead-containers-retiredkernel/Manifest:capabilities,kernel/Manifest:configuration,kernel/Manifest:extensionsplugin-manifest-kind-globs-retiredkernel/Manifest:contributes.kinds.globsplugin-security-scan-result-surface-retiredkernel/KernelSecurityScanResult,kernel/KernelSecurityVulnerability,kernel/PluginQualityMetrics:securityScan,kernel/PluginSecurityManifest:scanResults,kernel/PluginSecurityManifest:vulnerabilitiesrest-api-endpoint-handler-status-retiredapi/HandlerStatus,api/RestApiEndpoint:handlerStatus,api/RouteCoverageEntry,api/RouteCoverageReportrest-api-plugin-durations-unit-in-keyapi/RestApiEndpoint:cacheTtl,api/RestApiEndpoint:timeout,api/RestApiPluginConfig:performance.defaultCacheTtlrest-server-config-dead-keys-retiredapi/defssession-user-language-retiredapi/SessionUser:languagestack-themes-carrier-retiredui/BorderRadius,ui/ColorPalette,ui/Shadow,ui/Theme,ui/ThemeMode,ui/Typographystartup-orchestrator-retiredkernel/HealthStatus,kernel/PluginStartupResult:health,kernel/PluginStartupResult:plugin,kernel/PluginStartupResult:startTime,kernel/StartupOptions,kernel/StartupOrchestrationResultsystem-cache-durations-unit-in-keysystem/CacheAvalanchePrevention:circuitBreaker.resetTimeout,system/CacheTier:ttlsystem-collaboration-durations-unit-in-keysystem/CollaborationSessionConfig:idleTimeout,system/CollaborationSessionConfig:snapshot.intervalsystem-failover-health-check-interval-unit-in-keysystem/FailoverConfig:healthCheckIntervalsystem-metrics-jsdoc-durations-unit-in-keysystem/MetricDefinition:summary.maxAge,system/MetricExportConfig:interval,system/MetricsConfig:collectionInterval,system/MetricsConfig:retention.period,system/ServiceLevelObjective:errorBudget.burnRateWindows.windowsystem-metrics-window-durations-unit-in-keysystem/MetricAggregationConfig:window.size,system/ServiceLevelIndicator:window.size,system/ServiceLevelObjective:period.durationsystem-object-storage-durations-unit-in-keysystem/AccessControlConfig:maxAge,system/StorageConnection:timeoutsystem-registry-config-durations-unit-in-keysystem/RegistryConfig:cache.ttl,system/RegistryUpstream:syncInterval,system/RegistryUpstream:timeoutsystem-tracing-otel-exporter-durations-unit-in-keysystem/OpenTelemetryCompatibility:exporter.batch.exportTimeout,system/OpenTelemetryCompatibility:exporter.batch.scheduledDelay,system/OpenTelemetryCompatibility:exporter.timeout,system/TracingConfig:performance.exportIntervalsystem-tracing-span-duration-unit-in-keysystem/Span:durationsystem-worker-queue-rate-limit-duration-unit-in-keysystem/QueueConfig:rateLimit.durationtenant-schema-cache-ttl-unit-in-keysystem/SchemaLevelIsolationStrategy:performance.schemaCacheTTLtraining-deadline-keys-retiredsystem/TrainingCourse:durationMinutes,system/TrainingCourse:validityDays,system/TrainingPlan:gracePeriodDays,system/TrainingPlan:recertificationIntervalDays,system/TrainingPlan:reminderDaysBeforetraining-family-retiredsystem/TrainingCategory,system/TrainingCompletionStatus,system/TrainingCourse,system/TrainingPlan,system/TrainingRecordwebsocket-durations-unit-in-keyapi/WebSocketConfig:pingInterval,api/WebSocketConfig:reconnectInterval,api/WebSocketConfig:timeout,api/WebSocketServerConfig:heartbeatIntervalProse corrected (the single-entry sites of
5854110917/5854456343, and the rationale sentences)packages/spec/src/migrations/registry.ts:78–86(step 17 docblock, fact correction only)semanticlist is deliberately empty」semanticlist is NOT empty. ⛔ No step-17 entry added.packages/spec/src/migrations/registry.ts:5256(step 18 rationale,tenancy.organizationField)object-tenancy-organization-field-retiredpackages/spec/src/conversions/registry.ts:3460(datasource-driver-mongo-to-mongodb, protocol 17)packages/spec/src/conversions/registry.ts:9312(api-endpoint-cache-ttl-to-cache-ttl-seconds)packages/spec/src/conversions/registry.ts:9804(list-view-sort-string-clause-to-array)packages/spec/src/migrations/entries/retired-keys/18.api__ApiEndpoint__cacheTtl.ts:11–19packages/spec/src/migrations/entries/semantic/18.metadata-endpoints-switch-radius-repartitioned.ts:11–13packages/spec/scripts/build-migration-registry.ts:276⛔ Not touched: the governed texts (ADR-0087,
.claude/skills/spec-property-retirement/SKILL.md§3), which are #20188's.The census pin
Where:
packages/spec/src/migrations/migrations.test.ts›registry integrity:from protocol 18 on, every graduated D2 conversion is named by a D3 entry of its own step (ruling B), plus a control test. It reads the major'sentries/semantic/files (comment and literal) inside its own package;check:migration-registryalready proves those files and the generated region are one set.What it asserts: for every step whose
toMajoris 18 or later, every id inconversionIdsappears as a whole id in at least onesemantic/entry of that major. A new major-18 (or later) retirement that lands a D2 conversion with no D3 entry naming it goes red, naming the conversion.What it cannot see, stated so a green run is not over-read: (1) whether the naming entry is that family's OWN (a passing mention satisfies it; the census judged ownership by reading); (2) a family retired with no conversion at all (no machine-readable link joins a retired key or def to its D3 entry; the census paired those by reading, and found none missing). Protocol 17 is outside the pin by design: measured with the same logic, 53 of its 57 graduated conversions are named by no step-17 entry, and step-17 backfill is out of scope (triage
5854164872).Reverse verification (one-shot, no permanent test file). At
c8656ad35, with the entries committed: deleted18.object-tenancy-organization-field-retired.ts(absence confirmed on disk before the run), ran the pin:× from protocol 18 on …with+ "protocol 18: object-tenancy-organization-field-removed",Tests 1 failed | 140 skipped. Restored withgit checkout HEAD -- PATH(that path) inside atrap … EXIT INT TERM: blob2cf3007d9fffequals HEAD's,git diff HEADempty. Direction observed: red, the expected one. No build involved: the test importssrc/and reads the entry files directly.Patch round 1 (contract review
5857834457: FAIL at3197fce29)Blocking: fixed.
Lint & Repo Gatesstep 189 (check-issue-citations.mjs, judging pass) was red. Four bare citations this PR added answer 404 on the board: #10329, #10926, #12868 and #14676. Each was in an entry's leading comment, and again in the regenerated region.--probe-causeclasses all four as deleted (the web endpoint also answers 404, so none was transferred), so none of them is a reference to another repository to qualify. Each comment now anchors to the commit in this repository's history that retired the family, and says in words what that commit decided. That is the precedent of commit66e266c93(ruling C+D on #19123). Every sha is an ancestor oforigin/main:mapping-lookup-params-retired15d58dbf1(the import path never read the four lookup steering params)translation-component-submit-label-retiredd173125fb(the copy key left with its only declarer,element:form)form-view-option-default-retiredc459da6bc(the ruled narrowing: the form-view face drops per-optiondefault, the object-field face keeps it enforced)connector-error-mapping-retired13c48c2a5(eleven inert keys, one spelled like the liveuserMessagechannel)Only the comments changed; no string an author is shown moves. The region was regenerated with
gen:migration-registry. The round-1 report'spnpm check:issue-citations :: exit 0was the package script, which runs only the--self-test. The judging pass CI runs was exit 2 at3197fce29(8 findings = 4 numbers × 2 sites) and is exit 0 now (below).Pin message. The census pin's assertion now names the unnamed
protocol N: conversion-idpairs and the remedy: add a D3semanticentry of that step whose text names the conversion id as a whole word. Its logic and scope are unchanged. Shown firing at21418c4d2with one entry removed (trap-guarded restore, blob equal to HEAD's,git diff HEADempty):AssertionError: graduated D2 conversion(s) named by no D3 entry of their own step: protocol 18: object-tenancy-organization-field-removed. Remedy: add a D3 semantic entry of that step …,Tests 1 failed | 140 passed.Body. Table 1:
integration/Connector:connectionTimeoutMsmoved from row 16 to row 20. Its own comment namesconnector-connection-timeout-ms-removed; the permission id appears there only as a comparison. The code was already right.Sibling PRs
chart— container andblocks[].chart— parses and is never drawn, while the liveness ledger names the joined branch as its reader #20161) has since LANDED as6a6a17b62, with the D2 conversionreport-joined-chart-removedand18.ui-report-joined-chart-retired.ts, which names that id. The union of this head withmainat6a6a17b62is clean and passes the pin (37 pairs, 0 unnamed; delta review5859315908).mainwas merged three times withos-regen-merge.sh, and never by hand in a generated region: ata70cd62e5(fix(spec)!: refusescaleon a currency inline grid column;prefixpromises no default symbol (#20045) #20223 and fix(spec)!: a flattened view overlay is judged by the member its viewKind names (#20186) #20245), at21418c4d2(cel-predicate-one-value-comparand-refusedandfilter-query-face-comparands-refused-at-save) and ata930cacea(step-17 rationale prose from fix(spec): state the RLS using / check texts as the write gate enforces them #20268). Each is D3-only or prose, with no new step-18 conversion. Regeneration produced a commit only after the first merge (3197fce29) and changed nothing after the other two. Every sibling entry id was verified present.Verification (head
a930cacea)pnpm check:issue-citations && node scripts/check-issue-citations.mjs, exactly as CI runs it (baseorigin/main): exit 0, 112 citations across 29 files: 106 resolve, 6 cross-repo unjudged, 0 findings. At3197fce29the same command exited 2.pnpm --filter @objectstack/spec buildunder the verify lock: ok.check:generated: all 15 generated artifacts up to date.check:migration-registry: current (292 semantic, 214 retired-key, 199 retired-def).spec-changes.jsonanddocs/protocol-upgrade-guide.mddo not move: they project up to the current protocol major, and step 18 is beyond it.pnpm --filter @objectstack/spec exec vitest run --project local: 552 files, 16257 passed, 1 todo. Thesrc/migrations/directory alone: 3 files, 151 passed.pnpm --filter @objectstack/spec typecheck(tsc, scripts, test layer) at21418c4d2, the head before the last merge, which brought only another PR's prose into this diff's files: exit 0, test-typecheck debt unchanged (53 files / 255 errors / 142 signatures).node scripts/pm/dispatch-gates.mjs --commands(no paths) ata930cacea, every command run and its exit recorded, reconciled with--ran: 89 derived, 87 run (all exit 0), 2 NOT MEASURED.check:dual-build-cjs-loadsandcheck:type-check-debtexited 3 (PREREQUISITE NOT MET: the full 86-package workspace build does not fit the foreground cap on this shared box). CI runs both.check:pm-dispatch-gatesfinished this time: exit 0, in 907.6 s..tsfiles,--no-inline-config --format json: 0 errors, 0 warnings, none reported ignored.eslint.config.mjsenables no type-aware linting (its own statement ateslint.config.mjs:326–328), so this diff cannot move any untouched file's verdict.@objectstack/specpatch. The published registry text changes; no accept set moves.Acceptance notes (observed, not filed)
registry.ts:108(released step-17 text) still says the sharing-rulefullconversion 「leaves no semantic residue」: triage scoped step-17 backfill out, so it is left as is.//comments only, never in the strings an author is shown (AGENTS.md runtime-strings rule). Several older entries do cite numbers inreason; not touched.dashboard-refresh-interval-unit-in-keystates the console renderer's release lag as a verification step, not as a present fact: this container has no objectui checkout at the pin to measure it.mainmoved after the last merge (a930cacea). fix(spec): os migrate meta guidance for the engine-* migration entries states each lesson in words, not tracker numbers (stage 1) #20285 (2aa25efb4, prose in five semantic entries) and fix(spec): a joined report draws no chart — retire blocks[].chart and refuse a joined container chart (#20161) #20238 (6a6a17b62, a new step-18 conversion with its entry) landed undermigrations/andconversions/. The delta review merged this head onto6a6a17b62: clean, with all six regions still mirrored. The queue verifies the merged generation. (Corrected by the seat at 2026-09-27T19:57Z; the earlier wording said nothing under those paths had moved.)Generated by Claude Code