Skip to content

fix(spec): the protocol 17 → 18 conversion summaries state each decision in words instead of a tracker number (stage 5) - #21568

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-20749-spec-strings-stage5
Oct 3, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-20749-spec-strings-stage5

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #20749
Clause-②: no

Stage 5 of the domain:spec lane's share of the runtime-string burn-down (ruling 5902360492, form D): the rest of class (b), the protocol 17 → 18 conversion summaries in packages/spec/src/conversions/registry.ts. Every rewritten summary now states in words what the cited decision was, or drops a citation its sentence already explained. Text only.

What changed

  • 35 ADR-0087 conversion summaries (40 tracker ids, 34 distinct cards): every toMajor-18 summary that carried an id, from field-malformed-scale-precision-removed to flow-decision-mode-inclusive-explicit. A summary is what os migrate meta --json reports under specChanges (its chain already runs to protocol 18, CHAIN_TERMINUS_MAJOR), and it becomes the upgrade guide's "Change" column and the to text of spec-changes.json's converted[] once protocol 18 ships, so an author upgrading metadata reads it.
  • One @objectstack/spec patch changeset, Clause-②: no (message text only).
  • No generated file changes (A2 below): no generator projects a toMajor-18 summary today.

Census at the base (A1)

Stage 4's instrument (convtable.cjs, byte-identical copy, md5 9b7539067ffc38598172c692de637db5) at base e901c27449 (the worktree before any edit): 127 conversions, 35 summaries with ids, 40 id occurrences, 34 distinct cards — stage 4's split unchanged. The toMajor-18 conversion that PR #21547 added carries no id. Under the ~60-card bar, so one stage. The stage-3 census re-run at the same base agrees (class b: 35 messages / 40 ids; nothing else in class b).

file:line (base) id conversion
registry.ts:6551 #8321 field-malformed-scale-precision-removed
registry.ts:6659 #8762 record-chatter-position-vocabulary
registry.ts:6777 #9198 element-input-target-variable-removed
registry.ts:7024 #9220 element-filter-removed
registry.ts:7177 #9249 element-form-removed
registry.ts:7356 #15178 translation-per-app-settings-removed
registry.ts:7356 #19620 translation-per-app-settings-removed
registry.ts:7610 #9249 translation-component-submit-label-removed
registry.ts:7782 #3951 field-column-lists-canonicalized
registry.ts:7783 #9227 field-column-lists-canonicalized
registry.ts:7909 #10414 metric-filters-removed
registry.ts:8104 #17296 cube-sub-day-granularities-removed
registry.ts:8237 #18612 cube-join-sql-and-relationship-removed
registry.ts:8502 #10054 record-highlights-field-icon-removed
registry.ts:8759 #11027 page-component-responsive-removed
registry.ts:8860 #11805 object-grid-default-sort-removed
registry.ts:9047 #21445 object-grid-resizable-columns-removed
registry.ts:9250 #17260 object-kanban-quick-add-removed
registry.ts:9634 #12497 permission-allow-restore-purge-removed
registry.ts:9637 #1883 permission-allow-restore-purge-removed
registry.ts:9954 #6837 field-reference-to-alias
registry.ts:10372 #14478 hook-timeout-to-timeout-ms
registry.ts:10413 #14478 job-timeout-to-timeout-ms
registry.ts:11017 #14478 api-endpoint-cache-ttl-to-cache-ttl-seconds
registry.ts:11086 #14478 dashboard-refresh-interval-to-refresh-interval-seconds
registry.ts:11447 #14478 memory-persistence-auto-save-interval-to-ms
registry.ts:11671 #14478 turso-config-timeout-to-timeout-ms
registry.ts:11761 #17063 view-page-mount-removed
registry.ts:11866 #17053 list-view-sort-string-clause-to-array
registry.ts:11868 #8221 list-view-sort-string-clause-to-array
registry.ts:12366 #19054 object-tenancy-organization-field-removed
registry.ts:12476 #20085 view-item-owner-hidden-removed
registry.ts:12620 #20230 view-overlay-owner-hidden-removed
registry.ts:13214 #17321 page-component-filter-record-to-rule-array
registry.ts:13214 #6206 page-component-filter-record-to-rule-array
registry.ts:13463 #20161 report-joined-chart-removed
registry.ts:13728 #20221 form-layout-inline-grid-to-vertical
registry.ts:13884 #19992 currency-config-precision-removed
registry.ts:13988 #20321 permission-rls-tags-removed
registry.ts:14128 #15429 flow-decision-mode-inclusive-explicit

Projections (A2)

Neither generator projects a toMajor-18 summary at this base. build-spec-changes.ts and build-upgrade-guide.ts both loop major from MIGRATION_SUPPORT_FLOOR + 1 to PROTOCOL_MAJOR, which are 16 and 17 here (PROTOCOL_VERSION = '17.0.0'), so spec-changes.json carries one perMajor record (16 → 17) and the guide one "Protocol 16 → 17" table. check:generated reads all 15 artifacts up to date on this head with no regeneration, so no generated file is in the diff. Both projections will pick these summaries up when protocol 18 ships.

Delivered: each site, the decision read, the new words (A3)

