Repository navigation
fix(spec): type object-grid and object-kanban grouping by GroupingConfigSchema - #20856
Conversation
…figSchema Both ComponentPropsMap rows declared `grouping: z.unknown()`, so a padded grouping field name or a wrong-shaped value validated green on these doors while the same value is refused on list-view. Both renderers read the GroupingConfigSchema shape, so the rows now take that schema by reference. Claude-Session: https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6 Co-authored-by: Claude <noreply@anthropic.com>
…grid and object-kanban rows Claude-Session: https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6 Co-authored-by: Claude <noreply@anthropic.com>
… rows Claude-Session: https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6 Co-authored-by: Claude <noreply@anthropic.com>
…87 semantic entry Claude-Session: https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6 Co-authored-by: Claude <noreply@anthropic.com>
…, breaking) Claude-Session: https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6 Co-authored-by: Claude <noreply@anthropic.com>
…mponent-grouping-typed
📓 Docs Drift CheckThis PR changes 1 package(s): 6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 137 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 8b7f00e27eb04a3016a2c31d06d84e2724ee66d1 && git checkout 8b7f00e27eb04a3016a2c31d06d84e2724ee66d1
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 40d6c5fbd39db70f0e0885c046a11bef37baa0d9 47c76064d73aa96d19046b36f4d1e9cddc63b29b && git checkout -B drift-repro 40d6c5fbd39db70f0e0885c046a11bef37baa0d9 && git merge --no-ff 47c76064d73aa96d19046b36f4d1e9cddc63b29b
node scripts/docs-audit/affected-docs.mjs --json 40d6c5fbd39db70f0e0885c046a11bef37baa0d9
|
Contract reviewServed-tier: Read-only shape held. Inputs: card #20831 (body and all three comments — triage 5908917971, claim 5910072345, os-dev-report 5911558951), PR #20856 (body, the 7-file list, Check-runs on ① Derived judgmentsEvery accept-set and public-surface change the diff implies, each named and judged:
Residual, not a defect of this diff: the section header at ② Semver level
③ Boundary flagsDev-declared deviations (6):
Open question — Clause-② value (A: keep Out-of-scope findings (4, dev-noted, carrier none or the mirror card):
No governed surface is touched (7 files under Implemented-by: VERDICT: PASS |
…that decided it (objectstack-ai#20866) Part of objectstack-ai#20596 Clause-②: no ## What changed This is the seventeenth stage of the `domain:services` lane of the dead-citation sweep. It covers `packages/services/service-job/src/**` and nothing else. By the seat's claim (`5911377566`), it is the next of the two single-site packages left in the lane, taken one package per stage. Later stages cover the other packages, so this PR says `Part of` and the card stays open. Every comment or docblock site in scope that cited a tracker number answering 404 has been rewritten in ruling C+D's form C (comment 5749154545 on objectstack-ai#19123), by the method of stages 1 to 16 (the latest is PR objectstack-ai#20850, landed as `40d6c5fbd`, the base here). That is **2 sites on 2 lines in 2 files, covering 1 number**: - `src/never-fires.fixture.ts:30`, the census's one site in this package at the base (a docblock); - `src/db-job-adapter.timeout.test.ts:55`, a test-comment site the census defers, found by the supplementary reading below and rewritten too, as stages 1 to 15 did. No test string, source string or gate-invisible spelling in this package carries a dead number, so these two sites are the whole population. Both rewritten lines now cite the commit in this repository that decided what they describe: **1 commit sha**. No ADR or ruling record records that decision (see the per-number table), so ruling C's commit rung applies. No number was dropped. Only comments changed. Each file keeps its line count (2 lines out, 2 in), so no line citation into either file moves. No code token moves (see the guard below). **No citation number is added**: the only tracker number on the added lines is the live `objectstack-ai#8628`, which already stood on its line. **No changeset.** Neither rewritten line reaches `dist`, and `dist` is byte-identical with and without the rewrite (see Changeset below), so this PR takes `skip-changeset`. ## Census: `service-job`, before and after **Instrument (A1).** The gate's own `node scripts/check-issue-citations.mjs --census --json`, read-only and unchanged. The count below is its `allocated-but-absent` findings under `packages/services/service-job/`. Each run counts as a reading only because its board frontier equals the newest issue or pull-request number, read by a separate request just before and just after the run. | reading | tree | board | whole-repo `allocated-but-absent` | service-job sites | lines | files | numbers | |---|---|---|---|---|---|---|---| | before | base `40d6c5fbd`, run 2026-09-30T12:36:32Z to 12:40:56Z | enumerated, 187 pages, frontier objectstack-ai#20855 (newest objectstack-ai#20854 before; objectstack-ai#20855 after, an issue opened 12:40:06Z inside the run, so the frontier equals the newest at the run's end) | 745 | **1** | 1 | 1 | 1 | | after | head `6ac866f86`, run 12:46:10Z to 12:49:55Z | enumerated, 187 pages, frontier objectstack-ai#20856 (newest objectstack-ai#20856 before and after) | 744 | **0** | 0 | 0 | 0 | The whole-repo drop is 1, and the finding sets of the two runs differ by exactly one row, `never-fires.fixture.ts:30 objectstack-ai#8748`, removed; none was added. The `resolves` tally is 33,221 in both runs, and `resolves-as-pull-request` (1,985) and `cross-repo-unjudged` (1,018) did not move either. Neither run was truncated or discarded: both enumerations read 187 pages at the newest frontier. The seat's census counted 1 here at `6bff748b`, and the base agrees: `6bff748b` is an ancestor of the base, and no commit between them touches this package's `src`. **Supplementary instrument, the whole scope.** The census does not read test files or strings. So a second reading runs the gate's own exported `extractCitations` (whole-file and comment-prose projections) and `namesThisRepository` over every `.ts` file under `service-job/src` (18 files). It takes its verdicts from the before census's own board reading rather than from a second enumeration: a number is dead when that census reported it `allocated-but-absent`, and alive when the gate's own census-scope extraction (`collectCitations`) judged it and the census did not report it. 4 numbers are covered by neither, because they stand only in test files. Each was read on its own through the read-only tools, and each answers as an issue: `objectstack-ai#8628`, `objectstack-ai#9611`, `objectstack-ai#9631` and `objectstack-ai#9633`. The probe's control: `objectstack-ai#8748` answers 404 on the issues endpoint and on the pull-request endpoint. | reading | citations | dead | src comment | test comment | src string | test string | |---|---|---|---|---|---|---| | before, `40d6c5fbd` | 95 | **2** | 1 | 1 | 0 | 0 | | after, `6ac866f86` | 93 | **0** | 0 | 0 | 0 | 0 | Its src-comment column equals the census's 1, which is the control on the second instrument. The 93 live citations are the same in both readings (81 by the census's judgement, 12 by the single reads), none is cross-repo, none is unjudged, and the drop of 2 citations is exactly the two rewritten sites. A third, raw reading (every `#` followed by 2 to 6 digits, whatever surrounds it) finds 95 occurrences before and 93 after, the same drop of 2, and no token beyond the gate's grammar. ## Per-number table Sites and files count every dead occurrence in scope at the base (comments and strings, tests included). | number | sites / files | rewritten / left | anchor: what it decided | |---|---|---|---| | `objectstack-ai#8748` | 2/2 | 2/0 | `226cb71d5` (PR objectstack-ai#8774): every exact-count case in this package moves onto the inert `NEVER_FIRES` cron fixture (`'0 0 30 2 *'`, February 30th, which never occurs), and the fixture moves out of the timeout suite into the shared `never-fires.fixture.ts`. Its message records the measurement `:30` describes: only `Date` faked, the registration placed 2/5/10/15 ms before the expression's instant, one extra handler run per case under the firing spellings, and two of the three no longer reproducing at a 2 ms lead. That is what `:30` says, and what `:55`'s 「now shared with the sibling job suites rather than spelled a second time here」 says. Its subject names `objectstack-ai#8748`, and its own diff names it on 2 added lines; `git blame` puts both rewritten lines in this commit | The sha matches exactly one commit (`git rev-parse --disambiguate`, count 1) and is an ancestor of the base (`merge-base --is-ancestor`, exit 0; reverse leg, base against the anchor, exit 1; control legs exit 0: `7246d631d`, 5,451 commits behind the base, and the repository's root commit `1598cabe4`, 15,209 behind, against the anchor's 5,436; the history is complete, `--is-shallow-repository` false, 15,210 commits at the base). `objectstack-ai#8748` answers 404 on the issues endpoint and on the pull-request endpoint, read one by one. No ADR, `scripts/adr-anchors/` file or ruling record names `objectstack-ai#8748`, PR objectstack-ai#8774, `NEVER_FIRES`, `never-fires`, February 30th or an inert cron (`git grep` over `docs/adr` and `scripts/adr-anchors`: 0 hits), so ruling C's first rung is empty. The repository's other `objectstack-ai#8748` tokens are `objectui#8748`, a cross-repo reference the gate never judges. ## Wordings to check - **`never-fires.fixture.ts:30`, tag swap in place.** 「Measured (objectstack-ai#8748) by faking ONLY `Date`」 became 「Measured (commit 226cb71) by faking ONLY `Date`」. The form is the one `packages/lint/src/lint-flow-patterns.ts:1468` already carries on `main` (「Measured (commit 8ed9c54)」). - **`db-job-adapter.timeout.test.ts:55`, tag swap in place.** 「(objectstack-ai#8628, objectstack-ai#8748)」 became 「(objectstack-ai#8628, commit 226cb71)」. The live `objectstack-ai#8628` stays: it is the report of the wall-clock flake, whose own change, `7246d631d` (PR objectstack-ai#8750), first made this suite's cron unable to self-fire. The form is the one stages 7 and 9 landed (for example `(objectstack-ai#5928, commit 69787f0)` in `plugin-audit`). - Neither line was reflowed, so each is 11 characters longer than before (70 to 81, and 72 to 83), a little past its block's wrap. `eslint.config.mjs` declares no line-length rule, and reflowing would have moved neighbouring lines. ## Sites left - **In `src`: none.** No test title, assertion message, operator log string, runtime refusal, quoted maintainer ruling or generated file in this package carries a dead number. - **Outside `src`, listed and left, not edited in this stage:** - The release-owned `CHANGELOG.md` names the dead `objectstack-ai#13112` on 1 line (`:705`; 404 on the issue and pull-request endpoints). Its other 27 numbers are live: 19 by the census's judgement, and by single reads `objectstack-ai#18715`, `objectstack-ai#14619`, `objectstack-ai#9517`, `objectstack-ai#1893`, `objectstack-ai#9611`, `objectstack-ai#9631` and `objectstack-ai#9633` as issues and `objectstack-ai#9546` as a pull request. - `vitest.config.ts` names the live `objectstack-ai#10374`. - `README.md`, `package.json` and `tsconfig.json` name none. ## Mechanical guard: no code token moves The guard compares, base `40d6c5fbd` against head, over both touched `.ts` files: - **Reading 1**, the TypeScript parser's leaf nodes (a `forEachChild` walk, so comments are trivia and JSDoc nodes are never visited). String and template literals are therefore read in full. - **Reading 2**, the full token stream in parser context (a `getChildren` walk, so punctuation and keywords are included; JSDoc nodes skipped). Results: - Real run at the head `6ac866f86`: 755 base leaf tokens, **0 files with a token change** on either reading (exit 0). - Comment control (「croner resolves Feb 29 forward」 to 「croner rolls Feb 29 forward」): 0 files changed, as expected (exit 0). - Positive control, a code identifier renamed (`expression: NEVER_FIRES }` to `expression: NEVER_FIRESX }`): DIFFER on the identifier in both readings (exit 1). - Positive control, a string literal changed (`'0 0 30 2 *'` to `'0 0 31 2 *'`): DIFFER on the string literal in both readings (exit 1). - Positive control in the second file, a numeric literal changed (`TIMEOUT_MS = 20` to `21`): DIFFER on the literal in both readings (exit 1). Every mutation went through `scripts/ablation-replace.mjs` (wrap mode) under a shell trap that restores by absolute path, and each landed (anchor 1 to 0, blob changed). Each restore was proven byte-identical to the HEAD blobs (`1fd5e1b3f612`, `ce37b1050c78`), with `git diff HEAD` empty and a clean tree afterwards. ## Changeset: none (`skip-changeset`) `files[]` is `dist`, `README.md` and `CHANGELOG.md`, and the package is not private. `never-fires.fixture.ts` is a test-only support module (nothing in `src/index.ts` re-exports it, and the tsup entry is `src/index.ts` alone), and the other touched file is a test. Measured on the built package (A3), after `pnpm --filter '@objectstack/service-job...' build` at `6ac866f86`, where the package's own `tsup` ran and cleaned its output folder: - Neither rewritten line appears in any `dist` file (0 in all 6, source maps included). Nor do the old number, `objectstack-ai#8628`, the anchor sha, the neighbouring lines `:28`, `:31`, `:54` and `:56`, `NEVER_FIRES`, the `'0 0 30 2 *'` expression, or a never-written negative phrase. - Positive controls, text that does ship: `CronJobAdapter` (45 hits), `DbJobAdapter` (73) and `sys_job_run` (52) across the entries, and two docblock lines of the `DbJobAdapter` class JSDoc in `db-job-adapter.ts` (「`{@link DbJobAdapterOptions.recordRuns}` is `false`, the on/off switch for」 and the `recordRuns` gating line below it), each in `index.d.ts` and `index.d.cts`. - **Byte identity.** The package was rebuilt with the base text put back in both files (through nested `scripts/ablation-replace.mjs` legs, with each file proven equal to its base blob, `919a2e444f23` and `eb00f6eb1978`, during the leg), and again after the proven restore. The sha256 of all 6 `dist` files is identical in all three builds: JS entries, declaration files and source maps. So nothing this PR rewrites is published, and a changeset would announce a change no consumer can observe. No `.changeset/*` file is added or touched. ## Gates (head `6ac866f86`) - **Citation judging, as CI runs it:** `node scripts/check-issue-citations.mjs` exits 0 (「no issue citations added against 40d6c5f」). `pnpm check:issue-citations` exits 0 (self-test, 114 cases, 8 batteries). - **Doc authoring:** `pnpm check:doc-authoring` exits 0 (the sibling-package prose-id baseline holds, no growth). - **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at `6ac866f86` (change set derived from git: 2 paths against the merge base `40d6c5fbd`) derived 54 commands, all among the dispatch list's 63. - All 63 of the dispatch list ran, each with its exit code captured before any pipe, and all 63 exit 0; none exited 3. - `--ran`, fed each command with its exit code, reports 54 derived, 54 run, 0 NOT MEASURED (a derived zero), 0 unrun, and exits 0. Of the other 9, it classifies 7 as pending-changeset families and 2 as outside this derivation. - A full `turbo run build` over `./packages/*` and `./packages/*/*` ran first under the shared verify lock (71 of 71 tasks), so no gate hit an unbuilt workspace. - **Roster families the derivation lists outside its commands** (their rosters sit in a directory this diff touches): `pnpm check:authz-resolver`, `pnpm check:error-code-casing` and `pnpm check:filter-alias-parity`, plus `node scripts/check-changeset-fixed.mjs`, which the dispatch named. Each exits 0. - **Tests and typecheck, under the verify lock, at `6ac866f86`:** - `pnpm --filter @objectstack/service-job test`: 11 files pass and 117 tests pass, which is every tracked test file under `src/`. - `pnpm --filter @objectstack/service-job typecheck` exits 0, and `tsc --noEmit --listFiles` puts both touched files in the program (the config includes `src` and excludes no test file). - **Lint, as a proven narrowing:** eslint with inline config disabled, over the 2 touched `.ts` files, gives 2 files, 0 errors and 0 warnings (its `--format json` output). Both files are in eslint's own population (not reported ignored; `dist/index.js`, the control, reads ignored). `eslint.config.mjs` never enables type-aware linting (no `parserOptions.project`, as its own lines 327-328 state), so a comment edit here cannot move the verdict on any untouched file. The repo-wide `pnpm lint` is CI's run. - **Control bytes:** `pnpm check:nul-bytes` exits 0, and a raw scan of the changed files for control bytes finds none. ## Acceptance notes - **The gate-invisible spellings, grepped as the claim asked** (objectstack-ai#20636, including the `clause #N` position). At the base and at the head, `#N-word`, `#A/#B`, `option #N`, `clause #N` and URL-spelled links are each on 0 lines, so the claim's 0 / 0 / 0 / 0 / 0 hold. The one prefix-joined token, `pre-objectstack-ai#6617` in a test title of `db-job-adapter.degraded-outcome.test.ts`, is inside the gate's grammar and names a live number. - **Wording only:** 「the card」 / 「this card」 stands on 4 comment lines in 3 files under `service-job/src`. It carries no number, neither instrument sees it, and this diff removes no antecedent. - **Outside-`src` residue.** `CHANGELOG.md:705` names the dead `objectstack-ai#13112`; the file is release-owned and is left. - **The census instrument did not truncate in this stage.** Both enumerations read 187 pages at the newest frontier. - **Base.** The branch is on `main` at `40d6c5fbd`. `main` has since moved two commits (to `660a9b247`). Neither touches `packages/services/service-job`, `scripts/check-issue-citations.mjs`, `scripts/pm/dispatch-gates.mjs` or `.changeset/config.json`, so no merge was taken; the merge queue rebuilds on the merged generation. The one derivation input that moved is `scripts/doc-authoring-prose-id.baseline.json`, where the second commit removed only `packages/objectql` entries; this package has no entry there before or after, so the merged tree cannot move that gate's verdict on this diff. --- _Generated by [Claude Code](https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H)_ Co-authored-by: Claude <noreply@anthropic.com>
Fixes #20831
Clause-②: yes
What this changes
ComponentPropsMap['object-grid'].groupingandComponentPropsMap['object-kanban'].groupingwerez.unknown().optional(). Both now take the list view's ownGroupingConfigSchemaby reference, imported from./view.zodbeside the six schemascomponent.zod.tsalready takes from there. There is one schema, not a copy of its shape, and each row keeps its describe. A padded grouping field name, a number, a bare string, an emptyfieldslist or an undeclared key inside the config is now refused on these two doors exactly aslist-viewrefuses it.The prop is typed, not retired. Both renderers are live readers of this exact shape. Readings are at objectui
db11afd49, the pin in.objectui-sha:ObjectGridSchema.groupingis typed as the spec'sGroupingConfig(packages/types/src/objectql.ts:1123).ObjectGrid.tsxreadsschema.grouping?.fields(:2050,:2839), anduseGroupedData.tsreads.field,.orderand.collapsedper entry (:281,:443-547).effectiveSwimlaneField = schema.swimlaneField || schema.grouping?.fields?.[0]?.field(ObjectKanban.tsx:1212).grouping.member across objectuipackages/*/src(tests excluded) at the pin findsfieldsand nothing else.Files (7, +338 / -7 against
origin/main688ddef3c)packages/spec/src/ui/component.zod.tspackages/spec/src/ui/component-object-block-grouping.pin.test.tspackages/spec/src/migrations/entries/semantic/18.ui-object-block-grouping-config-typed.ts+ regeneratedregistry.tscontent/docs/references/ui/component.mdxgen:docs:groupingnow shows its shape on both rowspackages/spec/dropped-refinements.baseline.json.changeset/20831-object-block-grouping-typed.mdminor, BREAKING banner, migration table,Clause-②: yes (narrowing)and the registered ADR-0087 markerFile-surface breach, declared. The spec build refuses the schema change until
dropped-refinements.baseline.jsonrecords two new sites,grouping.fields.element.fieldonui/ObjectGridPropsand onui/ObjectKanbanProps, and movesdroppedRefinementSites610 to 612. The build prints the corrected entry itself. The cause is the non-padded field-namesuperRefinethatGroupingFieldSchemacarries. JSON Schema cannot express it, and the ledger already records it at every list-view grouping site (ui/GroupingConfig,ui/GroupingField,ui/ListView, the manifest views). The ledger is hand-edited by design, so a new site is a reviewed line. The alternatives are both out: deleting the refinement is forbidden by the ledger's own rule, and teaching the projection list a new named pattern is a public-contract decision of its own. The ledger'srefinementSitesThatDidProject(369) is left alone. This build prints 421, but none of the grouping refinements is one of the five projected patterns, so this diff adds zero to it.Pins
The pins are in
component-object-block-grouping.pin.test.ts: 22 cases, each asserting issuecodeandpath, on both rows.customatgrouping.fields.0.field, and the message names the received value and the unpadded spelling;grouping: 42:invalid_typeatgrouping. There is nofields[0]for a number to fail at, so the triage'sgrouping.fields[0].fieldpath applies to the padded case only;invalid_typeatgrouping;{ fields: [] }:too_smallatgrouping.fields;unrecognized_keysatgrouping.toStrictEqualon the input);{ field }parses clean and gains the list view's defaults (order: 'asc',collapsed: false) on the parse output. The component-props gate discards that output and the envelope audit walks raw keys only, so nothing downstream sees them;groupingstays absent.GroupingConfigSchema._zod.defby identity, and answers whatGroupingConfigSchemaanswers on the whole corpus, one path level down.Ablation, run at
69a1be23f(thecomponent.zod.tsblob39065a62is unchanged at HEAD): both members were reverted toz.unknown()throughscripts/ablation-replace.mjs(anchor 2 to 0,--expect 2, trap restore). Result: 18 failed / 4 passed. The 4 that stay green are the byte-identical and absent controls, whichz.unknown()also passes. The restore was proven by blob equals HEAD and an emptygit diff HEAD. Nodist/was involved: the test imports./component.zodfrom source.ADR-0087 disposition:
registered ui-object-block-grouping-config-typedA new semantic entry for protocol 18 carries the rewrite table:
{ fields: [{ field }] };object-kanban,swimlaneFieldstill wins overgrouping.It is deliberately not a D2 conversion: a padded name is refused with guidance, never trimmed. The entry states what happens at rest, and it was measured:
PageComponentSchemadoes not parsepropertiesagainstComponentPropsMap.tier: 'advisory',surfaces: CLI_ONLY(packages/lint/src/authoring-rules.ts:1022).component-props-invalidonos validate/os build/os lint.validateComponentPropson the rebuilt spec: a padded kanban grouping reports at…properties.grouping.fields.0.field, and42on a grid at…properties.grouping. The valid control reports nothing.node scripts/check-adr-0087-registration.mjs --base origin/mainanswers exit 0, reporting[BREAKING+clause-②-narrowing] registered ui-object-block-grouping-config-typed (new here …).Dispatch measurements
z.unknown()at base1741c5dcb(:3596,:3993).view.zod.tsimports nothing fromcomponent.zod.ts, andcomponent.zod.tsalready takes six lazy schemas from it. The identity pin is green under the lazy proxy.GroupingConfigSchemashape (readings above), so both rows are typed. There is noneeds_decisionfork.registered, judged by the gate (above).688ddef3c:examples/**has 2object-gridnodes (app-showcasecommand-center.page.ts:172,my-work.page.ts:62). Neither authorsgrouping, and there are 0object-kanbannodes.packages/**authorsgroupingon either row.@objectstack/lint(validate-component-props.ts,page-envelope-audit.ts), downstream of spec (the...@objectstack/specdirection). Its suite is green (below).z.unknown()acceptance on these rows, and the padded-name refusal text is unchanged, so no pin flips.Verification, at HEAD
47c76064d(after mergingorigin/mainthroughos-regen-merge.sh; no conflicts)pnpm --filter @objectstack/spec build: exit 0; 612 dropped sites across 212 schemas, all declared.pnpm --filter @objectstack/spec check:generated --fix: "All 15 generated artifacts are up to date", measured against a dist built by that run. authorable-surface, authorable-defaults and api-surface are unchanged.pnpm --filter @objectstack/spec typecheck: exit 0. The test layer compiles with debt unchanged (53 files / 251 errors / 138 signatures).vitest run --project local --maxWorkers=2): 579 files, 17088 passed, 1 todo.@objectstack/lintsuite: 117 files, 5436 passed, against a spec dist rebuilt from this tree.node scripts/pm/dispatch-gates.mjs --commands(no paths): 112 commands.--ranreconciles 112 derived, 110 run (all exit 0), 2 NOT MEASURED, 0 UNRUN.check:doc-formula-expressions,check:doc-security-posture,check:skill-examples,check:docs-transcript-drift,check:lean-entry-closure. Each was rerun green after building lint, client, client-react and objectql.check-changeset-no-majorwith this body as the event: LEVEL AXIS readsClause-②: yes, and no package is gradedpatch.pnpm lintis CI's):--no-inline-config --format jsonover the 4 changed TS files reports 4 files, 0 errors, 0 warnings;eslint.config.mjsputs everypackages/**/*.tsin the population, and the other 3 changed files are md, mdx and json, outside its globs;parserOptions.project), so this diff cannot move a verdict on an untouched file.NOT MEASURED locally:
check:dual-build-cjs-loadsandcheck:type-check-debt, reason: both need every workspace package built (84 have nodist/).integrationtier, reason: declared for CI; this diff touches no CLI file. Itsmigrate-meta-engine-guidance.test.tsprints every semantic entry. I checked the new entry against its tracker-id regex (no hit), but not its verbatim print.Acceptance notes
These were noted, not filed. Carrier: none.
ui-object-grid-page-size-positive-integer-refusedsays a stored page "is refused on its next authoring-path save". Its siblingobject-grid-default-filters-rule-array, and the rule'ssurfaces: CLI_ONLY, say the refusal is an advisory CLI finding only. This is a read-only reading: I did not exercise a save.component.zod.tssection header still lists "filter shapes … grouping configs" as thez.unknown()value posture. Both have since been typed. This is historical text, left as it stands.dropped-refinements.baseline.jsonmeasured.refinementSitesThatDidProjectreads 369 while the build prints 421. The gap predates this diff and is ungated.object-kanbanregistration description (plugin-kanban/src/index.tsx:684) andstructuredKeysAreDeclaredAndHonoured-8313.test.tsboth describe the spec row asz.unknown(). Its row-4 fixture{ fields: [{ field: 'owner' }] }stays valid under the typed row.Concurrent work: #20785 edits
component.zod.tsonly atPageHeaderPropsand onecomponent.mdxline in that section, disjoint from these regions; this diff touches neitherauthorable-surface/ui.jsonnorauthorable-defaults/ui.json. #20694 is not addressed here; it runs after this lands.Generated by Claude Code