Repository navigation
fix(spec): the protocol 17 → 18 upgrade rationale states each cited decision in words instead of a tracker number (stage 9) - #21698
Merged
Conversation
…n words, not tracker numbers The 49 STEP18_RATIONALE fragments that carried a tracker id (88 ids: 72 objectstack, 12 objectui, 4 decision-batch numbers) now say what the cited record decided, or drop a citation the sentence already explained. Text only: fragment ids, orders, entries, conversions and schemas are unchanged. Claude-Session: https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ Co-authored-by: Claude <noreply@anthropic.com>
…er rewrite Claude-Session: https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ Co-authored-by: Claude <noreply@anthropic.com>
Contributor
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)), so this run has no opinion about the docs. 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): |
This was referenced Oct 4, 2026
This was referenced Oct 4, 2026
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Oct 7, 2026
…t blocks[i].dataset, naming the block (objectstack-ai#21702) (objectstack-ai#21712) Fixes objectstack-ai#21702 Clause-②: yes (narrowing) Triage direction `5977861131` (enforce), claim `5977924610`. The joined arm of `ReportSchema`'s refinement now refuses each block of a `joined` report that binds no `dataset`, at `blocks[i].dataset`, naming the block, with the prescription to bind the block to a dataset. The refinement comment "each block dataset-bound" and the reports guide's type-table cell "(each block dataset-bound)" were declarations; they are now enforced. ## What changes - **The refusal** (`packages/spec/src/ui/report.zod.ts`). A per-block arm inside the joined branch of `ReportSchema`'s `superRefine`, right after the existing "needs `blocks`" check. For each block whose `dataset` is undefined it adds one `custom` issue at `['blocks', i, 'dataset']`. The message comes from a module-private builder, `joinedBlockDatasetRequired`, worded like the neighbouring joined refusals: ```text a `joined` report draws each block from that block's own `dataset`, and block `NAME` binds none, so nothing queries it and it draws no rows. Bind the block to a dataset: set its `dataset` to the dataset whose measures (`values`) and dimensions (`rows`) it shows. ``` `NAME` is the block's `name`. A block whose `name` is empty (its own `too_small` issue, which does not stop the refinement) is named by position instead, as `blocks[1]`. - **What stays accepted.** `JoinedReportBlockSchema.dataset` stays `.optional()`: `blocks` is read on a `joined` report only, so the requirement lives on the joined arm, and a block on a non-joined report is not judged (pinned). No type, export or JSON Schema key changes. The block's `dataset` `.describe()` now reads "Dataset name to bind (ADR-0021); a joined report refuses a block without one", and `content/docs/references/ui/report.mdx` is regenerated with `gen:docs` (two table rows). - **The ADR-0087 kit**, in objectstack-ai#21687's shape: - the D3 semantic entry `entries/semantic/18.ui-report-joined-block-dataset-required.ts`; - its step-18 rationale fragment in `STEP18_RATIONALE` at **order 77**, inserted where its id sorts. 77 is the next free order: the highest on `main` is 76, re-read at `7e0066af7a` just before this PR. objectstack-ai#21699 (74 to 76) and objectstack-ai#21698 landed before this branch's base `16d241a6af`, so no merge was needed; - `registry.ts` regenerated with `gen:migration-registry`; - one BREAKING `@objectstack/spec` `minor` changeset, `.changeset/21702-joined-report-block-dataset-required.md`, carrying the `Clause-②` line, the `adr-0087: registered ui-report-joined-block-dataset-required` disposition marker and a FROM → TO table. The fragment and entry text state each decision in words and carry no tracker number. No tombstone (no key is removed) and no D2 conversion (only the author knows which dataset a block was meant to show). - **`content/docs/ui/reports.mdx`: no edit.** No sentence there became false; the type-table cell became true. See Acceptance notes. ## Census, at base `16d241a6af`, before any edit `git grep` for a `type: 'joined'` report across `examples/**`, `packages/**`, `skills/**`, `content/docs/**`, `docs/**`, `scripts/**` and `apps/**` gave 33 lines in 15 files: CHANGELOG quotations, the spec's own comments, and these reports: | where | joined reports | unbound blocks | |:--|:--|:--| | `examples/app-showcase` `TaskOverviewReport` | 1 | 0 | | `content/docs/ui/reports.mdx` example | 1 | 0 | | `packages/lint` `validate-chart-bindings.test.ts` (raw stacks, never parsed) | 4 | 0 | | `packages/platform-objects` `report-form-echo-decisions.test.ts` | 2 | 0 | | `packages/spec` tests (`report.test.ts`, `filter-save-door-face-parity.test.ts`, `joined-report-block-type.test.ts`) and the `report-joined-chart-removed` conversion fixture | 10 | 0 | | `packages/metadata-protocol` `protocol.invalid-metadata-422-face-inventory.test.ts` | 1 | **1**, unbound on purpose (below) | | `skills/**` | 0 | 0 | - Triage measured hotcrm `4054ec26` (1 joined report, 0 unbound blocks) and cloud `2205b530` (none). I took those readings as given. I read hotcrm's `src/sales/reports/churn.report.ts` once, to shape the preservation fixture. Every block binds a dataset: three bind `account_metrics` and the fourth, `recently_closed_lost`, binds `opportunity_metrics`. So triage's line "every block binds `account_metrics`" is slightly off; its conclusion, zero unbound blocks, stands. - **objectui (read only, at `2e818d0b51` and at this repo's pin `ab18797215`).** Studio's joined-report authoring **can** save a block with no `dataset`. That producer is this narrowing's reach: - `ReportDefaultInspector.tsx` renders the blocks through `SchemaForm` with the spec `reportForm` "Joined blocks" repeater; - `RepeaterField`'s `add()` seeds a blank row with every column `undefined`; - the row's `dataset` column is free text and not required (the block's derived JSON Schema `required` is `["name"]`, measured); - the bundled `ReportSchema` (`clientValidation.ts`) and the save door both accepted the result. Filed as objectstack-ai/objectui#11601: category ①, no labels, dedupe query and hit count in its body. After the spec bump, Studio's live validation and the save door both refuse such a block at `blocks.N.dataset`. - The renderers, at the pin: `DatasetReportRenderer`'s joined branch passes `String(block.dataset ?? '')` to each block's table, and that table's query hook goes idle on an empty name (`:443`). A report whose blocks all lack one fails `isDatasetReport` and falls through to the presentation bridge. `DrillDownDrawer`'s `isDatasetBoundReport` lists the records instead of drawing it. ## Fixture triage: one test-only file outside the claim's file surface `protocol.invalid-metadata-422-face-inventory.test.ts` section 5 (the joined-report `chart` door pins) left its block **unbound on purpose**: the door's author-time gate refuses an unresolvable dataset with `chart-dataset-unknown`, and the stub engine had no dataset universe. The new refusal turned 2 of its 23 tests red (`Tests 2 failed | 21 passed`): the container case got a second issue at `blocks.0.dataset`, and the CONTROL was refused at `blocks.0.dataset`. That second red is the metadata save door (`422 INVALID_METADATA`, `writeFace: 'meta-envelope'`) refusing the probe shape. - **Disposition: add the declaration.** The block binds `task_metrics`, and the harness gains an optional `makeProtocol({ datasets })`, which registers that dataset through `registry.listItems('dataset')`. Every other caller passes nothing, so the registry lists nothing, as before. Result: `Tests 23 passed (23)`. - **The registration is load-bearing.** I mutated the CONTROL to call `makeProtocol()` without datasets (`scripts/ablation-replace.mjs`, anchor 1 → 0, blob `f718099c2d` → `9dc51bd7b8`). Predicted 1 red / 22 green; observed `Tests 1 failed | 22 passed`. The failure was `chart-dataset-unknown` at `reports[0].blocks[0].dataset`, "Declared datasets: (none)". Restore: blob back to `f718099c2d`, which equals HEAD, and `git diff HEAD` is empty. - This file is outside the claim's declared file surface. It is reported in the dev report as a deviation. - Queue check: objectstack-ai#21706, queued at this writing, appends a section to the same file at line 570 and later. A local `git merge-tree` of this head with that queue head exits 0. The file is not `merge=os-regen` routed, so that local answer is also GitHub's. ## Doors, tested and probed - **Pins** (`packages/spec/src/ui/report-joined-block-dataset.test.ts`, 16 tests): - the triage probe shape is refused once per block at `blocks.0.dataset` and `blocks.1.dataset`, each naming its block; a bound block beside an unbound one draws one issue, at the unbound index; an empty name is named by position; - the new issue joins the other joined-arm refusals rather than replacing them; - taking the advice parses; - CONTROLS: a block on a non-joined report, and `JoinedReportBlockSchema` parsed on its own, both still parse; - doors: `defineReport` throws; the registered `report` type schema refuses (and accepts the bound report); `ObjectStackDefinitionSchema`, the stack parse `objectstack validate` runs, refuses at `reports.1.blocks.{0,1}.dataset`; `defineStack` answers `STACK_SCHEMA_INVALID` / 422; - preservation: the showcase `TaskOverviewReport` (mirrored byte for byte) parses with output equal to its input, and a fixture shaped like hotcrm's `customer_churn_signals` parses, gaining only the `drilldown` default; - ledger: one D3 entry at protocol 18 with no conversion; the step-18 rationale names it; no `JoinedReportBlock:dataset` tombstone, with a control on a known tombstone. - **`objectstack validate`, the real CLI** (`packages/cli/bin/run.js`, built here). Fixtures were temporary, in the session scratchpad, outside the repo; `@objectstack/spec` resolved to this worktree's build. - The triage probe stack (two blocks, each only `name` + `label`): **exit 1**, `"code": "STACK_SCHEMA_INVALID"`, `defineStack validation failed (2 issues)` at `reports.0.blocks.0.dataset` and `reports.0.blocks.1.dataset`, each with the message above. - CONTROL, the same report with both blocks bound to a declared dataset: **exit 0**, `"valid": true`. - The showcase app (`examples/app-showcase/objectstack.config.ts`, four reports including `TaskOverviewReport`): **exit 0**, `"valid": true`. - **The CLI door's verdict is this arm's, proven through `dist`.** The CLI reads `@objectstack/spec` through `dist`, so the mutation was rebuilt: - mutated leg: the `ctx.addIssue` of the new arm became a `globalThis` marker assignment (anchor 1 → 0, blob `fadd536165` → `2cbfdb9feb`); `@objectstack/spec` rebuilt; `ablation-dist-preflight.mjs` found the marker in `dist` (exit 0); the probe then gave **exit 0**, `"valid": true`. That reproduces the card's reading at `main`; - restore leg: blob back to `fadd536165`, which equals HEAD, and `git diff HEAD` is empty; rebuilt; `--absent` found the marker in none of 228 built files (exit 0); the probe gave **exit 1** `STACK_SCHEMA_INVALID` again. ## Ablation of the refusal, at the pins (predicted first) `scripts/ablation-replace.mjs` replaced the arm's `ctx.addIssue(...)` with a no-op (anchor 1 → 0, blob `fadd536165` → `45f34370c5`). The pins import `./report.zod` relatively, which resolves to `src`, so no build was needed. Predicted: 8 red, the 4 refusal pins and the 4 door pins; 8 green, the advice, the 2 controls, the 2 preservation pins and the 3 ledger pins; `report.test.ts` untouched. Observed: `Tests 8 failed | 80 passed (88)` over the pin file and `report.test.ts`. The 8 were exactly the predicted ones. Restore: blob equals HEAD (`fadd536165`), and `git diff HEAD` is empty. ## Verification (all at head `c4e3ab9631` unless noted) - `@objectstack/spec` full suite (`vitest run --project local --maxWorkers=2`): `Test Files 613 passed (613)`, `Tests 18201 passed | 1 todo`. - `pnpm --filter @objectstack/spec typecheck` (tsc, scripts, test layer: `check:test-typecheck: OK`) and `pnpm --filter @objectstack/metadata-protocol typecheck`: exit 0. `--listFiles` shows both edited test files in their programs. - Consumers that parse a joined report: `platform-objects` `report-form-echo-decisions.test.ts` 35 passed; `lint` `validate-chart-bindings.test.ts` 47 passed; `@objectstack/example-showcase` whole suite `32 files, 399 passed`; `metadata-protocol` face-inventory 23 passed. - `pnpm --filter @objectstack/spec check:generated`: one artifact stale before regeneration (`content/docs/references/**`), regenerated with `gen:docs`; then `check:generated` exit 0. - Derived gates: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 115 commands. Every one was run, with its exit code recorded before any pipe. `--ran` reconciled **115 derived, 115 run, 0 NOT-MEASURED, 0 UNRUN**. - Two first exited 3 (prerequisite not met, packages with no `dist`): `check:skill-examples` and `check:dual-build-cjs-loads`. Both were re-run green after building those packages. All 115 exited 0. - Among them: `check:adr-0087-registration`, `check:changeset-no-major`, `check:migration-registry`, `check:spec-changes`, `check:upgrade-guide`, `check:authorable-surface`, `check:api-surface`, `check:docs`, `check:doc-authoring`, `check:issue-citations`, `check:nul-bytes`, `check:cross-package-test-inputs`. - eslint, narrowed and proven: 1. population: `eslint.config.mjs`'s `**/*.{ts,…}` block covers all five changed `.ts` files; 2. `--format json`: 5 files, 0 errors, 0 warnings; 3. invariance: the config enables no type-aware linting (no `parserOptions.project`, as its own comment states), so this diff cannot move a verdict on an untouched file. The repo-wide `pnpm lint` is CI's. - Declared to CI: the path-scheduled jobs (Test Core shards, Dogfood, Temporal Conformance, Build Core), the whole-workspace type-check lanes and every downstream consumer suite of `@objectstack/spec` beyond the four above. ## Acceptance notes (observations, not filed) - `reports.mdx` says a `joined` report "must declare at least one block". That is still true, but it no longer names every requirement: each block must now also bind a `dataset`. Not edited, under the claim's rule that only a sentence this change makes false is edited. Carrier: none. - The block row in the spec's own `reportForm` (`packages/spec/src/ui/report.form.ts`, "Joined blocks" repeater) offers `dataset` with no `ref:dataset` widget and no `required`. objectstack-ai/objectui#11601 names this as a possible spec-side fix for the Studio producer, to be measured first. Carrier: objectstack-ai/objectui#11601's acting seat. - The requirement is a refinement, so the published JSON Schema does not carry it. `ui/Report` already sits in `dropped-refinements.baseline.json`, and the new arm adds no site there. The `.describe()` text is now the JSON-Schema-visible hint. - The joined arm, this check included, does not run while a block carries an unknown key or a wrong-typed member: that issue aborts the refinement. The dataset refusal then arrives after that first issue is fixed. This is existing behaviour shared by every arm of the refinement. --- _Generated by [Claude Code](https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #20749
Clause-②: no
Stage 9 of the
domain:speclane's share of the runtime-string burn-down (ruling5902360492, form D): the author-visible tracker ids in step 18's rationale inpackages/spec/src/migrations/registry.ts, per the pointer5968465093and stage 8's landing5976757373. Every cited decision is now stated in words, or the citation is dropped where its sentence already said what was decided. Text only. The test strings (class (e)) are a later stage, so this PR closes no card.What changed
STEP18_RATIONALE: 49 of its 79 fragments carried 88 tracker ids. 72 are objectstack issue numbers, 12 point into objectui (nine issues spelledobjectui#N, one issue spelledui#Ntwice, one PR), and 4 are director decision-batch numbers that the gate's id pattern also counts. Exactly those 49 fragments changed, and each now reads id-free. Fragment ids, orders and the other 30 fragments are byte-identical.decision batch #91,#77) sat inside two of the same parentheticals. The gate's pattern (three to five digits) does not count them. They went with the sentence they belonged to, and the ruling they named is stated instead.docs/protocol-upgrade-guide.mdprints majors up toPROTOCOL_MAJOR(17), andspec-changes.jsoncarries no step rationale.check:generatedreads all 15 artefacts up to date at this head.os migrate metaprints the text at runtime today, because its chain runs to the highest registered major.@objectstack/specpatch changeset,Clause-②: no. The rationale ships indist/migrations.Census at the base (A1)
Base
7d0781482d, the worktree before any edit, which is the claim's stamp. I reused stage 8's instruments byte-identically (census.cjs, md56e42a45a926d375013c32d62f16a296e;skeleton.cjs, md5a43a72c63f55c883da7a54b6f600ca08) and held the census to the same reference:check-doc-authoring.mjs --census, run from a scratch copy whosePACKAGES_PROSE_ROOT/PACKAGES_PROSE_EXCLUDEDpoint atpackages/spec/srcand at nothing. The copy is byte-identical to the one stages 7 and 8 used (md58edc9a4064f55d2766c1eb0d3e8d889f; the live gate differs from it only on those two lines). Result: 88 = 88 ids, 0 differences per (line, id).migrations/registry.tsconst step17)STEP18_RATIONALE, lines 5025–6408)surface/replacement/reason/acceptanceCriteria, generated regions)eea82af677: 46 / 85, reproduced. Between that base and this one, PR feat(spec)!: object-metric drillDown and compareTo take the shape the tile reads; object-grid columns exits its hold (#21464, stage 5) #21673 (stage 5 of spec(ui): theComponentPropsMaprows still type renderer-read members asz.unknown()—navigationon object-map / object-gantt / object-tree andconditionalFormattingon object-kanban accept42— the family close-out after #21445 #21464, landed as72f3c74d60) added three step-18 fragments, orders 71–73 (ui-object-metric-compare-to-typed,ui-object-metric-drill-down-typed,ui-object-grid-columns-typed), each citing that card once. 46 + 3 = 49 and 85 + 3 = 88. No other fragment differs between the two bases. Ancestry with a control leg on this shallow checkout:72f3c74d60is an ancestor of HEAD (exit 0) and not ofeea82af677(exit 1), while the older stage-7 landing36e4647520is an ancestor ofeea82af677(exit 0).:5066(#8681) and:5126(#118) count. After the rewrite, an id planted into a scratch copy of the new file (one literal of thetargetVariablefragment) reads 1 message / 1 id, folded over the whole fragment (lines 5515–5522).STEP18_RATIONALEregion, 96 raw#NNNhits, 8 of them on comment lines (the step-18 docblock,:6421–:6440), which read 0: 96 − 8 = 88. Past:6444, 957 raw hits sit on comment lines in the generated entry regions, and all read 0. The header docblock (:19,:32,:40,:62) and step 17's 18 comment-line hits read 0.After
At
9b8c7f38d7: step 18 reads 0 / 0, step 17 still reads 0 / 0, andpackages/spec/srcoutside test strings reads 0 messages / 0 ids. The gate reference agrees (0 sites). Test strings are unchanged at 1803 / 1919.Text-only proof
Stage 8's
skeleton.cjs, base copy against the head file: SAME — 22335 skeleton tokens, 3508 string groups, 49 changed (each id-bearing before, id-free after), diagnostics 0/0. A+chain's string run is one group, so a fragment's whole text is one group; the 49 changed groups are the 49 fragments, and every other string in the file (fragment ids, entry ids, surfaces, conversions) is byte-identical. Controls, each mutation counted on disk on a scratch copy of the head file (anchor hit 1, mutation landed):step18bindingorder43 → 44idstring renamedsurfaceedited+chainLines grew where words replaced a number: the longest new line is 111 characters, inside the region's existing maximum of 117. There is no
max-lenrule; ESLint reads 0 / 0 on the file.Per-record table
Every cited record was read through REST with all its comments (59 objectstack issues and PRs, 11 objectui issues and PRs). The four three-digit decision-batch numbers are not cards of this repository: objectstack issues 118, 121, 132 and 210 are unrelated broken-link reports and a docs PR from 2026-01, which is the control that they are batch numbers. Each batch's decision was read from where it is recorded (a ruling comment, or the spec CHANGELOG). Numbers below are written bare.
metadata-plugin-additional-types-retired(2)additionalTypes(ruling 5293094846)field-scale-precision-integer-refused(3)scale/precisionat authoring (body, ACCEPT 5296942541)scaleby rejecting, never by rounding (ruling 5250623270)scalecheck, which refuses an over-scale value rather than rounding it"admin-export-wildcard-removed(4)allowExportfrom the'*'entry of both admin sets (ruling 5299825940)member_default's'*'object grant on create / read / edit (ruling 5219845380)member_default's CRUD wildcard"record-chatter-position-vocabulary-converged(5)element-input-target-variable-retired(6)element-filter-retired(7)element:filterat element grain (verdict 5312176877)targetVariableretirement recorded"element-form-retired(8)element:format element grain (cloud reading 5350190376)element:filterretirement's own verdict sweep found itelement:filtershape … recorded by that retirement's own verdict sweep"object-*blocks' props enterComponentPropsMap, so the component-props gate judges them (ruling 5261743573)field-inline-and-related-list-columns-closed(9)inlineColumns,relatedListColumnsin the same pass (ruling 5315735776)namewins, the reader is fixed, no tolerant dual read (ruling 5236150020)nameand retired thefieldspelling with no tolerant alias"cube-metric-filters-retired(10)fieldandfilteron both doors (ACCEPT 5360094389 on 10298)filter"record-highlights-field-icon-retired(12)iconis refused, since no render path reads it (ACCEPT 5296436860)iconrefused"readonly, because the chip's gate reads it (ruling 5196402876)page-component-responsive-retired(15)widgets[].responsive(ruling 5169512655)object-grid-default-sort-retired(17)table.defaultSorton its own card (ruling 5377367456 on 4869)table.defaultSortretirement, which the maintainer's 2026-08-22 「接受所有」 ruling on objectui's sort sink ordered"permission-restore-purge-bits-retired(18)owner_idis server-managed for non-privileged writers (body);allowTransferenforced through that write guard (1883 ruling 5163046733)field-reference-to-spelling-retired(21)compliance-deadline-keys-retired(23)duration-keys-unit-in-key(24)metadata-manager-config-inert-cache-keys-retired(25)ttlitsttlSecondsspellingttl"cron-positions-deleted(26)list-view-page-mount-retired(27)object-kanban-quick-add-retired(28)list-view-sort-string-clause-retired(29)convertSortToQueryParamsrefuses a runtime string; merged 2026-09-09, before the spec halfchart-config-aria-retired(31)2bf6ef1)dashboard-widget-chart-config-structure-refused(32)chartConfigappearance, and the structural keys are refused by name (ruling 5644017639 on card 17385)translation-per-app-settings-platform-only(33)settings(ruling 5653315643)settingsleaves the item door too, one app metadata type, one shape (ruling 5770445203)object-tenancy-organization-field-retired(34)page-component-filter-record-to-rule-array(36)view-item-owner-hidden-retired(37)ui-report-joined-chart-retired(38)view-overlay-owner-hidden-retired(39)ui-form-layout-inline-grid-retired(40)inline/grid(triage 5855767378)columnscolumns)"currency-config-precision-retired(41)permission-rls-tags-retired(42)flow-decision-edge-branching-first-match(45)ui-object-master-detail-form-details-closed(56)ui-record-line-items-props-closed(57)ui-object-grid-export-options-closed(59)translation-widget-sub-caption-retired(60)widget.description(ruling 5200254075, 2026-08-06), which C reversesobject-grid-resizable-columns-retired(62)resizableis canonical, and the alias retires with no window (seat answer 5957903936)resizableis canonical"ui-object-grid-row-members-typed(63)ui-object-map-gantt-tree-navigation-typed(64), list members (66), form members (67), metric aggregate / trend (69), compare-to (71), drill-down (72), grid columns (73)ui-ai-chat-window-retired(65)element-text-variant-heading-subheading-retired(68)object-master-detail-form-detail-sort-field-retired(70)Readers
'decision','objectui',/100/), plus the tracker-id detectors, which are absence checks that now pass. No pin needed moving.ui/component.test.tspins the phrase "retiresobject-kanban'squickAdd" and pins that the rationale never sayskanban-ui. That second pin shaped the rewrite: objectui's ruling kept the control onkanban-ui, a node type objectui has since retired, so the new text states the ruling without naming it.system/compliance-families-retirement.test.tspins/retires those three compliance-shaped families WHOLE/, kept verbatim.data/cube-metric-expression-types-retirement.test.tsreads a fragment this stage does not touch.scripts/step18-rationale-merge.test.tspins the fragments' sort and that the rationale equals their joined text. Neither moves.Tests and gates (at
9b8c7f38d7)All builds and tests ran through
scripts/pm/os-verify-lock.sh, with each exit code written to a file before reading.pnpm --filter @objectstack/spec build: exit 0, 38/38 dts.pnpm --filter @objectstack/spec check:generated: exit 0, all 15 artefacts up to date.migrations.test.ts,component.test.tsand the cube-metric test, 561 / 561. Repo project:step18-rationale-merge.test.tsand the compliance test, 19 / 19. The other 19 repo-project tests that import the registry: 314 / 314.conversions-major18-merge.test.ts: 12 / 12.pnpm --filter @objectstack/spec run typecheck: exit 0.dispatch-gates --ran(84 run, 0 NOT-MEASURED, 0 UNRUN). Three gates first answered PREREQUISITE NOT MET (exit 3, nothing measured) before the 71-package build, and passed after it.registry.ts, a proven narrowing: 1 file, 0 errors, 0 warnings. The population comes from ESLint's own config (calculateConfigForFilereturns a config,isPathIgnoredis false). Invariance:parserOptions.projectandprojectServiceare null, so no type-aware rule exists and an untouched file's verdict cannot move.dist: the new phrases are indist/migrations/index.jsandindex.mjs; the old runtime phrases probed are in nodist/migrationsfile. The ids still present indistare code comments, which this stage does not touch.scripts/build-schemas-check-mode.test.ts, which reads semanticsurfacestrings and never the rationale. Both are CI's, as is the repo-widepnpm lint.Overlap
#21654's PR #21687 (draft, this seat) adds one
STEP18_RATIONALEfragment (order 74, no tracker id in its text) and one D3 entry. A localgit merge-treeof this head with its heada9d2d5d453is clean, andregistry.tshas no merge driver, so that result is the plain text merge GitHub runs. Whichever lands later mergesorigin/mainthroughscripts/pm/os-regen-merge.sh.origin/mainwas re-fetched just before opening this PR (38bef8cf95): the four commits since the base touch neither this file, the conversions registry nor the guide, and a localgit merge-treewith it is clean.Acceptance notes
registry.ts's code comments still carry many ids (the step-18 docblock and the generated entries' comments), and so doesdist, which keeps comments. They are the comment lane's share.decision-inbox batch 4andbatch 5name a batch without a#number and are not ids. Present-tense clauses whose follow-through has since landed (for example "held up today only by objectui's fallback arms") were left as written: form D asks for the decision, and the decision is stated.packages/spec/liveness/dashboard.json,page.jsonandview.jsoncite some of the same decision batches in their notes. They are not runtime strings and sit outside this census; nothing here touched them.Clause-② and changeset
Clause-②: no. Nothing authorable, exported or typed is added, removed or renamed; only the value ofMIGRATIONS_BY_MAJOR[18].rationalechanges. It ships indistandos migrate metaprints it, so one@objectstack/specpatch changeset is owed, andskip-changesetdoes not apply.Generated by Claude Code