Every cited card was read through REST with all its comments (30 objectstack cards, objectui#3951, #6206, #6837, #8221). The record column names the comment the decision was read from. Where a summary already said why, the citation is dropped and the sentence kept; where an (#N, ADR-0049 — …) opener cited both, the card number goes and the ADR stays, as stage 4 did. In the cube-join-sql-and-relationship-removed row, ALIAS stands for the angle-bracket placeholder in the source.

conversion (head line) cited decision as read (record) summary now reads
field-malformed-scale-precision-removed (:6550) #8321 refuse a malformed scale/precision at the producer (z.number().int().min(0)); a stored malformed value takes the D2 strip so the row stays loadable; citation dropped, the sentence already said it (body + ACCEPT 5296942541) malformed field 'scale'/'precision' declarations (non-integer or negative) are removed — they were silently unenforced; the schema now refuses them at authoring
record-chatter-position-vocabulary (:6658) #8762 the row's vocabulary converges on the renderer's bottom/right/left: one vocabulary, no mapping layer; the three old spellings take a conversion (ruling 5299771841) record:chatter / record:discussion 'position' respelled to the renderer's vocabulary — 'sidebar' → 'right', 'inline' → 'bottom', 'drawer' → 'right' (one vocabulary, the renderer's, rather than a mapping layer between two: the renderer compares only bottom/right/left, and the old set fell through every branch)
element-input-target-variable-removed (:6778) #9198 ADR-0049 enforce-or-remove: verdict dead (a declarative hint with zero readers), retired with tombstones and a D2 conversion (ACCEPT 5311252358 (PR body verdict)) text-input/record-picker component prop 'targetVariable' removed (retired under ADR-0049 enforce-or-remove as a declarative hint nothing read; the live binding resolves from the page variable whose source names the component id)
element-filter-removed (:7025) #9220 dead at ELEMENT grain (no renderer anywhere; Studio excludes it from the palette), so the whole element retires under ADR-0049, not key by key (verdict 5312176877 + ACCEPT 5312709553) the whole 'element:filter' element retired (ADR-0049 enforce-or-remove at element grain, not key by key — no renderer for it ever shipped in any repo, so every key was a capability claim nothing kept; list surfaces own their filtering via a view's userFilters / the list filter builder). All six props are stripped; the bare node the conversion leaves is refused by name at the parse, with the prescription to delete the component
element-form-removed (:7179) #9249 dead at element grain, the #9220 precedent: the whole element retires; the palette already names object-form as the replacement (dev report 5384430470 (verdict re-taken in the PR body)) the whole 'element:form' element retired (ADR-0049 enforce-or-remove at element grain, not key by key — no renderer for it ever shipped in any repo, so every key was a capability claim nothing kept; use the object-bound 'object-form' block instead — rendered and designer-publishable). All six props are stripped; the bare node the conversion leaves is refused by name at the parse, with the prescription to delete the component
translation-per-app-settings-removed (:7358) #15178, #19620 #15178: the bundle type splits, the platform bundle keeps settings, a per-app bundle refuses it (settings is a platform key). #19620 ruling B: settings leaves the translation item too, because the file door and the item door are two authoring surfaces of one app metadata type and accept one shape (ruling 5653315643 (#15178); ruling 5770445203 (#19620)) translation group 'settings' removed from both application-authored faces, the per-app bundle entry and the registered translation item: settings copy belongs to the platform, and the two authoring doors of one application translation type accept one shape. It is keyed by SettingsManifest.namespace and only platform code declares a manifest. A per-app bundle entry could only fill gaps the platform's own bundle left in the one merged served tree, and was overwritten wherever both defined the key; a stored item OVERRODE the platform copy, because the runtime-authored layer is read over the shipped bundles. Overrides now give way to the platform copy, gaps fall back to the manifest literal, and the group stays on the PLATFORM bundle, PlatformTranslationData
translation-component-submit-label-removed (:7613) #9249 element:form retired whole because no renderer for it ever shipped (dead at element grain), which left submitLabel with no carrier (dev report 5384430470) translation component-copy key 'submitLabel' removed (retired rather than re-anchored — its only declared carrier, 'element:form', retired whole because no renderer for it ever shipped, so the resolver no longer overlays it and a stored string was read by nothing; the live form surface's submit copy is 'object-form''s 'submitText', localized at its own authoring site, and re-anchoring the key there would only have added a second place to translate one word)
field-column-lists-canonicalized (:7787) objectui#3951, #9227 objectui#3951: the published spec spelling name wins and the grid reader is fixed to read it. #9227: inlineColumns gets a strict name-keyed element schema (an unknown key is a named rejection at publish, not a blank cell); relatedListColumns, checked in the same pass, takes field-name strings (ruling 5236150020 (objectui#3951); ruling 5315735776 + ACCEPT 5317488979 (#9227)) inline-grid column entries respelled 'field' → 'name' (the declared spelling wins, and the grid renderer now reads 'name' too) and related-list column objects folded to their child field-name string (both lists were z.any(), so a mis-keyed column published clean and rendered blank cells; inline columns now take a strict name-keyed shape and related-list columns plain field names, so a mis-keyed column is refused at publish)
metric-filters-removed (:7916) #10414 the remove leg of enforce-or-remove: zero consumers (measured with a positive control) and a raw-SQL carrier; retire per the playbook; citation dropped, the sentence already said it (triage grading 5363699539) cube metric key 'filters' removed (ADR-0049 — no strategy ever read it: the authored raw-SQL condition was parsed and dropped, and the query returned the unfiltered aggregate. Filter at query time with where, or use an ADR-0021 dataset measure's structured filter; a metric's own sql is a column reference)
cube-sub-day-granularities-removed (:8111) #17296 each of second/minute/hour is residue and removed: no layer outside the enum names them and queryDateGranularity cannot advertise them; ADR-0049 prefers removal with no committed roadmap; citation dropped (dev report 5648182389 + landing 5648923185) cube dimension granularities 'second' / 'minute' / 'hour' removed (ADR-0049 — no backend bucketed them and none could advertise them: supports.queryDateGranularity is a record over DateGranularity, which declares day, week, month, quarter, year. Offer the coarsest interval that still answers the question)
cube-join-sql-and-relationship-removed (:8244) #18612 retire sql and relationship from CubeJoin: the join is derived from the FK relationship and no author-supplied ON clause executes; the addendum adds the D2 strip for persisted artifacts; citation dropped, the sentence already said it (ruling 5725370783 + addendum 5727426171) cube join keys 'sql' and 'relationship' removed (ADR-0049 — neither was ever read: both strategies synthesise the ON clause as a foreign-key equality, so an authored join condition was REPLACED under a 200 and a declared cardinality changed no SQL. Keep joins.ALIAS.name alone; the record KEY is the foreign-key field on the base object)
record-highlights-field-icon-removed (:8509) #10054 option A: measured dead (zero read points, not designer-publishable), so it retires under the ADR-0087 flow; citation dropped (ruling 5364978909) record:highlights highlight-field key 'icon' removed (ADR-0049 — no render path: the highlight chip has no icon slot, the register hook carries field names only, and the Studio designer publishes the field list as plain strings, so an authored icon was accepted and drawn by nothing)
page-component-responsive-removed (:8766) #11027 ruling B: retire page.components[].responsive (ADR-0049, wired into no renderer) and repair the texts that redirected authors to it (ruling 5380752244) page component key 'responsive' removed (ADR-0049 enforce-or-remove — no renderer ever applied per-component breakpoint layout overrides, and the shared ResponsiveConfig shape leaves with its last carrier; use responsiveStyles (ADR-0065) for breakpoint behaviour that IS applied)
object-grid-default-sort-removed (:8868) #11805 retire object-grid defaultSort (the strict route per the playbook), completing the objectui-side direction ruling at the producer (ruling 5404972152) object-grid component prop 'defaultSort' removed (retired under ADR-0049 enforce-or-remove as the legacy single-sort second spelling of 'sort', read only when 'sort' was absent; the pair moves to sort: [{ field, order }], the array shape every read path honours)
object-grid-resizable-columns-removed (:9055) #21445 resizable is canonical and resizableColumns retires now as a tombstone naming it: zero writers, so no window (immediate retirement) (triage direction 5958164933) object-grid component prop 'resizableColumns' removed (the legacy second spelling of 'resizable', read only when 'resizable' was absent, retires at once so 'resizable' is the one spelling; the value moves to 'resizable' when that is absent, and is deleted when it is present)
object-kanban-quick-add-removed (:9258) #17260 option B: quickAdd leaves object-kanban (accepted and dropped there); this repo carries the tombstone half (card body (the objectui ruling it executes, option B) + triage 5620331176) object-kanban component prop 'quickAdd' removed (retired from the board under ADR-0049 enforce-or-remove — the affordance is gated on a host-supplied 'onQuickAdd' function no producer puts on an object-kanban node, so the key was accepted and dropped; delete the key — object-kanban offers no quick-add control)
permission-allow-restore-purge-removed (:9643) #12497, #1883 option B: retire allowRestore / allowPurge, which gate operations that do not exist; #1883 stays open as the M2 anchor, where undelete/purge ship as feature + RBAC in one batch and the keys return with it (card body (#12497); ruling 5421209848 (#1883)) object-permission keys 'allowRestore' and 'allowPurge' removed (ADR-0049 — the restore/purge operations they claimed to gate have never existed, so granting the bits delivered nothing; dispatched destructive lifecycle verbs stay denied fail-closed. The keys return with the M2 lifecycle initiative, which builds undelete and purge together with the permission bits that gate them)
field-reference-to-alias (:9962) objectui#6837 ruling C: protocol normalisation belongs to the server and the frontend only executes the protocol; half 1 (this repo) guarantees the serve path carries only reference, half 2 deletes objectui's legacy fallback arms (ruling 5475017957 + half-1 pointer 5475055291) field key 'reference_to' → 'reference' (the legacy objectql runtime dialect for a lookup/master_detail target; normalising to the protocol is the server's job and the renderer only executes the protocol, so stored rows must serve the canonical spelling before objectui deletes its reference ?? reference_to fallback arms)
hook-timeout-to-timeout-ms (:10383) #14478 ruling B: a duration-shaped number key carries its unit in its name (or a unit-carrying value), every existing offender renamed under an ADR-0087 conversion, no grandfathered baseline (ruling 5518649320 + population ruling 5548763981) hook key 'timeout' → 'timeoutMs' (a duration key carries its unit in its name, and this one's unit lived only in the description; the value, milliseconds, is unchanged)
job-timeout-to-timeout-ms (:10424) #14478 as above (as above) job key 'timeout' → 'timeoutMs' (a duration key carries its unit in its name, and this one's unit lived only in the description; the value, milliseconds, is unchanged)
api-endpoint-cache-ttl-to-cache-ttl-seconds (:11028) #14478 as above (as above) api endpoint key 'cacheTtl' → 'cacheTtlSeconds' (a duration key carries its unit in its name, and this one's unit lived only in the description; the value, seconds, is unchanged, and the key stays GET-only)
dashboard-refresh-interval-to-refresh-interval-seconds (:11097) #14478 as above (as above) dashboard key 'refreshInterval' → 'refreshIntervalSeconds' (a duration key carries its unit in its name, and this one's unit lived only in the description; the value, seconds, is unchanged)
memory-persistence-auto-save-interval-to-ms (:11458) #14478 as above (as above) memory datasource key 'config.persistence.autoSaveInterval' → 'autoSaveIntervalMs', on both the file and auto arms (a duration key carries its unit in its name, and this one's unit lived only in the description; the value, milliseconds, is unchanged)
turso-config-timeout-to-timeout-ms (:11682) #14478 as above (as above) turso datasource key 'config.timeout' → 'config.timeoutMs' (a duration key carries its unit in its name, and this one's unit lived only in the description and a .meta() title no parse reads; the value, milliseconds, is unchanged)
view-page-mount-removed (:11772) #17063 the maintainer chose to retire (「撤」) over finishing the objectui render half or parking it: the page member and its mount leave the spec under enforce-or-remove (card body (the maintainer ruling it records)) list-view type 'page' and its pageName binding removed (retired rather than finished: the delegating render half was never built, so a page view fell through to the grid branch and drew an empty table; ADR-0049 enforce-or-remove)
list-view-sort-string-clause-to-array (:11877) #17053, objectui#8221 objectui#8221 option B: the legacy string sort is retired, one spelling platform-wide, the array. #17053: the spec slot that produces those documents stops accepting the string objectui now refuses (triage 5620223775 (#17053); ruling 5567944420 (objectui#8221)) the bare string list-view sort clause becomes the { field, order }[] array (one sort orthography platform-wide, the array: objectui already refuses the string, so the schema stops minting documents its own consumer refuses)
object-tenancy-organization-field-removed (:12377) #19054 take organizationField off the authorable surface; its one real use stays a platform-internal fact; citation dropped, the sentence already said it (card body (the maintainer ruling it records)) object tenancy.organizationField removed (ADR-0049 — the stamp-only column declaration was authorable by every application and declared exactly once in the whole protocol, on the platform's own credential table; the divergence moves to a platform-internal table in @objectstack/metadata-core and stops being a knob)
view-item-owner-hidden-removed (:12487) #20085 retire both keys (ADR-0049 enforce-or-remove, zero pull) via the retirement playbook; citation dropped (triage direction 5826969296) view item keys 'owner'/'hidden' removed (ADR-0049 — declared on the view item record and stored verbatim, read by nothing: no view switcher ever filtered on hidden, and no per-user scope ever read owner, so a view marked as one user's was listed for everyone)
view-overlay-owner-hidden-removed (:12631) #20230 follow #20085's disposition for the same key pair on the overlay door: the same retirement (triage direction 5856621469) flattened view overlay keys 'owner'/'hidden' removed (ADR-0049 — the view item's pair on the overlay door, retired the same way: declared, accepted by the write door and stored verbatim, read by nothing, so a hidden: true overlay hid no view and an owner scoped none)
page-component-filter-record-to-rule-array (:13220) objectui#6206, #17321 objectui#6206 option B: one filter orthography platform-wide, the rule array. #17321 ruling B: a partial D2 conversion of what maps losslessly; combinator-carrying rows pass through untouched and are named as a TODO (flattening would silently change what a page selects) (ruling 5406409590 (objectui#6206); ruling 5644018752 (#17321)) a record-form or single-level AST filter at a converged rule-array door becomes the [{ field, operator, value }] rule array wherever the mapping is lossless (flat keys → equals rules, { $op: v } → the mapped operator, AST comparisons → one rule each); a filter carrying $and / $or / $not or any part with no lossless rule spelling is left exactly as stored — reported as a TODO, which os migrate meta --stored lists — and is not the form its door declares (one filter orthography platform-wide, the rule array; the migration converts only what maps losslessly and names the rest, because flattening a combinator would silently change what a page selects)
report-joined-chart-removed (:13477) #20161 retire, not build block charts: the joined arm refuses a container chart, the block key goes, a non-joined report keeps its live chart; citation dropped (triage direction 5852548444) a joined report's 'chart' removed from its blocks and refused on the container (ADR-0049 enforce-or-remove: the joined renderer draws each block as a table and never read either, so the chart parsed and nothing was plotted; a non-joined report keeps its live 'chart')
form-layout-inline-grid-to-vertical (:13742) #20221 retire the inline / grid arms: multi-column already exists as columns and inline is not a record-form layout; citation dropped, the sentence already said it (triage direction 5855767378) form 'layout' arms 'inline' and 'grid' rewritten to 'vertical' (ADR-0049 — no renderer ever gave either a behaviour of its own: every form presentation folded both to 'vertical'. Multi-column is 'columns', honoured under either layout, and is left untouched)
currency-config-precision-removed (:13898) #19992 remove currencyConfig.precision: a currency's decimal places are the currency's, not a setting; citation dropped, the sentence already said it (triage 5817146460 (ruling 乙 on #19910 it executes)) currency field key 'currencyConfig.precision' removed (ADR-0049 — no renderer or runtime ever read it: an amount's decimal places are its currency's ISO 4217 minor unit, derived from the currency itself. Its ISO 4217 contradiction check and the default 2 baked into parse output went with it; the field-level precision is a total digit count and is untouched)
permission-rls-tags-removed (:14002) #20321 RETIRE by the maintainer's criterion (no mainstream platform has the capability); citation dropped, the sentence already said it (triage verdict 5860425529) RLS-policy key 'tags' removed (ADR-0049 — nothing ever read a policy's tags and no mainstream platform tags a row-level policy; dropping it changes no access decision)
flow-decision-mode-inclusive-explicit (:14141) #15429 align with mainstream engines: an edge-branched decision is exclusive (first match), and taking every true edge must be declared (mode: 'inclusive'); the migration writes it explicitly for existing nodes so authored behaviour is unchanged (ruling C narrows that promise to sources and artifacts) (ruling 5793803317; ruling C 5863827385) edge-branched decision with two or more conditioned out-edges and no mode: mode: 'inclusive' written explicitly (the traversal became exclusive, first match in declaration order, as mainstream engines treat a decision, and taking every true edge must now be declared; the key keeps the every-true-edge behaviour those nodes had, and the author deletes it where the branches partition)

No site was left in place as unclear; open_questions is empty.

Text only (A4)

Stage 3's AST-skeleton plus string-text tool (skeleton.cjs, TypeScript 6.0.3; stage 4's copy with only its TypeScript load path changed to this worktree, md5 3b10ec8a7e6284f19def54d35f001698 → 32b4630d7d0a532ee26239319269fe91). Leg 1 compares an AST skeleton with every string's text masked (a + chain of string operands reads as one string, so re-wrapping is invisible); leg 2 compares the text of every string group, requiring each changed group to carry a tracker id before and none after, and every other group byte-identical.

  • registry.ts, base e901c27449 vs the committed copy at 9c1d040145: 1 of 1 SAME, exit 0 — 62656 tokens both sides, 4955 string groups, 35 changed, every changed group carried an id before and carries none after; parse diagnostics 0/0.
  • Controls on scratch copies of the head file, each mutation counted on disk first (anchor hits 1, replacement present 1, anchor left 0): renameFlowConfigAliases renamed → DIFF exit 1; !== flipped to === → DIFF exit 1; one summary re-split into two + operands → SAME, 0 groups changed, exit 0; the hook.timeout surface string (never carried an id) changed → skeleton SAME, text leg VIOLATION, exit 1. No repo file was mutated for the controls.
  • The edits were applied by a script whose every anchor was asserted to hit exactly once, inside its own conversion's summary line span, and verified on disk after the write (36 anchors over 35 conversions). The conversion-table extractor reads 35 summaries changed, toMajor / surface / declaration unchanged on all 127, and 0 ids left in any summary.

