Repository navigation
feat(spec): enrol RestApiConfigSchema in the liveness ledger as rest_api - #19594
Merged
Merged
Conversation
…_api` The fifth `RestServerConfig` sub-object was left out of #14369's enrolment because the `api` block's consumption seam was then still validate-only, so a census would have recorded a half that was about to move. That half has moved: `RestServer.normalizeConfig` now builds the `api` block from `parseDeclaredApiConfig`'s output, and the change is released. The fence has expired, so the sub-object is measured on the settled seam and enrolled. 12 live / 14 dead over 26 classified properties. The dead set is the `requireAuth` tombstone plus the two declared containers `documentation` (ten, drilled through its nested `contact` / `license`) and `responseFormat` (three) — normalized into `this.config.api` and read back by nothing. The ledger is `rest_api.json`, never `api.json`: that name is already taken by `ApiEndpointSchema`, the registered `api` metadata type. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UDXER3sdqfeVYpEWZs5mZx
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UDXER3sdqfeVYpEWZs5mZx
…rage ratchet
Enrolling a liveness ledger grows the coverage ratchet's universe, so the
capability must be tested or waived. Mapped, not waived: the two items named
do exercise this sub-object on the DEFAULT config — route-ledger-live-parity
fires a representative route per family, and qa-suite-execution derives its
prefix from `apiPath ?? {basePath}/{version}` rather than from a literal.
The entry states plainly what those two do NOT cover: the non-default variants
that the three sibling `rest-*-config-contract` items drive in a scratch
harness. A dedicated `api-backend.rest-api-config-contract` item is the gap,
and it is authoring work with its own measurement.
Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UDXER3sdqfeVYpEWZs5mZx
…pi` ledger The cross-repo hole's lit control was recorded as `basePath` = 176. Re-measured on the two trees the note itself names -- the pinned sha 87af769e and objectui head 98178b20 -- `git grep -n -F basePath` returns 181 and 182 lines. The zero it controls is unchanged: RestApiConfig / RestServerConfig / responseFormat / includeMetadata / includePagination / termsOfService all return 0 at both. A cited count that does not reproduce on the tree it names is not a reading, so the number is corrected rather than the tree re-chosen. Claude-Session: https://claude.ai/code/session_01UDXER3sdqfeVYpEWZs5mZx Co-authored-by: Claude <noreply@anthropic.com>
Contributor
📓 Docs Drift Check
What this run could not see
Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
os-warren
marked this pull request as ready for review
September 21, 2026 13:29
os-warren
enabled auto-merge
September 21, 2026 13:29
This was referenced Sep 21, 2026
This was referenced Sep 22, 2026
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 28, 2026
… real home (objectstack-ai#19659) Fixes objectstack-ai#19269 Clause-②: no `packages/spec/liveness/README.md` told the reader that the lint which emits author warnings lives at `packages/cli/src/utils/lint-liveness-properties.ts`. That path does not exist. One line, repointed at the module's real home. ## Measured in this worktree, not carried from the card Base `5ce3705`. The card's line numbers were re-derived **by text**, because the file was touched by objectstack-ai#19594 after the card was written: ``` probe : packages/cli/src/utils/lint-liveness-properties.ts ls: cannot access ...: No such file or directory LIT CONTROL (same listing, same corpus): packages/lint/src/lint-liveness-properties.ts 35926 bytes packages/lint/src/lint-liveness-properties.test.ts 83518 bytes ``` Both pointers still sit where the card and the dispatch put them — `:548` (the stale full path) and `:948` (a bare filename) — so objectstack-ai#19594 moved neither. ## Why `packages/lint/src/lint-liveness-properties.ts` is the right referent A path that exists is not automatically the one the sentence means. Four readings say this one is: - The module's own header describes exactly what `:548` describes: it reads the `liveness/*.json` ledgers, opts in per entry via `authorWarn`, emits an advisory **warning** with a hint, and "NEVER fails the build". - The sentence at `:948` names `TYPE_COLLECTIONS`; that constant is declared in this module, at `:485`. - The rule registration carries the answer **as data**: `packages/lint/src/authoring-rules.ts` registers `{ name: 'lintLivenessProperties', tier: 'advisory', source: 'packages/lint/src/lint-liveness-properties.ts' }`. The repointed prose now agrees with a machine-readable field rather than with a second piece of prose. - The adjacent words "The CLI `compile` lint" stay true after the repoint: `packages/cli/src/commands/compile.ts` imports `runAuthoringRules` / `authoringRulesFor` from `@objectstack/lint` and the rule declares `commands: ALL`, so `os compile` is still a door this lint runs behind. Nothing else in the sentence changed, and no ledger entry, verdict, schema, export or runtime behaviour moved. ## Why the second pointer is deliberately NOT changed `:948` reads "see lint-liveness-properties.ts" — a bare filename with no directory. Leaving it is a decision, not an oversight: - **It is not stale.** The basename resolves uniquely in the tree today (the module plus its `.test.ts`), so a reader who follows it lands on the module. - **A bare filename has no directory to rot.** Giving it one would convert a rot-proof pointer into a rot-prone one — manufacturing the exact defect class this card exists to close, for a cosmetic gain. - **The cost is asymmetric.** A wrong full path actively misleads (the reader lands on nothing and may conclude the lint does not exist); a bare filename costs one `git grep`. The durable form wins where the defect class is this one. - The full path is stated once, at `:548`, which is where a reader who needs the directory gets it. ## Changeset: `patch`, measured rather than assumed `liveness/` is in `@objectstack/spec`'s `files[]`, and the package is published (not `private`, currently `17.4.0`), so this README ships inside the npm tarball. Measured with `npm pack --dry-run --json` in `packages/spec`: - 277 files in the tarball; `liveness/README.md` **present**; 42 `liveness/*` entries - negative controls on the same listing: no `scripts/` entry, no `*.test.ts` entry So the edited bytes are published bytes. `skip-changeset` would be false, and a `patch` changeset is owed. Precedent on the same file: the `objectstack-ai#14640` changeset states the same fact in the same words ("The ledgers ship inside this package (`files[]` includes `liveness`)"). ## Verification Gate families derived in this worktree with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` over the real changed paths, then reconciled with `--ran` carrying each command's exit code captured before any pipe. - **57 derived, 53 run green, 4 NOT MEASURED, 0 UNRUN** at `5ac6291`. - The 4 NOT MEASURED all exit **3** with their own `PREREQUISITE NOT MET` line, which is "nothing was swept", not a finding: `check:dts-closure`, `check:dual-build-cjs-loads`, `check:lean-entry-closure`, `check:sourcemap-no-sources-content`. Each reads built `dist/` across the workspace and needs a full `pnpm build` first. This diff contains no TypeScript and no published bytes outside one Markdown file, so it cannot move any of them; CI runs them on a built tree. - `check:plugin-teardown-shape --self-test` first exited 3 on a shallow checkout ("cannot read the positive control at 621a487"). After `git fetch --unshallow` it passes, 48 cases. Its PR-verdict run (no `--self-test`) was green both times. - The five roster gates whose allowlists sit under a directory this diff is in were run rather than read as silence: `check-changeset-fixed`, `check:spec-changes`, `check:authz-resolver`, `check:error-code-casing`, `check:filter-alias-parity` — all exit 0. - The one test file that reads this README, `packages/spec/scripts/liveness/check-liveness.test.ts`: 64 tests pass. - Control-byte self-scan over both changed files: no match. - `pnpm lint` was narrowed, and the narrowing is measured rather than claimed: eslint's own declared population is source extensions only (`files: ['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}']` and four TypeScript-only globs; no Markdown glob), `--format json` over both changed paths returns 2 entries, each "File ignored because no matching configuration was supplied" with `errorCount: 0`, and the config enables no type-aware linting (no `parserOptions.project`), so this diff cannot move the verdict on any file it does not touch. Count of changed files inside eslint's population: **0**. The repo-wide sweep is CI's. ## Acceptance notes Noted, not filed, and deliberately not fixed here — the file surface for this card is `packages/spec/liveness/README.md` and this observation is larger than the repoint: - **`:548` calls this "The CLI `compile` lint", which is now narrower than the truth.** The same rule is also dispatched at the **runtime publish door**: `packages/lint/src/runtime-gate.ts` bridges `email_template` and `mapping` (group C of the ADR-0049 ruling) and, since objectstack-ai#19568, `datasource` — the three `allowRuntimeCreate: true` types whose only author-facing door had run no authoring rule at all. So an `authorWarn` entry today reaches authors through Studio/REST/MCP writes as well as through `os compile`, and this section still describes only the compile door. The repoint does not make that sentence false, so widening it is out of scope for this PR; the successor the card names — the next author of an `authorWarn` entry — is the same reader who would be under-informed by it. Carrier: whoever next edits this section or adds a `runtime-gate.ts` bridge row. --- _Generated by [Claude Code](https://claude.ai/code/session_013RDBh5DqXd2xnLwvHLgLFr)_ Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: os-support-ai <318092878+os-support-ai@users.noreply.github.com>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 28, 2026
… on the list view, with pins and a re-cited liveness row (ruling B) (objectstack-ai#19598) Fixes objectstack-ai#15184 Clause-②: no ## Fix round — contract review `5791391832` (FAIL on `cfc103a20a`), now at `e76041c2b4` Revised by `domain:spec` seat 2, session `session_01UDXER3sdqfeVYpEWZs5mZx`. Both fail bases were re-derived before fixing and both reproduced; the folded-in note did too. 1. **The hover claim is dropped.** "… and into the TSDoc an author hovers" is gone from §1 below, from the changeset and from the pin file's header. Re-measured with the TypeScript language service over `packages/spec` at the merged tree: quick-info on `fieldOrder` carries **0** documentation characters at `defineView`, at `ListViewSchema.parse` and on the `ListView` type member, and no hover at any of those sites carries a `.describe()` lead string. The instrument is lit: `columns` and `hiddenFields` show their JSDoc (447 and 1859 characters). The built declaration files (94 `dist/*.d.ts` / `*.d.mts`) contain **0** of the three describe lead strings and **0** docblock phrases (`Field Visibility`, `orders what survives`, `declares no projection`), against 70 lines naming `fieldOrder`. The `json-schema/` and generated-docs legs stay; both were re-read (`json-schema/ui/ListView.json` carries the describe text, and `json-schema` is in `files[]`). 2. **The projection clause now states the `columns: []` boundary.** "A field omitted here is not displayed" and "Nothing downstream may re-add a field this list omits" are gone. In their place: `hiddenFields` and `fieldOrder` cannot add a field `columns` omits, and **an empty `columns` declares no projection**, so neither of them applies, and which columns show is left to the renderer. The boundary is pinned in the declaration half, twice (the `columns` description and the docblock's projection step), and the door half pins that all four doors admit `columns: []` verbatim. ⛔ `[]` is not refused at the door; that would be a narrowing, and it is not this card's call. 3. **"Runs exactly these three steps" is corrected by naming the gate.** The docblock now says the `effectiveFields` memo runs the three steps in this order, **with one more between steps 1 and 2**: it drops the columns field-level security denies the current user read on. Two sentences not named by the review were narrowed while fixing base 2, because they carry the same over-reach. The docblock's opening "three keys … decide which fields a list view shows" became "together build one field list": at the pin the kanban branch takes `cardFields` ahead of the composed list (`ListView.tsx:2977`), and the gallery branch passes its own `titleField`, defaulting to `'name'` (`:3058`). And "an author who wants a field gone edits `columns` …" was deleted, since emptying `columns` brings the object's default columns back in the `ListView` grid. The boundary, measured at objectui `.objectui-sha` `87af769e9a3e` (`git show` from the local objectui clone): - `packages/plugin-list/src/ListView.tsx:2828-2831`: the `hasAuthoredColumns` memo is `Array.isArray(schema.columns) && schema.columns.length > 0`. The comment at `:2795` says "The author declared none (`columns` absent, or `[]`)". - `:2880-2882`, the grid branch: an unauthored projection passes `{ fields: undefined, columns: undefined }`. - `packages/plugin-grid/src/ObjectGrid.tsx:3228`: "Default columns priority (when schema doesn't specify columns)". In that file **0** lines name `hiddenFields` and **0** lines name `fieldOrder`, against 71 lines naming `columns`. - The hide-fields popover's field list (`ListView.tsx`, `allFields`) is built from `schema.columns` too, so with `[]` it offers nothing to hide. ## Ruling B is what this PR executes — the card body is superseded The card body executes the 2026-09-04 ruling "retire at the spec" and declares `Clause-②: yes`. That direction is dead. Ruling B (comment 5635654931, director seat summon objectstack-ai#22, maintainer decision batch objectstack-ai#115, verbatim 「同意」, 2026-09-11) supersedes it, verbatim: > **Ruling B** (the 2026-09-04 ruling's own fallback, now the ruling): `ListViewSchema.fieldOrder` stays. The contract states, in the `.describe()` / docblock of the three keys under the `Field Visibility and Ordering per View` comment in `packages/spec/src/ui/view.zod.ts`, the composition objectui already applies: `columns` is the projection, `hiddenFields` subtracts from it, `fieldOrder` orders what survives (entries absent from `fieldOrder` sort last); pins assert the composition at the schema doors. In the same PR the liveness row `packages/spec/liveness/view.json` `/props/list/children/fieldOrder` is re-cited (its first evidence path `packages/react/src/spec-bridge/bridges/list-view.ts` no longer exists; the `ListView.tsx` line numbers drifted to ~`:2424-2434`) with `verifiedAt` refreshed. ⛔ Not A: it deletes a channel measured live end to end and reds objectui's relay-rung census pin. Nothing is retired, tombstoned or deprecated here. No `retiredKey()`, no ADR-0087 entry, no `packages/lint` validator change. ## 1. The declaration — `packages/spec/src/ui/view.zod.ts` The three keys now state their own role in the composition, in the `.describe()` text that ships (into `json-schema/` and into the generated `content/docs/references/**`), plus one docblock above the block that states the application order once: - `columns` is the **projection**: the candidate set AND the baseline order. `hiddenFields` and `fieldOrder` cannot add a field omitted here. **An empty `columns` declares no projection**, so neither of them applies, and which columns show is left to the renderer (objectui's `ListView` grid derives the object's default columns). - `hiddenFields` **subtracts** from that projection, before any ordering runs. A name `columns` never projected subtracts nothing. - `fieldOrder` **orders what survives** and never adds a field. A surviving column absent from `fieldOrder` sorts **last**, after every listed one, keeping its `columns`-relative order; a name listed there that did not survive orders nothing. This is a declaration of the ORDER OF APPLICATION, not a precedence rule between rival spellings — `columns` and `fieldOrder` never contradict each other, because one selects and the other sorts. That is why the retirement premise was false. **The accept set does not move** (`Clause-②: no`): the schema declaration is unchanged apart from the description strings — ``` columns: z.union([z.array(z.string()), z.array(ListColumnSchema)]).describe(…) hiddenFields: z.array(z.string()).optional().describe(…) fieldOrder: z.array(z.string()).optional().describe(…) ``` No key is added, removed, narrowed or widened, and no parse verdict changes anywhere. Re-measured this round, base `origin/main` `6eaa0f4a81` against head `e76041c2b4`: 22 inputs went through the four doors (`ListViewSchema`, `ObjectListViewSchema`, `defineView`, the registered `view` schema). The inputs include `columns: []` alone, `[]` with both other keys, all-empty arrays, a kanban with `[]`, missing `columns`, wrong types, an unknown key and a case variant. Both trees gave **48 accept / 40 reject**. The probe output is byte-identical (sha256 `6e4f279566c5c0e0…` at both) in verdicts, parsed values and issue codes. `ListViewSchema.shape` is identical: 50 keys, `columns` required, the other two optional. Lit control in the same run: the descriptions differ for exactly `columns`, `hiddenFields` and `fieldOrder`, and not for `rowColor` or `rowActions`. ## 2. The pins — `packages/spec/src/ui/view-field-order-composition.pin.test.ts` (21 cases) Two halves, deliberately independent: - **Declaration half** reads the three descriptions off the LIVE schema (not the source text) and requires each one to name all three keys and state its own role, plus the "sorts LAST" rule an author cannot guess. One further case pins the block docblock's three steps in application order. Two more pin the empty-`columns` boundary: once in the `columns` description, once on the docblock's projection step. - **Door half** parses a document carrying all three keys through the four doors a list view really arrives by — `ListViewSchema`, `ObjectListViewSchema`, `defineView` and the registered `view` metadata schema — and asserts the arrays come back VERBATIM, with no unrecognized-key report. Three more cases pin that the spec DECLARES the composition and does not PERFORM it: no cross-validation of `fieldOrder` or `hiddenFields` against `columns` (reference integrity is `@objectstack/lint`'s job, against the object's real field map), and `columns` comes back whole. One more pins that every door admits `columns: []` verbatim, which makes the boundary the declaration states reachable. A later ruling that refuses `[]` retires that case and the boundary sentence together. The pin asserts nothing about how a renderer sorts. That belongs to objectui and is carried by the ledger row below. ### Ablation — seven legs at `e76041c2b4`, each mutation proven on disk and each restore proven byte-identical Every leg ran through `scripts/ablation-replace.mjs`: literal anchor, hit count declared, blob hash before and after, and `git diff HEAD` empty on restore. The legs ran against the committed tree, under a driver carrying its own EXIT/INT/TERM restore. Control: **21 of 21 green**. | leg | mutation | result | |---|---|---| | A | `fieldOrder`'s `.describe()` reverted to its pre-ruling one-liner | **3 of 21 red**, all in the declaration half; the door half stayed green | | B | the whole `fieldOrder` key DELETED from the shape (the superseded retirement) | **12 of 21 red** | | C | the docblock's ordering step reworded to drop "orders what survives" | **1 of 21 red**, the application-order case alone | | D | the empty-list boundary removed from the `columns` description | **1 of 21 red**, the new `columns` boundary case | | E | the boundary removed from the docblock's projection step | **1 of 21 red**, the new docblock boundary case | | F | both `columns` arrays given `.min(1)`, so the doors refuse `[]` | **1 of 21 red**, the new door case | | G | "`hiddenFields` and `fieldOrder` cannot add a field omitted here" removed | **1 of 21 red**, the (retitled) projection case | Restore evidence, identical on every leg: blob after restore `5a0b5cf9d298` equals the blob at HEAD, `git diff HEAD` is empty, and `git status` is clean. Leg B's first attempt was a **no-op**: plant mode with an empty replacement, which the tool refused before running any test. It was re-run in `--delete` mode, and that run is the reading above. ## 3. The ledger — `packages/spec/liveness/view.json`, `/props/list/children/fieldOrder` Re-measured against the pin THIS tree carries, `.objectui-sha` = `87af769e9a3e` (⛔ not objectui HEAD, and ⛔ not ruling B's own 2026-09-11 coordinates, which had drifted again). `verifiedAt: 2026-09-21`. - **`evidence`** now cites the applying reader: `objectui packages/plugin-list/src/ListView.tsx#effectiveFields:2750-2784` — it takes `schema.columns` as the candidate set, subtracts the hidden-field set at `:2766-2771`, then at `:2774-2781` builds an order map from `schema.fieldOrder` and sorts the survivors by it. A survivor the map does not name scores `Infinity`, which is the mechanism behind "sorts last". `:2784` lists `schema.fieldOrder` as a memo dependency. - **`producer`** is new, and it is the half a consumer pointer alone cannot prove: `objectui packages/plugin-view/src/ObjectView.tsx#renderContent:2284` relays a named view's `fieldOrder` onto the list node that memo reads. Without a producer the read could be permanently `undefined` — the `Seed.env` shape the ledger README records. - **Both halves of the old citation had rotted.** Its first path is gone: `git ls-tree -r` at objectui `@87af769e` returns **0** files under `packages/react/src/spec-bridge/`, while `packages/react/` itself returns **160** — the lit control, so the zero belongs to the directory and not to the instrument. Its second rotted IN RANGE: `:1499-1500` at `@11c1e71e`, `~:2424-2434` when ruling B was written, `:2774-2781` today. Both pointers now carry symbol anchors, which is the form that moves with the consumer. - One measured gap is recorded in the row's `note` as objectui's to close and explicitly NOT a defect in this key: the OTHER relay, `objectui packages/app-shell/src/views/ObjectView.tsx`, carries `hiddenFields` at `:2480` but still records `fieldOrder` as a declared absence (objectui#7516) in its relay-rung census. The plugin-view rung cited under `producer` is the live one. `packages/spec/liveness/state-counts.md` and `liveness/README.md` were DECLARED but not cleared (held at claim time by PR objectstack-ai#19594, merged since). Neither is in this diff. On the merged tree, `check:liveness` reports `state-counts.md` current: the same 40 rows. The row's status stays `live`, so no count moved. ## 4. Changeset — `patch`, as ruling B fixed `@objectstack/spec` ships `src/**/*.zod.ts` and `liveness/` in its `files[]`, so both edits publish. `Clause-②: no` plus `patch` is the declared pair; no arm of the `(widening)`/`(narrowing)` pair applies, because the accept set does not move. ## 5. Generated artifacts `content/docs/references/{ui/view,api/protocol,data/object}.mdx` and `skills/objectstack-ui/references/react-blocks.md` are the mechanical output of `gen:docs` and `gen:react-blocks`. Every changed row is one of the three descriptions rendered where the generators already render it — no hand edits, and nothing unrelated moved. **This round merged `origin/main` (`6eaa0f4a81`) through `scripts/pm/os-regen-merge.sh`.** The driver deferred the three `content/docs/references/**` files and step 2 took main's side. The step-3 commit was then refused as stale, so that commit (`47bc43df13`) carries the three files regenerated on the merged tree (`gen:schema`, `gen:docs`, `gen:react-blocks`). Its only delta over main's side is the three describe rows this branch already carried: 27 rows in 9 tables. Nothing was text-merged. The fix commit regenerated them again. `react-blocks.md` did not move this round, because its `columns` cell is truncated before the changed sentence. **About the one governed path.** `skills/objectstack-ui/references/react-blocks.md` is the `spec-react-blocks` row of `GENERATED_SURFACE_EXCEPTIONS`: recomputable byte-exact by `pnpm --filter @objectstack/spec gen:react-blocks`, whose trusted generator tree is `packages/spec/scripts/` — which this diff does not touch, so the objectstack-ai#11084 co-edit fence does not trip. Size readings, since a published skill package is in the diff: the changed file is **115 lines before and 115 after** (one row rewritten, net 0), and the package's SKILL.md total is **309 before and 309 after**. No skill prose was authored here. ## Verification — this round, at `e76041c2b4` (merged tree) All exit codes were captured BEFORE any pipe. Heavy runs went through `scripts/pm/os-verify-lock.sh`, and each printed `VERDICT command-exit 0`. | what | result | |---|---| | `pnpm --filter @objectstack/spec build` (with DTS) | exit 0; `check-dts-emitted` 34/34 | | `pnpm --filter @objectstack/spec test` | exit 0: **520 files passed, 15259 tests passed**, 1 todo (15260) | | `pnpm --filter @objectstack/spec typecheck` | exit 0; the pin file is in the `tsconfig.test.json` program (1 of 491 test files) with 0 debt entries | | `pnpm --filter @objectstack/spec check:generated` | exit 0: **all 15** generated artifacts up to date, measured against the DTS build above | | `check:liveness` · `check:objectui-pin-citations` · `check:docs` · `check:react-blocks` · `check:nul-bytes` | exit 0 each | | the pin alone | exit 0: 21 passed | | eslint, narrowed to this diff's lintable files | exit 0: 2 files, 0 errors, 0 warnings (see below) | | `node scripts/pm/dispatch-gates.mjs --ran` | exit 0: **113 derived, 111 run green, 0 UNRUN, 2 NOT MEASURED** | | `node scripts/pm/check-governed-merges.mjs --pr 19598` (installed tree) | exit 0: NOT governed. `react-blocks.md` is lifted as a pure regeneration; 8 paths, +433/-36 | The 2 NOT MEASURED are `pnpm check:dual-build-cjs-loads` and `pnpm check:type-check-debt`. Both exit **3** (`PREREQUISITE NOT MET`) and both need a whole-monorepo `dist`. ⛔ Neither is a pass and neither is a finding; CI builds the closure before running them. Thirteen gates first answered on a missing `dist`: twelve with exit 3, and `check:generated` with exit 1 through its `check:api-surface` member. They were cleared by the spec DTS build and by a targeted `turbo run build` of `@objectstack/lint`, `@objectstack/formula`, `@objectstack/client-react` and `@objectstack/objectql`, then re-run green. **Lint was narrowed, not run repo-wide.** Three readings back the narrowing. ① Population: all eight changed paths were handed to eslint. Its own config lints the two `.ts` files (`print-config` resolves rules for both), and it supplies no configuration for the other six (`.md`, `.mdx`, `.json`): each answers "File ignored because no matching configuration was supplied". ② Count: the `--format json` output lists 8 files. The 2 linted ones carry 0 errors and 0 warnings; the other 6 carry only that one ignore warning each. ③ Invariance: no `parserOptions.project` and no `projectService` are set, so linting is not type-aware and this diff cannot move a verdict on an untouched file. The repo-wide `pnpm lint` is CI's `Lint & Repo Gates`.⚠️ One reading a gate CANNOT give. `check:objectui-pin-citations` is green (46 asserting citations match `87af769e9` on the merged tree), but it scans `packages/spec/src/**` only. The ledger's `@87af769e` citation lives in `liveness/view.json`, outside that scan, and `check:liveness` attributes cross-repo paths without resolving them. The objectui coordinates in this body and in the row's `note` therefore rest on measurements taken by hand at the pin, with the lit controls stated. ## Acceptance notes - **Noted, not filed — the sibling `hiddenFields` ledger row has the same rotted citation.** `/props/list/children/hiddenFields` still cites `objectui packages/react/src/spec-bridge/bridges/list-view.ts:123` (the directory that greps to zero above) and `ListView.tsx:763` / `:1491-1494` / `:1509`, all measured at `@11c1e71e` and all drifted at the current pin — the applying lines are `:1465-1466` and `:2766-2771`. It is left untouched deliberately: ruling B names ONE row, and widening past an explicit ruling is not this PR's call. It is reported to the seat for filing rather than fixed here. Whoever next re-cites a `view.json` list row will touch this file. - **Reported to the seat for filing — the registered `object-view` renderer's grid path applies neither `hiddenFields` nor `fieldOrder`.** At objectui `87af769e`, `packages/plugin-view/src/ObjectView.tsx` relays both keys only inside the host-composition `renderListView` branch (`:2268`, `:2284`). Without a host, a grid view renders `ObjectGrid` directly (`:2335-2344`) from `gridSchema` (`:1909`), and neither that memo nor `ObjectGrid.tsx` reads either key. On that path a named grid view with `columns: []` also sends `fields: []`: `viewColumnFieldNames` is presence-preserving, and `schemaFields` is then shown as-is (`ObjectGrid.tsx:3245-3246`). This is objectui's to close. It changes no sentence here, because the boundary text names `ListView`. ## 维护者速读(草稿) **改了什么** —— `ListViewSchema` 上 `columns` / `hiddenFields` / `fieldOrder` 三个键的说明文字,现在把它们本来就构成的组合关系写进契约:`columns` 投影,`hiddenFields` 做减法,`fieldOrder` 给剩下的排序(没被列进 `fieldOrder` 的排在最后)。配一份把这条组合钉在四个 schema 入口上的 pin 测试,外加 `liveness/view.json` 里 `fieldOrder` 那一行的证据重测。复核轮补上两处:说明文字不再声称会出现在编辑器悬停提示里(实测不会);`columns` 为空数组时不算投影,另两个键都不生效,由渲染器决定显示哪些列(objectui 的 `ListView` 表格改用对象的默认列),这条边界也钉进了 pin。 **为什么改** —— 2026-09-04 的裁决要把 `fieldOrder` 退役,前提是它和 `columns` 是同一件事的两种拼法、且没有契约规定谁赢。这个前提经实测不成立:两者根本不冲突,一个选字段一个排顺序,objectui 一直就是三步连着做的。2026-09-11 决策批次 objectstack-ai#115 的裁决 B 改为保留该键,并要求把这条组合写进契约 —— 本 PR 就是执行它。 **风险与代价(含回滚)** —— 受理集合不动,没有任何键被加、删、收紧或放宽,解析结果零变化,`packages/lint` 的四个校验器没碰。代价只有三处说明文字变长,以及随之重新生成的参考文档与 react-blocks 契约。回滚 = 直接 revert 本 PR,没有数据迁移、没有 tombstone、没有下游需要跟进的动作。 **席位意见** —— 本轮修正已交付;达档复核按维护者「契约复审不要启动了,交给下一任」交给下一任席位,复核通过前不入队。 **你要做的** —— 本 PR 唯一触到的受管路径是 `skills/objectstack-ui/references/react-blocks.md`,它是 `gen:react-blocks` 的产物行,队列腿可按字节重算认证、无需您点头。若认证因故没生效,则需要您对该一行(115 行文件中被重写的 1 行、净增 0 行)给一次批准;除此之外无需您做任何事。 --- _Generated by [Claude Code](https://claude.ai/code)_ --------- 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.
Fixes #14640
Clause-②: no
Enrols
RestApiConfigSchema— theapisub-object ofRestServerConfig, and the fifth of its five — in the ADR-0049 liveness ledger as a new governed typerest_api. 26 properties classified: 12live, 14dead. The card's two containers,api.documentation(ten members) andapi.responseFormat(three), are recordeddeadwith a distinct reason each.This file records status. It makes no per-key enforce-or-remove call — that is deferred to the human floor, and the two containers deliberately do not share one verdict.
The card's first ordered act: the falsifier, re-established first-hand
The triage answer (comment 5624242736) replaced the body's phantom blocker with a precondition:
Result: no seam constrains either key. The measured zero holds. Re-measured independently on
origin/mainat1ff3a8f210(the ledger's own census was taken at31184e5daf3b), by shape and never by line number, since the file has grown and every line number in the card is stale.The seam that used to constrain them has settled and shipped.
RestServer.normalizeConfignow opens withconst api = this.parseDeclaredApiConfig(config.api)and builds theapiblock key by key from that parsed output —documentation: api.documentationandresponseFormat: api.responseFormatamong them — where it previously discarded the parse and rebuilt from a cast over the raw input. The source says so itself: "the asymmetry is gone and all five now build from their parsed output". Introduced by53cbad9f75(2026-09-05, PR #15673);git merge-base --is-ancestor 53cbad9f75 '@objectstack/rest@17.4.0'exits 0 — a positive ancestry result, self-certifying — andpackages/rest/package.jsononmainreads17.4.0.packages/rest/CHANGELOG.mdcarries it under the17.4.0heading. So: released, not in flight.Zero read sites, three instruments, each with a lit control.
git grep, no pathspec, filtered after)responseFormat/includeMetadata/includePagination/termsOfServicereachpackages/rest/srconly at theNormalizedRestServerConfigtype declaration and thenormalizeConfigbuild-throughenableCrud/apiPath/projectResolutionreturn real read sites inrest-api-plugin.ts,direct-mount-composition.ts,dispatcher-plugin.ts,serve.tsthis.config.apianywhere; the only{ ...api }in the tree ispackages/spec/src/conversions/registry.ts, which copies the stackapi:blockpackages/rest/srcalone, so the file filter is litObject.keys/values/entriesoverthis.config, noJSON.stringify(this.configObject.entries(this.config.initialData)indriver-memoryRadius: every tracked file in this repo, plus the sibling repo objectui measured directly at the pinned sha
87af769eand at its head98178b20. Not in radius, and declared rather than papered over: the closed cloud runtime is not reachable from this container, which is why everyevidenceScopereadsin-repo.Two structural backstops a grep cannot give:
NormalizedRestServerConfigis declared withoutexport, andRestServer.configisprivate— the normalized block is unreachable from outside that one class. And fordocumentationspecifically, the/openapi.jsonhandler passesinfothrough untouched by a recorded decision (#11646); the servedinfo.titleis the literal'ObjectStack REST API'written inpackages/spec/scripts/build-openapi.ts. An enforce route for that container is not wiring up a title — it is reopening who ownsinfo.What the parse does and does not settle. Since the seam moved, a malformed
api.documentationorapi.responseFormatis now refused at construction. That is accept/reject, a different axis from liveness: nothing reads either key's value back. The ledger states this distinction rather than letting a reader collapse the two.File-surface accounting
The claim declared
packages/spec/src/api/rest-server.zod.ts,packages/rest/src/rest-server.ts, their tests,packages/spec/liveness/conditionally, and.changeset/. Three files on the branch are outside that. Each was put to an ablation — mutate, prove the mutation landed on disk by blob hash, run the gate, restore and prove the restore bygit diff HEAD— rather than argued for.1.
docs/qa/platform-checklist/coverage.json— mechanically forced. Kept.scripts/check-platform-checklist.mjsderives its kind universe frompackages/spec/liveness/*.jsonand is bidirectional. Renaming the key (blob690af861to6a554e59) tookpnpm check:platform-checklistto exit 1 on both limbs at once:Adding the ledger is what obliges the entry. The entry itself is a scope statement, not a coverage claim: it names what the two mapped items do cover (the eight
enable*switches at their defaults, andversion/basePath/apiPathas a derived prefix) and what they do not (every non-default variant, and both containers on this card).2.
packages/spec/scripts/liveness/check-liveness.mts— mechanically forced, both hunks. Kept.Deleting the
SPEC_ONLY_SCHEMASrow (blob4ec7afb6tocc1136c0):check:livenessexits 1 withError: metadata type 'rest_api' has no registered schema. Deleting theGOVERNEDentry (blob4ec7afb6to6adc1c38): exit 1 with four findings — an unbacked README row, a heading error, a stale count artifact, and a README/state-counts disagreement. The ledger is simply not read without both. The same commit also corrects the now-expired fence sentence in the gate's own comment, which is where the exclusion was recorded in the first place.3.
packages/spec/scripts/liveness/tombstoned-row-status.test.ts— NOT gate-forced. Kept anyway, and here is the measurement.Deleting the mirrored
rest_apirow leaves the test green. That is not evidence the edit is idle — it is evidence the pin is a floor.TOMBSTONE_FLOOR = 40is asserted withtoBeGreaterThanOrEqual, so it absorbs the loss. Driven directly, with the floor raised to 41 as the probe:AssertionError: expected 40 to be greater than or equal to 41So the row adds exactly one member to the measured population:
rest_api.requireAuth, aretiredKey()tombstone whosedeadverdict would otherwise never be visited by the invariant this test exists to enforce. That is the #18304 shape —agent.toolssat atliveon a tombstoned key for months because nothing walked it. The edit is correctness-forced even though no gate reds without it. See the acceptance note below on the floor itself.Nothing else was widened.
packages/spec/liveness/README.mdandstate-counts.mdare inside the conditionally-declaredpackages/spec/liveness/surface and are both gate-owned.Verification
Taken on the final commit
006888fbef.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackyields 70. All 70 ran with$?captured before any pipe;--ranreconciliation reads 70 derived, 66 run, 4 NOT-MEASURED, 0 UNRUN.check:doc-formula-expressions,check:dual-build-cjs-loads,check:lean-entry-closureandcheck:type-check-debt, all exit 3 —PREREQUISITE NOT MET, one cause: they read built output and the workspace closure is not built in this container. Exit 3 is neither a pass nor a failure, and none of them is recorded as one. CI builds fresh and runs all four.check-changeset-no-major --base origin/mainran the bump-level limb against the real merge base and passed ("This diff introduces nomajorbump"). Only its LEVEL AXIS limb readsNOT APPLICABLE, for want of apull_requestpayload — so the green is real on the axis this diff could have moved, and vacuous only on the axis a local run cannot reach.pnpm lint— the whole-repo scan,eslint . --no-inline-config: exit 0 in 1m40s. No narrowing claimed and none needed.pnpm --filter @objectstack/spec typecheck— exit 0. This package'stypecheckis three programs, so it reachessrc,scripts/(where the edited gate lives) and the test layer.packages/spec/scripts/liveness: 12 files, 333 tests, all pass. The ledger directory's only consumer outside this package ispackages/lint/src/lint-liveness-properties.ts; its tests run 91 assertions green. No author warning is emitted, and none should be: nothing here is markedauthorWarn, because aRestServerConfigis never part of a stack and that lint walks stack collections.state-counts.mdis generated, not hand-edited —git hash-objectreads7c8c1cb1beforegen:liveness-countsand7c8c1cb1after, with the working tree clean. Byte-identical to the generator's own output.documentationcontainer adds no row to the undrilled-container baseline —undrilled-containers.baseline.jsonis untouched by this branch, contains zerorest_apicoordinates, andcheck:livenessis green against it. That gate fails in both directions: an undrilled container missing from the list reds, and so does a listed row whose container has since been drilled.packages/spec'sfiles[]includesliveness, so the ledger ships in the tarball.skip-changesetwould be wrong here.Acceptance notes
TOMBSTONE_FLOOR = 40intombstoned-row-status.test.ts, and its comment says "40 was the measured population". This branch takes the real population to 41 (measured above). The pin istoBeGreaterThanOrEqual, so this is not a gate failure and arguably not a defect — a>=floor is loose on purpose so that every new governed type does not have to touch this file. But the prose now reads one behind the tree, and the floor carries a row of slack it did not have before. Whether it advances is the file owner's call, so it is noted rather than changed. Not filed.006888fbefcorrects the objectui cross-repo control count cited in the ledger. The note recordedbasePath = 176 at the pin; re-measured on the two trees the note itself names,git grep -n -F basePathreturns 181 at87af769eand 182 at98178b20. The zero it controls is unchanged — all six key spellings return 0 at both. A cited count that does not reproduce on the tree it names is not a reading, so the number was corrected rather than the tree re-chosen. Ten row notes plus the header carried it; all eleven updated.#14366,#14369,#14691,#14365and#14690all return HTTP 404 while neighbours resolve at 200. The work they name is visibly landed in the tree, which is how every claim above was checked — read the tree, not the tracker. The class is carded at The repo cites issue numbers that do not resolve — 5 measured instances, and the two cited from source docblocks and a release page are swept by nothing #17512. No replacement numbers were guessed.Generated by Claude Code