Pins and quotes (A6)

No test asserts a summary, so no pin moves: nothing under *.test.* reads .summary off a conversion (the only summary reads are spec-changes.ts and build-upgrade-guide.ts), and every removed id-bearing fragment was searched across the repo. The hits are other files' own prose with their own citations (CHANGELOGs, migrations/registry.ts rationale, docblocks, liveness/*.json notes, test titles such as permission.test.ts:278, docs prose in content/docs/permissions/*.mdx), not quotes of a summary. content/docs/**: no quote of a changed summary. skills/**: none.

Verification

All builds and tests through scripts/pm/os-verify-lock.sh (slot issue-20749-s5), each VERDICT command-exit 0:

  • pnpm --filter @objectstack/spec build, then check:generated on the merged head 9a35e049e5: "✓ All 15 generated artifacts are up to date".
  • The package test script (vitest run --project local --maxWorkers=2) on 9a35e049e5: "Test Files 605 passed (605) / Tests 17904 passed | 1 todo (17905)".
  • test:repo: the 15 repo-project files that read the conversion registry, spec-changes or the guide, "Test Files 15 passed (15) / Tests 221 passed (221)". NOT MEASURED: scripts/build-schemas-check-mode.test.ts (88 cases at about 7 s each, over the foreground cap; it reads only conversion surfaces, which the proof shows unchanged) and the remaining repo-project files; reason: wall clock on a shared box. CI runs them.
  • pnpm --filter @objectstack/spec run typecheck on 9a35e049e5: exit 0; "check:test-typecheck: OK — 52 file(s) / 246 error(s) / 135 pinned signature(s) held".
  • pnpm turbo run build --concurrency=2 --filter=./packages/* --filter=./packages/*/*: "Tasks: 71 successful, 71 total", for the gates that read built packages.
  • Gates: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (no paths) at 9a35e049e5 derives 79 commands; each ran with its exit code written to disk before any pipe. Three first exited 3 (PREREQUISITE NOT MET: check:doc-formula-expressions, check:dual-build-cjs-loads, check:lean-entry-closure need built packages) and exit 0 after the build; the dist-reading gates were re-run after it too. --ran: "✓ dispatch-gates --ran: 79 derived famil(ies) accounted for — 79 run, 0 NOT-MEASURED".
  • pnpm check:doc-authoring (self-test and run): exit 0, "17323 customer-facing string(s) across 1250 spec sources clean"; the sibling-package ledger holds its baseline (packages/spec sits outside it, so no ledger change). pnpm check:nul-bytes: exit 0, "no raw ASCII control bytes".
  • Changeset gates with this body as the --event payload: check-changeset-no-major.mjs --base origin/main --event exit 0 ("✓ LEVEL AXIS: this PR declares clause-② no", declaration line Clause-②: no); check-partof-closing-keyword.mjs with PR_BODY exit 0 ("no Part-of/closing-keyword contradiction"); check-adr-0087-registration.mjs --base origin/main exit 0 ("adds no declared-breaking changeset (1 non-breaking changeset(s) seen)"); check-empty-changeset.mjs --base origin/main exit 0; check-changeset-fixed.mjs exit 0.
  • ESLint, a proven narrowing: eslint --no-inline-config --format json over the one changed TS file reads 1 file, 0 errors, 0 warnings; the population is read from ESLint's own config (calculateConfigForFile resolves it, isPathIgnored false); invariance: eslint.config.mjs enables no type-aware linting (parserOptions.project / projectService null for this file, its header at :327-328 says so), so a string-text edit cannot move an untouched file's verdict. Repo-wide pnpm lint is CI's.

Acceptance notes


Generated by Claude Code

claude added 2 commits October 3, 2026 07:31
…ion in words instead of a tracker number (stage 5)

Text only: the 35 toMajor-18 summaries in conversions/registry.ts that
cited a tracker number now say what the cited decision was, or drop a
citation the sentence already explained. No id, surface, step,
transform or order changes. One @objectstack/spec patch changeset.

Claude-Session: https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added the size/m label Oct 3, 2026
@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 15 documentable anchor(s).

14 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/declarative-endpoints.mdx (via cacheTtlSeconds (literal, a string literal in summary))
  • content/docs/automation/flows.mdx (via timeoutMs (literal, a string literal in summary))
  • content/docs/automation/hook-bodies.mdx (via timeoutMs (literal, a string literal in summary))
  • content/docs/automation/jobs.mdx (via timeoutMs (literal, a string literal in summary))
  • content/docs/automation/webhooks.mdx (via timeoutMs (literal, a string literal in summary))
  • content/docs/deployment/environment-variables.mdx (via timeoutMs (literal, a string literal in summary))
  • content/docs/getting-started/quick-reference.mdx (via cacheTtlSeconds (literal, a string literal in summary))
  • content/docs/permissions/permission-metadata.mdx (via allowPurge (literal, a string literal in summary), allowRestore (literal, a string literal in summary))
  • content/docs/permissions/permission-sets.mdx (via allowPurge (literal, a string literal in summary), allowRestore (literal, a string literal in summary))
  • content/docs/permissions/permissions-matrix.mdx (via allowPurge (literal, a string literal in summary), allowRestore (literal, a string literal in summary))
  • content/docs/protocol/kernel/http-protocol.mdx (via cacheTtlSeconds (literal, a string literal in summary))
  • content/docs/protocol/kernel/lifecycle.mdx (via timeoutMs (literal, a string literal in summary))
  • content/docs/protocol/objectql/security.mdx (via allowPurge (literal, a string literal in summary), allowRestore (literal, a string literal in summary))
  • content/docs/ui/dashboards.mdx (via refreshIntervalSeconds (literal, a string literal in summary))

⛔ 8 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v12.mdx (via allowPurge (literal, a string literal in summary), allowRestore (literal, a string literal in summary))
  • content/docs/releases/v16.mdx (via timeoutMs (literal, a string literal in summary))
  • content/docs/releases/v17/17-0.mdx (via defaultSort (literal, a string literal in summary), timeoutMs (literal, a string literal in summary))
  • content/docs/releases/v17/17-1.mdx (via targetVariable (literal, a string literal in summary))
  • content/docs/releases/v17/17-3.mdx (via allowPurge (literal, a string literal in summary), allowRestore (literal, a string literal in summary), defaultSort (literal, a string literal in summary))
  • content/docs/releases/v17/17-4.mdx (via cacheTtlSeconds (literal, a string literal in summary), refreshIntervalSeconds (literal, a string literal in summary), timeoutMs (literal, a string literal in summary))
  • content/docs/releases/v17/17-5.mdx (via quickAdd (literal, a string literal in summary), timeoutMs (literal, a string literal in summary))
  • content/docs/releases/v17/17-6.mdx (via quickAdd (literal, a string literal in summary), refreshIntervalSeconds (literal, a string literal in summary))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see

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

Which tree this was computed on

This run read content/docs from 835137fc4333c4abc146465144903d1df7b98658 — the merge of head 9a35e049e5af42d5453da3da41026dd631d937bf into base 1ca1eb09727d7769bc605428ad1e6fb62d743f49, 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 835137fc4333c4abc146465144903d1df7b98658 && git checkout 835137fc4333c4abc146465144903d1df7b98658
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 1ca1eb09727d7769bc605428ad1e6fb62d743f49 9a35e049e5af42d5453da3da41026dd631d937bf && git checkout -B drift-repro 1ca1eb09727d7769bc605428ad1e6fb62d743f49 && git merge --no-ff 9a35e049e5af42d5453da3da41026dd631d937bf

node scripts/docs-audit/affected-docs.mjs --json 1ca1eb09727d7769bc605428ad1e6fb62d743f49

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

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 1ca1eb09727d7769bc605428ad1e6fb62d743f49 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tooling labels Oct 3, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 9a35e049e5af42d5453da3da41026dd631d937bf
Local-runs: none

① Derived judgments

Inputs. Read on GitHub through REST: card #20749 (body and all 17 comments, the stage-4 dev report 5966338878 with its split_next_stage, the stage-4 review 5966471031, the stage-5 claim 5966674486 and the stage-5 dev report 5967265796 included), ruling 5902360492 on #20513, the stage-4 precedent (PR #21555 landed e901c27449, its 9-file list and review), PR #21568 (body, 2-file list, net diff against the merge base 5dbcee8a6d, its one bot comment, the Docs Drift Check), this head's check-runs at 08:37Z and again at 08:53Z, the 34 cited cards with every comment (30 here plus objectui#3951, #6206, #6837, #8221, and the objectui#8285 ruling 5583979207 that #17260 executes), and the file lists of the eight other open PRs. Repository reads were git show / git grep at origin/main and this head; the head's conversions/registry.ts, spec-changes.json, docs/protocol-upgrade-guide.md, kernel/protocol-version.ts, migrations/registry.ts, migrations/spec-changes.ts, the two generator scripts and cli/src/commands/migrate/meta.ts were read, nothing built, run or re-run. An isolated subagent of the seat session rendered this record.

Text only; the accept set is unchanged. The registry diff is 35 hunks and 127 changed lines; every removed and every added line is the string literal of a summary: or one operand of its + chain (checked mechanically over the diff text, 0 lines of any other shape). No id, toMajor, surface, retiredFromLoadPath, retiredAfter, apply body, schema key, shape, default or enum moves; no summary's "what moves" clause changes. 40 tracker ids leave the removed lines (34 distinct cards; #14478 six times, #9249 twice); 0 id-like tokens stand on any added line. At the head no conversion summary carries an id (the only # followed by digits left in the file are a code comment at :3432 and two #000000 colour literals), so class (b) is empty, as the report says.

Public surface. A summary is a data value an author reads. conversion.summary has exactly two readers in the tree: migrations/spec-changes.ts:250 (to: conversion.summary, inside composeSpecChanges) and scripts/build-upgrade-guide.ts:101 (the guide's "Change" column). Through the first, the one live surface today is os migrate meta --json, whose specChanges is composeSpecChanges(fromMajor, toMajor) with toMajor defaulting to CHAIN_TERMINUS_MAJOR = Math.max(PROTOCOL_MAJOR, ...MIGRATION_MAJORS) (meta.ts:89, :606, :641), which is 18 because step-18 migration entries exist; the human-readable os migrate meta output prints no summary. Only text changes on that surface: no export, type, key or acceptance change, so check:api-surface (success inside Lint & Repo Gates), the liveness gate and the type-check gates read green as expected.

The A2 projection reading is right. build-spec-changes.ts:254 and build-upgrade-guide.ts:78 both loop major from MIGRATION_SUPPORT_FLOOR + 1 to PROTOCOL_MAJOR; at this head PROTOCOL_VERSION = '17.0.0' (kernel/protocol-version.ts:18) and MIGRATION_SUPPORT_FLOOR = 16 (migrations/registry.ts:79), so each projects protocol 16 → 17 and nothing else. Confirmed on the artifacts themselves: spec-changes.json at the head carries aggregate 16 → 17 and a single perMajor record (57 converted each, 0 occurrences of toMajor: 18 or to: 18), byte-equal in coverage to main; the guide has one "Protocol 16 → 17" table and no protocol 18 heading. No toMajor-18 summary is projected anywhere, so no generated file is owed, and the 2-file list is right; both projections will pick these 35 summaries up when protocol 18 ships. The MCP spec_changes tool derives from spec-changes.json, so it is unchanged too. Lint & Repo Gates, which runs check:generated, check:spec-changes and check:upgrade-guide on the merge ref, completed success at 08:52:38Z, which confirms it.

The 35 rewritten strings, each against the cited decision. All 35 state the decision truly or drop a citation whose sentence already stated it; none changes the summary's "what moves" clause. Eleven are citation drops with the sentence otherwise unchanged (the ADR-0049 name kept where the opener carried it): #8321, #10414, #17296, #18612, #10054, #19054, #20085, #20161, #20221, #19992, #20321. The other 24 gain words naming the decision.

Conversion Cited Read against Judgment
field-malformed-scale-precision-removed #8321 body: refuse a malformed scale/precision at the producer (z.number().int().min(0)); ACCEPT 5296942541: stored rows take the D2 strip with retiredFromLoadPath so a row stays loadable True: citation dropped
record-chatter-position-vocabulary #8762 ruling 5299771841 item 1: the row's vocabulary converges on the renderer's bottom/right/left, one vocabulary, no mapping layer; item 2: the three old spellings take a conversion True
element-input-target-variable-removed #9198 body (ADR-0049 enforce-or-remove on a declarative hint with zero readers) and ACCEPT 5311252358 (retirement route, tombstones, D2 conversion) True
element-filter-removed #9220 verdict 5312176877: dead at element grain, element-grain retirement under ADR-0049, not key by key; ACCEPT 5312709553 True
element-form-removed #9249 dev report 5384430470: verdict dead at element grain, no renderer registration anywhere, the palette exclusion names object-form as the replacement True
translation-per-app-settings-removed #15178, #19620 ruling 5653315643 ②: settings is a platform key and a per-app bundle refuses it; ruling 5770445203 B: the file door and the item door are two authoring surfaces for one app metadata type, so they accept one shape True
translation-component-submit-label-removed #9249 5384430470: element:form dead at element grain because no renderer ever shipped True
field-column-lists-canonicalized objectui#3951, #9227 objectui ruling 5236150020: the published spelling name wins, fix the reader; ruling 5315735776: a strict name-keyed element schema, relatedListColumns checked in the same pass; ACCEPT 5317488979: relatedListColumns took field-name strings True
metric-filters-removed #10414 triage 5363699539: the remove leg, zero consumers measured with a positive control, retire per the playbook True: citation dropped
cube-sub-day-granularities-removed #17296 dev report 5648182389 and landing 5648923185: second/minute/hour are residue; supports.queryDateGranularity is a record over the five-member DateGranularity, so no driver could advertise them True: citation dropped
cube-join-sql-and-relationship-removed #18612 ruling 5725370783 letter 2: retire sql and relationship, the join is derived from the FK relationship and no authored ON clause executes; addendum 5727426171: the D2 strip for persisted artifacts True: citation dropped
record-highlights-field-icon-removed #10054 ruling 5364978909 option A: measured dead (zero read points, not designer-publishable), retires True: citation dropped
page-component-responsive-removed #11027 ruling 5380752244 B: retire under ADR-0049 D2 and repair the four redirect texts; responsiveStyles is the live channel True
object-grid-default-sort-removed #11805 ruling 5404972152: retirement approved by the strict route of the playbook; the card is the ADR-0049 enforce-or-remove channel of objectui#4869 True
object-grid-resizable-columns-removed #21445 triage 5958164933 item 2: resizableColumns retires now as a tombstone naming resizable; zero writers, so no window True
object-kanban-quick-add-removed #17260 objectui#8285 ruling 5583979207 B, which the card executes: the key is retired from ComponentPropsMap['object-kanban'] under ADR-0049 enforce-or-remove; triage 5620331176 True
permission-allow-restore-purge-removed #12497, #1883 #12497 body: retire the two props under ADR-0049/ADR-0087; #1883 ruling 5421209848: the card stays the M2 anchor, undelete/purge ships as feature plus RBAC in one batch and the retired keys return with it True
field-reference-to-alias objectui#6837 ruling 5475017957 C: protocol normalisation belongs to the server and the frontend only executes the protocol; half 1 (#13700) guarantees the serve path, half 2 deletes the arms (5475055291) True
the six #14478 renames (hook-timeout-to-timeout-ms, job-timeout-to-timeout-ms, api-endpoint-cache-ttl-to-cache-ttl-seconds, dashboard-refresh-interval-to-refresh-interval-seconds, memory-persistence-auto-save-interval-to-ms, turso-config-timeout-to-timeout-ms) #14478 ruling 5518649320 B: a duration-shaped z.number() key carries its unit in the key name or in the value, every offender renamed under an ADR-0087 conversion, no grandfathered baseline; population ruling 5548763981 B widens it to every workspace package, the turso offender named True, all six
view-page-mount-removed #17063 card body: the maintainer's 「撤」 over A (finish the objectui mount) and D (park); the render half was never built True
list-view-sort-string-clause-to-array #17053, objectui#8221 objectui ruling 5567944420 B: one spelling, the array, the string refused; triage 5620223775: the spec slot is the producer of documents objectui now refuses True
object-tenancy-organization-field-removed #19054 card body (maintainer 「organizationField 撤出可授权面 同意你的建议」): off the authorable surface, its one real use a platform-internal fact in metadata-core True: citation dropped
view-item-owner-hidden-removed #20085 triage 5826969296: retire both keys, ADR-0049 enforce-or-remove, zero pull True: citation dropped
view-overlay-owner-hidden-removed #20230 triage 5856621469: follow #20085's disposition for the same pair on the overlay door True
page-component-filter-record-to-rule-array objectui#6206, #17321 objectui ruling 5406409590 B: one filter orthography platform-wide, the rule array; ruling 5644018752 B: a partial D2 of the lossless subset, combinator rows pass through and are named; C refused because flattening silently changes what a page selects True
report-joined-chart-removed #20161 triage 5852548444: retire; chart on non-joined reports stays True: citation dropped
form-layout-inline-grid-to-vertical #20221 triage 5855767378: retire the inline and grid arms; multi-column is columns True: citation dropped
currency-config-precision-removed #19992 triage 5817146460, executing ruling 乙 on #19910: a currency's decimal places are the currency's, not a setting; removal True: citation dropped
permission-rls-tags-removed #20321 triage 5860425529: RETIRE by the maintainer's #18900 criterion, no mainstream platform tags a row-level policy True: citation dropped
flow-decision-mode-inclusive-explicit #15429 ruling 5793803317 「跟主流对齐」: exclusive, first match in declaration order; every-true-edge must be declared as mode: 'inclusive', written by the migration for existing nodes; ruling C 5863827385 narrows that preservation to sources and artifacts, which is exactly a conversion's scope True

No surviving quote, no pin. .summary is read by no test in the tree (every .summary hit under *.test.* is another object's field: endpoints, reports, blueprints). The removed id-bearing fragments survive at this head only as other files' own prose with their own citations: source comments and docblocks (data/driver.zod.ts:23, conversions.test.ts:1664, the step-18 semantic entries), liveness/*.json notes, test case titles (record-validator.test.ts:997, component.test.ts:2607, protocol.stored-conversions.test.ts:178), hand-written docs prose (content/docs/permissions/*.mdx, protocol/objectql/security.mdx), ADR text and CHANGELOGs. The generated reference pages carry the retiredKey prescriptions, whose wording differs from the summaries ("in objectui, framework or cloud" against the summary's "in any repo"), so none quotes a summary. composeSpecChanges tests (migrations.test.ts, spec-changes-surface-scope.test.ts) assert structure over the 16 → 17 range; the CLI meta.report-order.test.ts asserts applied[] from/to records, not summaries. No pin moves, correctly.

Census and boundary. The stage-4 split listed 35 summaries / 40 ids / 34 cards at db81973cf6; the base e901c27449 carries the same (PR #21547's page-requires-non-compiled-kind-removed, landed since, carries no id); the diff removes exactly those 40 ids from exactly those 35 summaries. Under the claim's ~60-card bar, one stage; the boundary is the one the claim named, and class (b) closes with it.

② Semver level

@objectstack/spec patch, Clause-②: no — right. Author-visible text changes on a data value with no key, shape, default, export or acceptance change; no published surface grows or narrows. The changeset's frontmatter is '@objectstack/spec': patch, it carries Clause-②: no at a line start, and it states in words that a tool or test matching the old summary text needs the new spelling. The PR body carries Part of #20749 and Clause-②: no at line starts and no closing keyword (Check Changeset, "Part-of PR must not also close its card", "The card this PR closes must claim this branch" and the two single-writer guards all success). Same level and clause as the stage-4 precedent, whose review 5966471031 passed the identical shape.

③ Boundary flags

  1. Proof tool — stage 3's skeleton.cjs with the TypeScript load path changed, convtable.cjs byte-identical; md5s stated. Not re-run here. The text-only finding above rests on reading every hunk and on a mechanical check over the diff text that all 127 changed lines are summary string operands; nothing in it depends on the tool.
  2. Citation form — eleven drop-only, 24 with new words, as the report counts; verified line by line (the eleven are named above). The form stage 4's review accepted.
  3. Decision records — eight cards whose decision of record is a triage direction executed by a landed PR ([finding] MetricSchema.filters is an authorable per-metric filter with zero consumers — a hand-authored cube's filters: [{ sql }] is parsed and dropped #10414, view.hidden and view.owner are declared on the strict ViewItem authoring door and stored verbatim, but nothing in either repo reads or writes them — and check:liveness cannot see it, because its view walk stops at the container arm #20085, spec(ui): a flattened view overlay accepts owner / hidden at the save door and nothing reads either, so hidden: true saves clean and the view stays listed #20230, [finding] a joined report's chart — container and blocks[].chart — parses and is never drawn, while the liveness ledger names the joined branch as its reader #20161, spec(ui): layout: 'inline' | 'grid' on object-form / FormView parse green and render as vertical — enforce-or-remove (ADR-0049) #20221, spec(security): retire rowLevelSecurity[].tags (1 key); no mainstream platform tags a row-level policy #20321, finding(spec): ComponentPropsMap['object-grid'] keeps resizableColumns as a live alias of resizable, and types six members ObjectGrid reads with fixed shapes as z.unknown() (rowHeight: 42 passes every door) #21445; spec(ui): ListViewSchema.sort still accepts the legacy "field desc" string — it is the PRODUCER whose documents objectui now refuses loudly, and #16553 does not cover it #17053 executing objectui#8221's ruling), ADR-0049 candidate: element:form declares a full form contract with no renderer registration found in objectstack/objectui #9249's element-grain verdict in its dev report and PR body, spec: retire the page member of the list-view type enum and its pageName mount — enforce-or-remove, the objectui render half was never built (maintainer 2026-09-09 「撤」) #17063 and Retire tenancy.organizationField from the authorable surface — one platform table's fact, not customer configuration #19054 carrying the maintainer's word in the card body. Each was read with every comment; each new sentence states what its record says. spec(ui): ObjectKanbanProps.quickAdd is accepted-and-dropped on object-kanban — objectui ruled it retired; the tombstone is this repo's half #17260's new words name ADR-0049 enforce-or-remove: the objectui#8285 ruling it executes says so verbatim, so the card filer's hedge about ADR-0049's scope does not bear on the summary. Clear, not unclear.
  4. Edit tooling — the backslash-u escape mishap was caught before any run; the added api-endpoint-cache-ttl-to-cache-ttl-seconds line keeps its → escape, and the removed — left with the citation it belonged to. Cosmetic; nothing owed.
  5. Hot file and main movement — origin/main merged once (9a35e049e5 is the merge, first parent 5dbcee8a6d; none of the five commits touches this PR's files); main then moved three commits (cli, service-analytics, the sibling-package ledger), none under packages/spec, and GitHub reads mergeable: true; the Docs Drift Check built the merge ref 835137fc43 on base 1ca1eb0972. At 08:5xZ none of the eight other open PRs (fix(rest): one anonymous-intake rule honours every declared public-form withdrawal #21566, fix(runtime)!: an app-authored body may not bind a hook to, or write, the stored-metadata tables (#21520) #21563, feat(spec)!: eight list members of object-grid, object-kanban and object-calendar take the shape each block reads; object-grid columns held (#21464, stage 2) #21559, fix(cli): os dev -a and os start --artifact serve the named artifact beside a cwd objectstack.config.ts — one artifact precedence for start, dev and the serve child #21549, fix(objectql): an in-process engine verb refuses an object name the registry does not resolve (#21516) #21545, fix(cloud-connection): an install-local uninstall runs the protocol's registered uninstall cleanups #21512, chore: version packages #21352, chore(ci): refresh the Test Core shard-timings dataset #20388) touches conversions/registry.ts, spec-changes.json or the upgrade guide, which matches the report. Nothing owed.
  6. A2 — judged right in ①: both generators stop at PROTOCOL_MAJOR = 17, and the artifacts confirm it; the one live surface today is os migrate meta --json's specChanges.
  7. Partial local test run — scripts/build-schemas-check-mode.test.ts, the other repo-project files and the publish-smoke files were not run locally; CI is the measurement. At 2026-10-03T08:53Z this head read 26 success, 3 skipped by design (Build Docs, Console Pin Gate, the opt-in packed-tarball smoke), 4 in progress (Test Core 1/6, 2/6, 4/6; Type Check · workspace), 0 failed. Lint & Repo Gates, Type Check · consumer gates, Type Check · source gates, Type Check · debt ledger, Build Core, Dogfood Verify CLI, the three Dogfood Regression Gate shards, Temporal Conformance and Test Core 3/6, 5/6, 6/6 completed success. The four in-progress runs are an honest reading, not a pass: this verdict rests on the diff, the cards and the completed gates, and the seat reads the four to completion before enqueue, as stage 4's did.
  8. Docs Drift Check — advisory; its 14 hand-written pages are anchored on key literals inside the summaries (timeoutMs, cacheTtlSeconds, allowPurge, …), and no key moves, so no page is falsified; the 8 release-owned pages are untouched, rightly.
  9. Angle-bracket placeholders — PR body only (GitHub's sanitizer); the source keeps joins.ALIAS.name spelled with its angle brackets, unchanged by this diff. Cosmetic.
  10. Governed surface — NOT governed (Governed Surface Queue Guard success; no .claude/**, skills/**, ADR, NORTH-STAR or AGENTS.md path, no generated artifact). Ordinary queue once every check is complete. The PR is a draft; the seat flips it, as stage 4's was.
  11. Serial constraints — migrations/registry.ts (packages/spec/src: 1,277 comment lines still cite 170 deleted tracker numbers (1,295 sites) — the staged remainder of ruling C+D on #19123, measured by PR #20226 #20234 stage 11), the conformance modules (class c), api/error-code-ledger.zod.ts and kernel/public-auth-features.ts (class f), every test file and the two .mjs gate scripts are untouched; the 2-file list is exactly the one source and the changeset. No gate is added or loosened, by ruling: no scripts/** or .github/** path.
  12. Ruling order and the ledger — class (b) is author-visible text, so it rightly precedes classes (c), (f) and the test strings; packages/spec sits outside scripts/doc-authoring-prose-id.baseline.json, so the ledger is rightly untouched (check:doc-authoring exit 0 in the report and inside Lint & Repo Gates; Spec property liveness success).
  13. open_questions and out_of_scope_findings — both empty; none owed. Stage 4's "noted, not filed" feat(spec)!: page requires only on the compiled kinds — refused at parse on react, full and slotted pages (#21459) #21547 item was rightly dropped: feat(spec)!: page requires only on the compiled kinds — refused at parse on react, full and slotted pages (#21459) #21547 landed, its toMajor-18 entry carries no id, and neither projection owed it a regeneration.
  14. Trailers — the commits carry the repo's model-free pair per the report; not a contract matter and not re-read here.

Implemented-by: claude/issue-20749-spec-strings-stage5
Reviewed-by: session_01YDt3PzwfrkuFzUBF89WPmM

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

ACCEPT — PR #21568 @ 9a35e049 (stage 5 of #20749)

domain:spec seat 2 (session_01YDt3PzwfrkuFzUBF89WPmM), holder of claim 5966674486 · 2026-10-03T09:06Z

  • Shape (read on GitHub): a draft against main. The first line is Part of #20749, and Clause-②: no sits at a line start, with no closing keyword. 2 files, +87 / −56: conversions/registry.ts and one changeset.
  • Review: the contract review at CONTRACT_REVIEW_TIER, 5967411495, reads PASS on this head.
    • All 35 toMajor-18 conversion summaries state the cited decision truly, or drop a citation whose sentence already stated it. The reviewer read all 34 cited cards (30 here and four objectui cards) with every comment.
    • Text only: every changed line is a summary string or one operand of its chain. No id, toMajor, apply body, key or acceptance moves. 40 ids leave, and none stands on an added line.
    • No generated file is owed. Both projections (spec-changes.json, the upgrade guide) loop to PROTOCOL_MAJOR = 17, so none projects a toMajor-18 summary yet. The one live surface today is os migrate meta --json's specChanges, which reaches step 18. Both projections pick these summaries up when protocol 18 ships.
    • Release: @objectstack/spec patch, Clause-②: no.
  • Class (b) is now empty in packages/spec/src. The conversion summaries and the descriptions are done.
  • CI on 9a35e049: 32 success and 3 skipped by design (Build Docs, Console Pin Gate, packed-tarball smoke).
  • Governed surface: not governed.
  • Serial: at this read, no other open PR touches conversions/registry.ts.

Staging after this lands: class (c), the 58 conformance-case notes. The #5322 selector in validate-empty-combinators.test.ts and the #8934 name pin in icontains-dialect-sql.test.ts move with their notes. Then class (f), the 17 registry rationales, and the test strings. migrations/registry.ts stays #20234's stage 11.

Next: ready, auto-merge, the queue.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 3, 2026 09:07
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 3, 2026 09:07
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 3, 2026
Merged via the queue into main with commit a387354 Oct 3, 2026
37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20749-spec-strings-stage5 branch October 3, 2026 09:38
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/m tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants