From 83f4481ab9743fafaa451972a6196b87489270da Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 26 Oct 2026 10:58:16 +0000 Subject: [PATCH 1/2] test(ui-merge): the ui register's workflows row comment records the maintainer's ruling The maintainer kept objectui's `.github/workflows/**` row on objectstack#25712 (option b): it stays in the register and is read through the root register. The comment above the moved-row case said the call was pending; it now records the ruling, and leaves the register's own retirement to sub-issue 4. Comment only. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01A6SEitNngAMugGbuJHWsDS --- ui/scripts/__tests__/check-governed-queue-guard.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ui/scripts/__tests__/check-governed-queue-guard.test.ts b/ui/scripts/__tests__/check-governed-queue-guard.test.ts index 36553a1177a..1d5a875391f 100644 --- a/ui/scripts/__tests__/check-governed-queue-guard.test.ts +++ b/ui/scripts/__tests__/check-governed-queue-guard.test.ts @@ -110,10 +110,10 @@ describe('the governed surface is the ruled definition, and it is real', () => { /** * The one row whose directory left this tree: objectstack#25712 deleted - * `ui/.github/workflows/`, which GitHub never ran under the monorepo. ⚠️ PENDING - * the maintainer's call on objectstack#25712, since the row is objectui#12255's - * ruling: drop it, keep it as below, or retire this register for the root one - * (sub-issue 4). + * `ui/.github/workflows/`, which GitHub never ran under the monorepo. The row is + * objectui#12255's ruling, and the maintainer kept it on objectstack#25712 + * (2026-10-26, option b): it stays, read through the root register below. Whether + * this register retires for the root one is sub-issue 4's question. */ const MOVED = 'workflows'; From 23cb18d2b9dbf742c9d7f717294e74d5bd8e3cd3 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 26 Oct 2026 12:20:20 +0000 Subject: [PATCH 2/2] refactor(spec-liveness): the renderer's ledger evidence is in-repo under ui/; objectui becomes a retired realm that fails objectui merged into this repo as ui/ (#25677), so the liveness ledger's renderer evidence stops being cross-repo (objectstack#25712 item 1). Reader (scripts/liveness/evidence.mts): - `ui` joins REPO_ROOTS, so `ui/packages/...` pointers resolve like any path: the file exists, a cited line is inside it, an anchored symbol is in it, and the file names the key. - `objectui` leaves FOREIGN_REALMS and becomes RETIRED_REALMS (objectui -> ui). A path still attributed to it lands in a bucket of its own that the gate fails on, whatever the row's status, printing the ui/ spelling as the remedy. Counted as foreign, it would never fail. Read as local, it could resolve against the wrong root, because both trees have packages/core/. A path already spelled ui/ is local whatever prose precedes it. Ledger (24 files): 374 objectui-attributed pointers in 164 rows are re-rooted under ui/, and the `objectui [@sha]:` marker in front of each one is dropped. verifiedAt is unchanged everywhere. 107 cross-repo rows become in-repo. The one row that also cites cloud stays cross-repo, and the 56 undeclared scopes stay undeclared. Line citations: of the 133 objectui `path:N` pointers, 6 named the same lines in ui/, 66 named code that moved, 58 could not be checked against today's file, and 3 were in deleted files. A clause is relocated only when every line it cites (the pointer plus its bare `:N` follow-ons) is byte-identical at exactly one offset, both at the cited objectui commit and in today's file, so one clause never mixes old and new numbering. That gives 7 kept, 58 relocated, and 65 that drop the line from the pointer and keep the reading as prose (`ui/p (:N at objectui @sha)`). Line numbers written as prose inside a relocated clause (sibling `file.tsx:N` refs and "line N" in eight rows) were relocated by the same rule. Rows whose objectui reader is gone get no repoint and a dated note line: - app.navigation.id: the useNavOrder clause (objectui#12059). - view.list.hiddenFields and view.form.groups: the spec-bridge clauses (objectui#6632). - field.widget: the permission-facet-link registration (objectui#12998). - field.externalId: the only pointer was deleted in objectui dc14bcb, so it is now prose, and follow-up F23 owns finding the reader. Key mention: 13 newly in-repo citations read a renamed key or delegate to an anchored sibling: list.inlineEdit -> `editable`, form.subforms -> `details`, page print members -> `print`, chartConfig -> `chartConfigPresentation`, and flows.screens -> the localized field keys. Each is a key-mention.baseline.json row that names the spelling the file uses. Tests: evidence and gate cases are retargeted from objectui to cloud/ee, with new cases for the retired realm. The `path:NNN` contract test and the line-citation floor are restored, as their own guard comments instruct once that population is non-zero again. The README sections on the realm marker, the commit pin, evidenceScope and the preview lookup now cite ui/. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01A6SEitNngAMugGbuJHWsDS --- packages/spec/liveness/README.md | 61 +++++--- packages/spec/liveness/action.json | 32 ++--- packages/spec/liveness/app.json | 122 ++++++++-------- packages/spec/liveness/book.json | 8 +- packages/spec/liveness/capability.json | 6 +- packages/spec/liveness/connector.json | 28 ++-- packages/spec/liveness/dashboard.json | 70 ++++----- packages/spec/liveness/dataset.json | 4 +- packages/spec/liveness/datasource.json | 12 +- packages/spec/liveness/email_template.json | 8 +- packages/spec/liveness/field.json | 60 ++++---- packages/spec/liveness/flow.json | 8 +- packages/spec/liveness/hook.json | 12 +- packages/spec/liveness/job.json | 12 +- packages/spec/liveness/manifest.json | 20 +-- packages/spec/liveness/mapping.json | 2 +- packages/spec/liveness/object.json | 4 +- packages/spec/liveness/page.json | 60 ++++---- packages/spec/liveness/permission.json | 20 +-- packages/spec/liveness/sharing_rule.json | 4 +- packages/spec/liveness/tool.json | 8 +- packages/spec/liveness/translation.json | 40 +++--- packages/spec/liveness/validation.json | 18 +-- packages/spec/liveness/view.json | 38 ++--- packages/spec/liveness/webhook.json | 12 +- .../spec/scripts/liveness/check-liveness.mts | 48 ++++++- .../scripts/liveness/check-liveness.test.ts | 80 +++++++++-- .../spec/scripts/liveness/empty-state.mts | 6 +- packages/spec/scripts/liveness/evidence.mts | 73 ++++++++-- .../spec/scripts/liveness/evidence.test.ts | 133 +++++++++++++++--- .../liveness/key-mention.baseline.json | 65 +++++++++ .../spec/scripts/liveness/key-mention.mts | 6 +- 32 files changed, 699 insertions(+), 381 deletions(-) diff --git a/packages/spec/liveness/README.md b/packages/spec/liveness/README.md index fbfdcc6d3d2..1a4f39f4612 100644 --- a/packages/spec/liveness/README.md +++ b/packages/spec/liveness/README.md @@ -72,7 +72,7 @@ registry to fold it back onto — the override *is* its governance. | Status | Meaning | |---|---| -| `live` | Has a runtime consumer. Cite it in `evidence`, preferably anchored to the consuming symbol — `file#symbol` — with `file:line` as an optional convenience; for another repo's path, prefix the realm — `objectui: packages/app-shell/…` (see below). | +| `live` | Has a runtime consumer. Cite it in `evidence`, preferably anchored to the consuming symbol — `file#symbol` — with `file:line` as an optional convenience; the renderer is in this repo, cited `ui/packages/app-shell/…#Symbol`; for another repo's path, prefix the realm — `cloud: packages/…` (see below). | | `experimental` / `planned` | Declared, intentionally not enforced yet. Also read from a spec `.describe()` marker like `[EXPERIMENTAL — not enforced]`. | | `dead` | Parsed, no consumer **anywhere the census looked**. Tracked for **enforce-or-remove** (ADR-0049). A key that is dead here but enforced in a sibling repo is NOT `dead` — it is `live-elsewhere`, below. | | `live-elsewhere` | Dead here by measurement, **genuinely enforced in a sibling repo** (#13483). Not deletable. The template is `manifest.runtime`, "ENFORCED AT THE CLOUD PUBLISH GATE, NOT ENFORCED AT LOAD" — its load side belongs to this repo's loader. A key whose consumer is the closed cloud runtime is `live` with a `cloud`-attributed pointer instead (`agent.json`'s `_note`). Carries its own gate-executable criteria — see the section below. Ledger-entry-only (no `.describe()` marker). | @@ -116,22 +116,38 @@ enforce-or-remove instead of repointing at a plausible survivor. **A path in another repo must say so**, or the gate will report it as rot — attributed paths are counted and never resolved, so they cannot fail this check -(that boundary is deliberate: ~101 entries cite the renderer repo or the closed -cloud runtime, whose files are legitimately absent here): +(that boundary is deliberate: the closed cloud runtime's files are legitimately +absent here): ```jsonc -"evidence": "objectui: packages/app-shell/src/views/RecordDetailView.tsx:573" -"evidence": "registered into ActionEngine (objectui packages/core/…/ActionEngine.ts:150) but no caller" +"evidence": "cloud @cb8ee7ff: packages/service-ai/src/tools/action-tools.ts#buildParametersSchema" +"evidence": "carried onto the tool definition (cloud packages/service-ai/…/action-tools.ts) but no runtime here" ``` -A realm marker (`objectui`, `cloud`, `ee`) attributes the paths that follow it, -up to the next clause boundary (`;` or `)`), so a string can cite both repos — +A realm marker (`cloud`, `ee`) attributes the paths that follow it, up to the +next clause boundary (`;` or `)`), so a string can cite both repos — `framework` switches back explicitly. `packages/services/service-ai/…` is always treated as foreign: that is the closed cloud runtime, and `packages/services/` here ships every sibling service except it. A path that is not repo-rooted (`app-shell/MetadataProvider.tsx`, `action-button/-group`) reads as prose and is neither resolved nor reported. +**The renderer is not another repo.** objectui merged into this one as `ui/` +(#25677), so its files are cited like any other path — `ui/packages/app-shell/src/views/RecordDetailView.tsx#RecordDetailView` +— and held to the same standard: the file exists, a cited line is inside it, an +anchored symbol is in it, and the file names the key. `objectui` is a **retired** +realm marker (`RETIRED_REALMS` in `scripts/liveness/evidence.mts`): a path still +attributed to it FAILS, with its `ui/` spelling printed as the remedy. It cannot +stay foreign (counted, never resolved, it would never fail) and it cannot be read +as local either: both trees have a `packages/core/`, so `objectui: packages/core/…` +could pass on a file that never was the reader. A path already spelled `ui/…` is +local whatever prose precedes it, so a dated reading may keep naming the old repo — +`ui/packages/layout/src/NavigationRenderer.tsx (:961 at objectui @940ba24)` — and +that is exactly how #25712 re-rooted the 164 renderer rows: a `path:NNN` whose cited +block is byte-identical at one offset of today's file was relocated there; any other +line left the pointer and stayed behind as that dated prose, because a line read at +an old objectui commit is not a line of this checkout. + > This replaces the old advice to write objectui paths "as prose to avoid false > stale-flags." That was a workaround for a parser bug — the check took > `evidence.split(':')[0]` as the filename, so any prose made it fail. It flagged @@ -171,9 +187,11 @@ Two rules, and the asymmetry between them is the point: loudly instead. Most entries predate the field and are simply undated; date them as you -re-verify rather than back-filling guesses. **For objectui-side evidence, pin -the commit** (`objectui @732b1bf`) — `action.undoable`'s reader line numbers had +re-verify rather than back-filling guesses. **For cloud-side evidence, pin the +commit** (`cloud @15f55df`) — `action.undoable`'s objectui reader line numbers had already drifted by 28 lines one day after the issue citing them was filed. +Renderer evidence needs no pin since the merge (#25712): `ui/` is this checkout, +and the gate bounds its lines and anchors on every run. ### `producer` — a consumer is only HALF the call graph (#4837) @@ -259,7 +277,11 @@ barrel package adds a blind spot, and the renderer repo is all barrels. | Value | Means | |---|---| | `in-repo` | the call graph was closed inside this repo only | -| `cross-repo` | a named foreign realm was walked too — say **which**, in the evidence, and **pin the commit** (`objectui @c2fd1223`): `action.undoable`'s reader line numbers drifted 28 lines in one day (#3714) | +| `cross-repo` | a named foreign realm was walked too — say **which**, in the evidence, and **pin the commit** (`cloud @15f55df`): `action.undoable`'s reader line numbers drifted 28 lines in one day (#3714) | + +`in-repo` covers `ui/` — the renderer is this repo since #25712, which moved the +scope of every re-rooted renderer row from `cross-repo` to `in-repo` (a row that +also cites `cloud` kept `cross-repo`; an undeclared scope stayed undeclared). Absent = scope undeclared, a worklist row rather than a failure; the field is younger than nearly every entry. A value outside the vocabulary FAILS, the same @@ -397,16 +419,15 @@ the maintainer settled it directly: **The mechanical rule, for the next sweep.** Before writing `"no runtime consumer"` — or any wording that means it — **enumerate the metadata-admin preview -read points** for the type, in objectui, and say what you found. The population is +read points** for the type, under `ui/`, and say what you found. The population is small, enumerable, and registered by type name, so this is a lookup and not a search: ```bash # 1. does this type have a preview at all? -git -C ../objectui grep -n "registerMetadataPreview(''" origin/main +git grep -n "registerMetadataPreview(''" -- ui/ # 2. what does that preview read off the draft? -git -C ../objectui show origin/main:packages/app-shell/src/views/metadata-admin/previews/Preview.tsx \ - | grep -n 'd\.' +grep -n 'd\.' ui/packages/app-shell/src/views/metadata-admin/previews/Preview.tsx ``` An absent preview is a finding to record, not a step to skip — "the type has no @@ -433,10 +454,10 @@ person. because a human sees it in the designer; the scheduler still stores name/schedule only, and the row says so. A re-grade is **not** an ADR-0033 change: these four rows are docs-shaped, deliberately KEPT, and still not `authorWarn`'d — nothing -about enforce-or-remove moves. Cite the preview the way any cross-repo evidence is -cited: realm marker, pinned objectui commit, and a `producer` naming the -`registerMetadataPreview` call plus the surface that resolves it — a preview no -registry hands a draft to is a read point that never runs. +about enforce-or-remove moves. Cite the preview the way any in-repo evidence is +cited — `ui/…#Symbol` — with a `producer` naming the `registerMetadataPreview` +call plus the surface that resolves it: a preview no registry hands a draft to is +a read point that never runs. ### How to verify a claim without fooling yourself @@ -858,8 +879,8 @@ The governed set is `GOVERNED` at the top of `check-liveness.mts`. To add a type 2. Seed `.json` from that type's liveness audit (file:line evidence) + targeted greps. **Classify only with evidence** — `live` needs a cited consumer; `dead` needs a confirmed absence. -3. **Confirmed absence means BOTH repos.** The renderer layer is a legitimate consumer - (`live` with objectui evidence as prose), so grep `../objectui` before writing `dead`. +3. **Confirmed absence includes the renderer.** The renderer layer is a legitimate + consumer (`live` with `ui/…` evidence), so grep `ui/` before writing `dead`. Precedent: `enable.trackHistory` was misclassified dead for a month while RecordDetailView had been gating the History tab on it the whole time (#2707). 4. Add the type to `GOVERNED`; confirm the gate is green. diff --git a/packages/spec/liveness/action.json b/packages/spec/liveness/action.json index bc31102c686..9d94d65a26d 100644 --- a/packages/spec/liveness/action.json +++ b/packages/spec/liveness/action.json @@ -13,7 +13,7 @@ "description": { "status": "live", "verifiedAt": "2026-08-10", - "evidence": "objectui @b9814b334 packages/app-shell/src/views/ActionParamDialog.tsx#ActionParamDialog renders it as the dialog's DialogDescription, falling back to the generic `actionDialog.description` string; objectui @b9814b334 packages/app-shell/src/hooks/useConsoleActionRuntime.tsx#paramCollectionHandler and objectui @b9814b334 packages/app-shell/src/views/RecordDetailView.tsx#paramCollectionHandler both feed it as `actionDescription(objForI18n, action?.name, action?.description)`; objectui @b9814b334 packages/i18n/src/useObjectLabel.ts#actionDescription resolves it through objectui @b9814b334 packages/i18n/src/useObjectLabel.ts#actionSuffixes, reading objects.{o}._actions.{a}.description for an action with a key object or globalActions.{a}.description for one without, never both, then this literal; framework: packages/cli/src/utils/i18n-extract.ts emits the key and packages/spec/src/system/translation.zod.ts declares the translation slot.", + "evidence": "ui/packages/app-shell/src/views/ActionParamDialog.tsx#ActionParamDialog renders it as the dialog's DialogDescription, falling back to the generic `actionDialog.description` string; ui/packages/app-shell/src/hooks/useConsoleActionRuntime.tsx#paramCollectionHandler and ui/packages/app-shell/src/views/RecordDetailView.tsx#paramCollectionHandler both feed it as `actionDescription(objForI18n, action?.name, action?.description)`; ui/packages/i18n/src/useObjectLabel.ts#actionDescription resolves it through ui/packages/i18n/src/useObjectLabel.ts#actionSuffixes, reading objects.{o}._actions.{a}.description for an action with a key object or globalActions.{a}.description for one without, never both, then this literal; framework: packages/cli/src/utils/i18n-extract.ts emits the key and packages/spec/src/system/translation.zod.ts declares the translation slot.", "note": "Added #7367 — the RENDERER half predates the key by design: the consumer chain above shipped while `ActionSchema` (strictObject) refused `description`, so no producer could reach it (the mirror image of declared-but-unenforced). Live from the moment this key exists: the dialog reads `action.description` directly, no second input required. Mechanism for the 2026-08-10 ruling on #7278 — an action with `params` carries its confirm question here instead of in `confirmText`, so one decision is one dialog. Moved out of spec source, #23434: packages/spec/src/ui/action.zod.ts (this key's docblock) and packages/spec/src/system/translation.zod.ts (the translation slot's comment) cited this chain by line, sha-less. Re-read at objectui @b9814b334 on 2026-10-15: the object-then-global fallback both described, and this entry's evidence with them, is gone — objectui#11439 retired it, so the client resolves one bundle entry the way packages/spec/src/system/i18n-resolver.ts#actionTranslationNode does." }, "objectName": { @@ -30,7 +30,7 @@ "locations": { "status": "live", "verifiedAt": "2026-08-10", - "evidence": "objectui `actionRendersAt` (packages/types/src/ui-action.ts) is the ONE placement predicate since objectui#3142 — `Array.isArray(declared) && declared.includes(location)` — consumed by action-bar.tsx and the record surfaces; `locations: []` therefore renders nowhere, which is the documented headless shape.", + "evidence": "objectui `actionRendersAt` (ui/packages/types/src/ui-action.ts) is the ONE placement predicate since objectui#3142 — `Array.isArray(declared) && declared.includes(location)` — consumed by action-bar.tsx and the record surfaces; `locations: []` therefore renders nowhere, which is the documented headless shape.", "note": "The KEY is live; its VALUE SET was audited per-member in #6888 and `global_nav` was retired (no product surface read it — the console ⌘K palette composes its groups from nav items/objects/dashboards/pages/reports/recents/record search and reads no action metadata; the only renderer of the value was the Studio designer's mock preview frame). An enum-VALUE retirement leaves this row untouched in status: the key is still walked, still authorable and still enforced — see the conversion `action-global-nav-location-removed`." }, "component": { @@ -108,8 +108,8 @@ "outcomeMessages": { "status": "live", "verifiedAt": "2026-10-03", - "evidenceScope": "cross-repo", - "evidence": "objectui @ab1879721595: packages/core/src/actions/ActionRunner.ts#composeSuccessMessage — the success toast's text: `outcomeMessages[outcome]` off the handler's return value, then `successMessage`, then the runner's default text, with `${result.*}` filled in whichever is shown (called from `handlePostExecution`); objectui @ab1879721595: packages/components/src/renderers/action/action-button.tsx#ActionButtonRenderer — forwards the key to the runner, and `action:bar` spreads a registered action onto `action:button` whole (`action-icon.tsx`, `action-group.tsx` and `action-menu.tsx` forward it too); packages/spec/src/ui/action.zod.ts#refuseInertOutcomeMessages (authoring-time: the map is refused on a type with no server response to report an outcome and beside `resultDialog`, which suppresses the toast; the declarative-update table refuses it beside `operation: 'update'`); packages/spec/src/system/i18n-resolver.ts#translateAction (the REST localization seam overlays each DECLARED outcome's translated copy at the action's one address: `_actions..outcomeMessages.` for a bound action, `globalActions` for an object-less one); packages/cli/src/utils/i18n-extract.ts#pushActionOutcomeMessages (one translation key per declared outcome, beside `successMessage`)", + "evidenceScope": "in-repo", + "evidence": "ui/packages/core/src/actions/ActionRunner.ts#composeSuccessMessage — the success toast's text: `outcomeMessages[outcome]` off the handler's return value, then `successMessage`, then the runner's default text, with `${result.*}` filled in whichever is shown (called from `handlePostExecution`); ui/packages/components/src/renderers/action/action-button.tsx#ActionButtonRenderer — forwards the key to the runner, and `action:bar` spreads a registered action onto `action:button` whole (`action-icon.tsx`, `action-group.tsx` and `action-menu.tsx` forward it too); packages/spec/src/ui/action.zod.ts#refuseInertOutcomeMessages (authoring-time: the map is refused on a type with no server response to report an outcome and beside `resultDialog`, which suppresses the toast; the declarative-update table refuses it beside `operation: 'update'`); packages/spec/src/system/i18n-resolver.ts#translateAction (the REST localization seam overlays each DECLARED outcome's translated copy at the action's one address: `_actions..outcomeMessages.` for a bound action, `globalActions` for an object-less one); packages/cli/src/utils/i18n-extract.ts#pushActionOutcomeMessages (one translation key per declared outcome, beside `successMessage`)", "note": "LIVE from the `.objectui-sha` pin ab1879721595 (2026-10-03): the console reader objectstack-ai/objectui#11344 (objectui c476be0e0) is in the pin's range 89cad75d5570..ab1879721595, so the flip this row prescribed lands on the pin bump that carries it, with `authorWarn` / `authorHint` dropped in the same edit. At 89cad75d5570 no objectui source outside `packages/types` named the key (0 hit lines), and `handlePostExecution` toasted `result.data.message || action.successMessage || runner default`; at ab1879721595 the server-message rung is gone and `composeSuccessMessage` selects the outcome copy. An `outcome` reaches it only from a handler that returns one, which a `script` handler's author writes beside the map and an `api` target returns in its body. History: PLANNED, carrier objectstack-ai/objectui#11344 (the console's success toast picks `outcomeMessages[result.outcome]`, interpolates ${result.*}, falls back to `successMessage`, then the runner default). Declared by ruling A on objectstack-ai/cloud#2315 (maintainer 2026-10-01), the mechanism half of ruling B there — the server returns facts, the console composes the message in the user's locale — landing spec+client first (#21095), the console second, cloud's producers LAST. Measured at objectui @e420df31: packages/core/src/actions/ActionRunner.ts#handlePostExecution composes the toast from `result.data.message || action.successMessage || runner default` and reads no `outcomeMessages`; re-read unchanged at objectui @31971ff1 (the `.objectui-sha` pin after the base merge), and objectui main 3c3ce15 has no reader either. The in-repo pieces cited in `evidence` validate, translate and extract the copy; none of them SELECTS or shows it, which is what a `live` verdict would claim. Flip to `live` on the commit that cites the console reader at the `.objectui-sha` pin — and drop `authorWarn` / `authorHint` in that same edit: a warned `live` row reds this gate (#21127). Re-read at objectui @b9814b334 on 2026-10-14 (the record moved out of packages/spec/src/ui/action.zod.ts, #23434): objectui @b9814b334 packages/core/src/actions/ActionRunner.ts#composeSuccessMessage unchanged; at objectui @89cad75d5570 no objectui source outside its types named the key." }, "errorMessage": { @@ -174,22 +174,22 @@ "bodyShape": { "status": "live", "verifiedAt": "2026-08-25", - "evidenceScope": "cross-repo", - "evidence": "objectui @a76b18cf2: packages/app-shell/src/hooks/useConsoleActionRuntime.tsx:359-360 reads action.bodyShape.wrap and nests the payload under that key, and objectui packages/components/src/renderers/action/action-button.tsx:173 forwards it off the schema onto that runtime call (siblings action-icon.tsx:114, action-group.tsx:264, action-menu.tsx:244)", + "evidenceScope": "in-repo", + "evidence": "ui/packages/app-shell/src/hooks/useConsoleActionRuntime.tsx:446-447 reads action.bodyShape.wrap and nests the payload under that key, and ui/packages/components/src/renderers/action/action-button.tsx:311 forwards it off the schema onto that runtime call (siblings action-icon.tsx:185, action-group.tsx:365, action-menu.tsx:298)", "note": "{wrap} request shaping. 2026-08-25: REPOINTED, and the repoint CHANGED THE REALM — the evidence cited packages/runtime/src/http-dispatcher.ts, which has 0 occurrences of `bodyShape`, and so does every other file in this repo outside the schema/conversions/CLI declaration sites. `type:'api'` actions are client-dispatched by design: action-execution.ts:468-477 refuses them server-side with the message \"it dispatches on `target`, not through the action registry\". So the only consumer this key has ever had is the renderer, and the in-repo citation could not have been right at any point." }, "method": { "status": "live", "verifiedAt": "2026-08-26", - "evidenceScope": "cross-repo", - "evidence": "objectui @7b6f7f889: packages/core/src/actions/ActionRunner.ts:1774 (`method = action.method || 'POST'` on the string-endpoint branch of executeAPI) and :1780 (`config.method || action.method` on the ApiConfig branch), both landing on the real request at :1800 `const fetchInit: RequestInit = { method, headers }` and gating payload attachment at :1795/:1801", + "evidenceScope": "in-repo", + "evidence": "ui/packages/core/src/actions/ActionRunner.ts:2692 (`method = action.method || 'POST'` on the string-endpoint branch of executeAPI) and :2698 (`config.method || action.method` on the ApiConfig branch), both landing on the real request at :2718 `const fetchInit: RequestInit = { method, headers }` and gating payload attachment at :2713/:2719", "note": "The HTTP verb of an inline `type:'api'` action. 2026-08-26: REPOINTED, and the repoint CHANGED THE REALM for exactly the reason its siblings `bodyShape`/`bodyExtra` did — `type:'api'` actions are client-dispatched by design, and action-execution.ts:557-563 (the `api` branch of headlessActionTypeError, :551-568) refuses them server-side with the message \"it dispatches on `target`, not through the action registry\". So packages/runtime/src/http-dispatcher.ts never read this key at any point: its 41 word-`method` occurrences are the inbound HTTP verb (15 `method: string` parameter declarations on the domain delegates, plus route matching such as `method === 'GET'` at :2067 and the log line at :2178) and the ordinary object-oriented sense in prose (\"this method\", :837/:2047/:2108/:2135). The ONE in-repo occurrence of `action.method` — action-execution.ts:561 — is a DIAGNOSTIC that interpolates the verb into the very refusal explaining the server does NOT dispatch it, so it is evidence of non-consumption and deliberately not cited above as a consumer. WHY THE GATE COULD NOT SEE THE ROT: `method` is this dispatcher's most-used noun, so the word-bounded key-mention check anchors incidentally and passes — the designed, honest limit of that signal." }, "bodyExtra": { "status": "live", "verifiedAt": "2026-08-25", - "evidenceScope": "cross-repo", - "evidence": "objectui @a76b18cf2: packages/app-shell/src/hooks/useConsoleActionRuntime.tsx:397-398 merges action.bodyExtra into the request body after resolving page-var tokens, and :474-475 is the form-submit branch merging it into `fields`, with objectui packages/components/src/renderers/action/action-button.tsx:166 forwarding it off the schema (siblings action-icon.tsx:112, action-group.tsx:262, action-menu.tsx:242)", + "evidenceScope": "in-repo", + "evidence": "ui/packages/app-shell/src/hooks/useConsoleActionRuntime.tsx:484-485 merges action.bodyExtra into the request body after resolving page-var tokens, and :562-563 is the form-submit branch merging it into `fields`, with ui/packages/components/src/renderers/action/action-button.tsx:304 forwarding it off the schema (siblings action-icon.tsx:183, action-group.tsx:363, action-menu.tsx:296)", "note": "The payload key for an inline `type:'api'` action — `params` stays the ActionParam[] definition array (#5777; the object form of `params` is converted to `bodyExtra` by the ADR-0087 entry in packages/spec/src/conversions/registry.ts:5578). 2026-08-25: REPOINTED to the renderer for the same reason as its sibling `bodyShape` — `type:'api'` actions are client-dispatched, so packages/runtime/src/http-dispatcher.ts never read this key." }, "mode": { @@ -214,17 +214,17 @@ "navigate": { "status": "live", "verifiedAt": "2026-09-27", - "evidenceScope": "cross-repo", - "evidence": "objectui @f8a9d0fb: packages/core/src/actions/ActionRunner.ts#handlePostExecution (on a successful action, `readOnSuccessNavigation(action.onSuccess)` shape-guards the block and hands it to `navigateOnSuccess`); objectui @f8a9d0fb: packages/core/src/actions/ActionRunner.ts#navigateOnSuccess (interpolates `block.navigate` through `interpolateTarget` with the `${param.*}`, `${ctx.*}` and `${result.*}` scopes, the last one being the handler's own return value, refuses a URL that is neither http(s) nor relative, and hands the result to the host's `navigationHandler`); objectui @f8a9d0fb: packages/app-shell/src/hooks/useConsoleActionRuntime.tsx#useConsoleActionRuntime (the console's `navigateHandler`, wired as the runner's `onNavigate`: a relative URL is a react-router `navigate`, an external or new-tab one opens a window)", - "producer": "objectui @f8a9d0fb: packages/components/src/renderers/action/action-button.tsx#ActionButtonRenderer (forwards `onSuccess: schema.onSuccess` into the def the runner executes); objectui @f8a9d0fb: packages/components/src/renderers/action/action-icon.tsx#ActionIconRenderer (the same forward); objectui @f8a9d0fb: packages/components/src/renderers/action/action-menu.tsx#ActionMenuRenderer (the same forward); objectui @f8a9d0fb: packages/components/src/renderers/action/action-group.tsx#ActionGroupRenderer (the same forward); objectui @f8a9d0fb: packages/app-shell/src/views/DeclaredActionsBar.tsx#DeclaredActionButton (spreads the declared action, `onSuccess` included, into its dispatch)", + "evidenceScope": "in-repo", + "evidence": "ui/packages/core/src/actions/ActionRunner.ts#handlePostExecution (on a successful action, `readOnSuccessNavigation(action.onSuccess)` shape-guards the block and hands it to `navigateOnSuccess`); ui/packages/core/src/actions/ActionRunner.ts#navigateOnSuccess (interpolates `block.navigate` through `interpolateTarget` with the `${param.*}`, `${ctx.*}` and `${result.*}` scopes, the last one being the handler's own return value, refuses a URL that is neither http(s) nor relative, and hands the result to the host's `navigationHandler`); ui/packages/app-shell/src/hooks/useConsoleActionRuntime.tsx#useConsoleActionRuntime (the console's `navigateHandler`, wired as the runner's `onNavigate`: a relative URL is a react-router `navigate`, an external or new-tab one opens a window)", + "producer": "ui/packages/components/src/renderers/action/action-button.tsx#ActionButtonRenderer (forwards `onSuccess: schema.onSuccess` into the def the runner executes); ui/packages/components/src/renderers/action/action-icon.tsx#ActionIconRenderer (the same forward); ui/packages/components/src/renderers/action/action-menu.tsx#ActionMenuRenderer (the same forward); ui/packages/components/src/renderers/action/action-group.tsx#ActionGroupRenderer (the same forward); ui/packages/app-shell/src/views/DeclaredActionsBar.tsx#DeclaredActionButton (spreads the declared action, `onSuccess` included, into its dispatch)", "note": "FLIPPED planned → live 2026-09-27 (#20296). It was PLANNED under the #9340 map / #9463 viewMode convention for a spec-first contract-split key: the #9566/#9474 maintainer ruling (2026-08-18) declared one post-success route/URL template for type:'api'/'script' actions whose interpolation scope gains `${result.*}` (the server response) beside `${param.*}`/`${ctx.*}`, and at spec/console 17.0.0 no console consumer read it (consoleServerAction drove only the handler-return `{ redirectUrl }` convention, and interpolateTarget had no `result` member). The objectui half has since landed (objectui#5221 for the runner hop, objectui#5493 for the renderer forward, and objectui#5934, which retired the runner's older chained-callback meaning of this key), and the `.objectui-sha` pin f8a9d0fb carries it, so every pointer above was read at the pin this repo builds against. Each read is unchanged at objectui main 256b4c9e. The hop sits in `handlePostExecution`, after the executor for every action type, so it is not an executeAPI branch: `api` actions reach it through the console's `api` handler and `script` actions through its server-action handler. That handler (objectui app-shell consoleServerAction.ts) defers to a declared block rather than also following a handler-returned `redirectUrl`, so one success makes one navigation; the schema refuses `onSuccess` beside `opensInNewTab: true`. `${result.*}` reads `readActionPayload(result.data)`, one level below the action envelope, and `navigateOnSuccess` is the only caller that passes a result scope. MOUNT CHAIN, closed by hand at the pin: the console root (apps/console App.tsx) mounts `ConsoleShell`, whose `useConsoleActionRuntime` feeds ``; @object-ui/react's ActionContext builds the `ActionRunner` and calls `setNavigationHandler(onNavigate)`. A declared action rendered by `action:button` / `action:icon` / `action:menu` / `action:group` (ObjectView's list toolbar renders `action:bar`) or by DeclaredActionsBar reaches `ActionRunner.execute` with `onSuccess` on the def (`producer`). Executor pairing (packages/spec/src/ui/action-on-success-executor.ts ON_SUCCESS_EXECUTORS; read points moved out of spec source, #23434), measured at objectui @1fb1ced36243 and byte-identical at objectui main @72594f29: every branch of objectui @1fb1ced36243 packages/core/src/actions/ActionRunner.ts#execute — a host-registered handler, a built-in, and the declarative `operation: 'update'`, which is read before any executor — ends in objectui @1fb1ced36243 packages/core/src/actions/ActionRunner.ts#handlePostExecution, which hands `onSuccess` to objectui @1fb1ced36243 packages/core/src/actions/ActionRunner.ts#readOnSuccessNavigation and then `navigateOnSuccess`." }, "openIn": { "status": "live", "verifiedAt": "2026-09-27", - "evidenceScope": "cross-repo", - "evidence": "objectui @f8a9d0fb: packages/core/src/actions/ActionRunner.ts#navigateOnSuccess (`const newTab = block.openIn === 'newTab'` reads the one member that changes the branch, with no fallback of its own, and passes `newTab` to the host's `navigationHandler`, or opens a window itself when no handler is registered); objectui @f8a9d0fb: packages/app-shell/src/hooks/useConsoleActionRuntime.tsx#useConsoleActionRuntime (the console's `navigateHandler` opens a new window on `newTab`, and otherwise makes a react-router `navigate`)", - "producer": "packages/spec/src/ui/action.zod.ts#ActionSchema (materializes `.default('self')` at parse, so parsed metadata always carries the resolved member); objectui @f8a9d0fb: packages/components/src/renderers/action/action-button.tsx#ActionButtonRenderer (forwards the whole `onSuccess` block, `openIn` included, into the def the runner executes; action-icon, action-menu, action-group and DeclaredActionsBar forward it the same way, see the `navigate` row)", + "evidenceScope": "in-repo", + "evidence": "ui/packages/core/src/actions/ActionRunner.ts#navigateOnSuccess (`const newTab = block.openIn === 'newTab'` reads the one member that changes the branch, with no fallback of its own, and passes `newTab` to the host's `navigationHandler`, or opens a window itself when no handler is registered); ui/packages/app-shell/src/hooks/useConsoleActionRuntime.tsx#useConsoleActionRuntime (the console's `navigateHandler` opens a new window on `newTab`, and otherwise makes a react-router `navigate`)", + "producer": "packages/spec/src/ui/action.zod.ts#ActionSchema (materializes `.default('self')` at parse, so parsed metadata always carries the resolved member); ui/packages/components/src/renderers/action/action-button.tsx#ActionButtonRenderer (forwards the whole `onSuccess` block, `openIn` included, into the def the runner executes; action-icon, action-menu, action-group and DeclaredActionsBar forward it the same way, see the `navigate` row)", "note": "FLIPPED planned → live 2026-09-27 (#20296), together with its sibling `navigate`, whose note carries the history (planned under the #9340 / #9463 spec-first convention, Blocked-by #9566/#9474), the objectui cards that landed the reader, and the mount chain. Everything here was read at the `.objectui-sha` pin f8a9d0fb and is unchanged at objectui main 256b4c9e. The closed enum 'self'|'newTab' carries a MATERIALIZED .default('self'), and the reader relies on it as designed: it tests `=== 'newTab'` and writes no `?? 'self'`, so the default has one source of truth. A stored row rehydrated unparsed (#3903) that omits the key lands on the same branch, the in-place hop. The two `openIn` spellings never cross: the reader reads `onSuccess.openIn` only, never the top-level `type:'url'` switch spelled 'new-tab'. The shipped handler-return surface `{ redirectUrl, openIn? }` keeps its 17.0.0 semantics at the pin (absent openIn ⇒ new tab; objectui app-shell consoleServerAction.ts), and only this schema key defaults 'self'." } } diff --git a/packages/spec/liveness/app.json b/packages/spec/liveness/app.json index b2e5d970dec..2e2b75cda72 100644 --- a/packages/spec/liveness/app.json +++ b/packages/spec/liveness/app.json @@ -11,22 +11,22 @@ "label": { "status": "live", "verifiedAt": "2026-09-27", - "evidenceScope": "cross-repo", - "evidence": "packages/spec/src/system/i18n-resolver.ts#lookupAppAttr (reads `apps..label` down the locale chain); packages/spec/src/system/i18n-resolver.ts#translateApp (applies it over the authored value on every /meta app read); objectui @f8a9d0fb: packages/app-shell/src/layout/AppSwitcher.tsx#AppSwitcher (`resolveKeyedI18nLabel(activeApp.label, t)` on the pill, and `app.label` on each switcher row); objectui @f8a9d0fb: packages/app-shell/src/layout/ConsoleLayout.tsx#ConsoleLayout (`appLabel` into `AppHeader`, and the `AppShell` branding title); objectui @f8a9d0fb: packages/app-shell/src/console/home/HomeAppsStrip.tsx#HomeAppsStrip (the launcher tiles)", + "evidenceScope": "in-repo", + "evidence": "packages/spec/src/system/i18n-resolver.ts#lookupAppAttr (reads `apps..label` down the locale chain); packages/spec/src/system/i18n-resolver.ts#translateApp (applies it over the authored value on every /meta app read); ui/packages/app-shell/src/layout/AppSwitcher.tsx#AppSwitcher (`resolveKeyedI18nLabel(activeApp.label, t)` on the pill, and `app.label` on each switcher row); ui/packages/app-shell/src/layout/ConsoleLayout.tsx#ConsoleLayout (`appLabel` into `AppHeader`, and the `AppShell` branding title); ui/packages/app-shell/src/console/home/HomeAppsStrip.tsx#HomeAppsStrip (the launcher tiles)", "note": "localized on serve by translateApp; rendered by the App Switcher (mounted in `AppHeader`), the console header and the tab title (`ConsoleLayout`), and Home's launcher tiles (`HomeAppsStrip`). 2026-09-27 (#20146): REPOINTED off objectui's `AppSidebar.tsx` — deprecated, never mounted (the console renders `UnifiedSidebar`), and removed from objectui main by objectui PR #10617 — to the mounted reader(s) above, each re-read at the `.objectui-sha` pin f8a9d0fb and unchanged at objectui main fb91ac9b0. localized on serve by translateApp, rendered by the app switcher and shell header. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:481` had rotted onto `if (!spec) return spec;`, a guard on the ACTION result-dialog path. Re-closed by hand against 8cb96ec41." }, "description": { "status": "live", "verifiedAt": "2026-09-27", - "evidenceScope": "cross-repo", - "evidence": "objectui @f8a9d0fb: apps/console/src/pages/system/AppManagementPage.tsx#AppManagementPage (each row of the `/system/apps` list renders `resolveKeyedI18nLabel(app.description, t)` under the app's title, and `filteredApps` matches the search query against the same resolved text); framework packages/spec/src/system/i18n-resolver.ts#lookupAppAttr (reads `apps..description`); packages/spec/src/system/i18n-resolver.ts#translateApp", + "evidenceScope": "in-repo", + "evidence": "ui/apps/console/src/pages/system/AppManagementPage.tsx#AppManagementPage (each row of the `/system/apps` list renders `resolveKeyedI18nLabel(app.description, t)` under the app's title, and `filteredApps` matches the search query against the same resolved text); framework packages/spec/src/system/i18n-resolver.ts#lookupAppAttr (reads `apps..description`); packages/spec/src/system/i18n-resolver.ts#translateApp", "note": "LIVE, but narrower than this row used to say: the one mounted reader is the admin app list at `/system/apps` (`AppManagementPage`), which shows the localized description under each app's title and searches it; localized by translateApp. The presentation this row used to cite, the description under the active app's title in the sidebar, has NO successor: that was AppSidebar, and it was never mounted. Not a reader: objectui's app-shell `AppCard` also reads `app.description`, but at the pin it is only barrel-exported, with no mount site, which is the same trap AppSidebar was. 2026-09-27 (#20146): REPOINTED off objectui's `AppSidebar.tsx` — deprecated, never mounted (the console renders `UnifiedSidebar`), and removed from objectui main by objectui PR #10617 — to the mounted reader(s) above, each re-read at the `.objectui-sha` pin f8a9d0fb and unchanged at objectui main fb91ac9b0. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:482` had rotted onto the `title` lookup inside `resolveActionResultDialog`; it, `:481` (label) and `:478` (name) were three consecutive lines of ONE unrelated function, which is what a block of pointers looks like after the file above them grows. Re-closed by hand against 8cb96ec41." }, "icon": { "status": "live", "verifiedAt": "2026-09-27", - "evidenceScope": "cross-repo", - "evidence": "objectui @f8a9d0fb: packages/app-shell/src/layout/AppSwitcher.tsx#AppSwitcher (`getIcon(app.icon)` on each switcher row); objectui @f8a9d0fb: packages/app-shell/src/console/home/HomeAppsStrip.tsx#HomeAppsStrip (`getIcon(app.icon)` on each launcher tile)", + "evidenceScope": "in-repo", + "evidence": "ui/packages/app-shell/src/layout/AppSwitcher.tsx#AppSwitcher (`getIcon(app.icon)` on each switcher row); ui/packages/app-shell/src/console/home/HomeAppsStrip.tsx#HomeAppsStrip (`getIcon(app.icon)` on each launcher tile)", "note": "App Launcher / switcher icon. 2026-09-27 (#20146): REPOINTED off objectui's `AppSidebar.tsx` — deprecated, never mounted (the console renders `UnifiedSidebar`), and removed from objectui main by objectui PR #10617 — to the mounted reader(s) above, each re-read at the `.objectui-sha` pin f8a9d0fb and unchanged at objectui main fb91ac9b0." }, "branding": { @@ -34,30 +34,30 @@ "primaryColor": { "status": "live", "verifiedAt": "2026-09-27", - "evidenceScope": "cross-repo", - "evidence": "objectui @f8a9d0fb: packages/app-shell/src/layout/ConsoleLayout.tsx#ConsoleLayout (hands `activeApp.branding.primaryColor` to `AppShell`'s `branding` prop); objectui @f8a9d0fb: packages/layout/src/AppShell.tsx#useAppShellBranding (maps it onto the `--primary`, `--ring` and `--sidebar-primary` theme tokens, with a dark-mode variant)", + "evidenceScope": "in-repo", + "evidence": "ui/packages/app-shell/src/layout/ConsoleLayout.tsx#ConsoleLayout (hands `activeApp.branding.primaryColor` to `AppShell`'s `branding` prop); ui/packages/layout/src/AppShell.tsx#useAppShellBranding (maps it onto the `--primary`, `--ring` and `--sidebar-primary` theme tokens, with a dark-mode variant)", "note": "the app's brand colour, applied to the whole console while the app is active." }, "accentColor": { "status": "live", "verifiedAt": "2026-09-27", - "evidenceScope": "cross-repo", - "evidence": "objectui @f8a9d0fb: packages/app-shell/src/layout/ConsoleLayout.tsx#ConsoleLayout (hands `activeApp.branding.accentColor` to `AppShell`'s `branding` prop); objectui @f8a9d0fb: packages/layout/src/AppShell.tsx#useAppShellBranding (maps it onto the `--accent` theme tokens)", + "evidenceScope": "in-repo", + "evidence": "ui/packages/app-shell/src/layout/ConsoleLayout.tsx#ConsoleLayout (hands `activeApp.branding.accentColor` to `AppShell`'s `branding` prop); ui/packages/layout/src/AppShell.tsx#useAppShellBranding (maps it onto the `--accent` theme tokens)", "note": "declared to match an existing objectui read (inverse-drift fix, liveness audit #1878/#1891/#1894)." }, "favicon": { "status": "live", "verifiedAt": "2026-09-27", - "evidenceScope": "cross-repo", - "evidence": "objectui @f8a9d0fb: packages/app-shell/src/layout/ConsoleLayout.tsx#ConsoleLayout (hands `activeApp.branding.favicon` to `AppShell`'s `branding` prop); objectui @f8a9d0fb: packages/layout/src/AppShell.tsx#useAppShellBranding (sets the favicon link's href)", + "evidenceScope": "in-repo", + "evidence": "ui/packages/app-shell/src/layout/ConsoleLayout.tsx#ConsoleLayout (hands `activeApp.branding.favicon` to `AppShell`'s `branding` prop); ui/packages/layout/src/AppShell.tsx#useAppShellBranding (sets the favicon link's href)", "note": "the tab icon while the app is active." }, "logo": { "status": "live", "verifiedAt": "2026-10-16", - "evidenceScope": "cross-repo", - "evidence": "objectui @daeb77fc: packages/app-shell/src/layout/AppMark.tsx#AppMark (reads `branding.logo` and draws it as an `object-contain` image at the surface's size, falling back to the `icon` tile when it is absent or fails to load); objectui @daeb77fc: packages/app-shell/src/layout/AppSwitcher.tsx#AppSwitcher (`AppMark` at 22 px on the top-bar app pill and on every switcher row; the switcher is mounted in `AppHeader`); objectui @daeb77fc: packages/app-shell/src/layout/UnifiedSidebar.tsx#UnifiedSidebar (`AppMark` at 26 px in the phone drawer header)", - "producer": "objectui @daeb77fc: packages/plugin-designer/src/AppCreationWizard.tsx#AppCreationWizard (writes `branding.logo`, with a preview); objectui @daeb77fc: packages/plugin-designer/src/BrandingEditor.tsx#BrandingEditor (writes it, with a preview); objectui @daeb77fc: packages/plugin-designer/src/pages/EditAppPage.tsx#EditAppPage (pre-fills the editor from it)", + "evidenceScope": "in-repo", + "evidence": "ui/packages/app-shell/src/layout/AppMark.tsx#AppMark (reads `branding.logo` and draws it as an `object-contain` image at the surface's size, falling back to the `icon` tile when it is absent or fails to load); ui/packages/app-shell/src/layout/AppSwitcher.tsx#AppSwitcher (`AppMark` at 22 px on the top-bar app pill and on every switcher row; the switcher is mounted in `AppHeader`); ui/packages/app-shell/src/layout/UnifiedSidebar.tsx#UnifiedSidebar (`AppMark` at 26 px in the phone drawer header)", + "producer": "ui/packages/plugin-designer/src/AppCreationWizard.tsx#AppCreationWizard (writes `branding.logo`, with a preview); ui/packages/plugin-designer/src/BrandingEditor.tsx#BrandingEditor (writes it, with a preview); ui/packages/plugin-designer/src/pages/EditAppPage.tsx#EditAppPage (pre-fills the editor from it)", "note": "the app's one square (1:1) icon, an image URL: objectui's `AppMark` renders it on the top-bar app pill, on each App Switcher row and in the phone drawer header, each at its own size, and an app without it gets its `icon` on a tile. Read at the `.objectui-sha` pin daeb77fc. 2026-10-16: FLIPPED from `planned` — the reader shipped under objectui#12201, after the 2026-09-27 drill found `branding.logo` authored by the designer (`producer`) and rendered by nothing mounted. ONE SPELLING: `AppSchema` declares only `branding.logo`. An app-level `logo` is REFUSED by the strict app shape (`unrecognized_keys`), and the refusal suggests `branding` through the alias table; it is not folded or rewritten, and no ADR-0087 conversion carries it." } }, @@ -66,21 +66,21 @@ "active": { "status": "live", "verifiedAt": "2026-09-27", - "evidenceScope": "cross-repo", - "evidence": "objectui @f8a9d0fb: packages/app-shell/src/layout/AppSwitcher.tsx#AppSwitcher (`a.active !== false && a.hidden !== true` builds the switcher list); objectui @f8a9d0fb: packages/app-shell/src/layout/UnifiedSidebar.tsx#UnifiedSidebar (the same filter, and the active-app lookup over the apps with `active !== false`); objectui @f8a9d0fb: packages/app-shell/src/utils/appRoute.ts#filterActiveApps (the shared openable-apps predicate: Home's launcher, the `app-launcher` renderer and `resolveHostAppSegment` call it); objectui @f8a9d0fb: packages/app-shell/src/layout/AppHeader.tsx#AppHeader (the avatar menu lists only apps with `active !== false`)", + "evidenceScope": "in-repo", + "evidence": "ui/packages/app-shell/src/layout/AppSwitcher.tsx#AppSwitcher (`a.active !== false && a.hidden !== true` builds the switcher list); ui/packages/app-shell/src/layout/UnifiedSidebar.tsx#UnifiedSidebar (the same filter, and the active-app lookup over the apps with `active !== false`); ui/packages/app-shell/src/utils/appRoute.ts#filterActiveApps (the shared openable-apps predicate: Home's launcher, the `app-launcher` renderer and `resolveHostAppSegment` call it); ui/packages/app-shell/src/layout/AppHeader.tsx#AppHeader (the avatar menu lists only apps with `active !== false`)", "note": "`active: false` delists the app from the App Switcher, the sidebar, Home's launcher, the avatar menu and every link `resolveHostAppSegment` builds into an app, and the sidebar's active-app lookup skips it. Deliberately does NOT disable routing: the console's route resolver (objectui app-shell `AppContent`) matches the URL segment against ALL apps with `matchAppBySegment(apps, appName)`, so a direct /apps/ URL keeps rendering the app. Weaker than the name implies, but a real consumer. CORRECTED 2026-09-27: this note used to place the \"lookup spans all apps\" behaviour in the sidebar, citing an AppSidebar comment. Measured at the pin, that comment is about HIDDEN apps; the sidebar lookup excludes `active: false`, and what keeps an inactive app routable is `AppContent`. 2026-09-27 (#20146): REPOINTED off objectui's `AppSidebar.tsx` — deprecated, never mounted (the console renders `UnifiedSidebar`), and removed from objectui main by objectui PR #10617 — to the mounted reader(s) above, each re-read at the `.objectui-sha` pin f8a9d0fb and unchanged at objectui main fb91ac9b0." }, "isDefault": { "status": "live", "verifiedAt": "2026-08-01", - "evidence": "objectui @940ba24: apps/console/src/components/RootLandingRedirect.tsx:46", + "evidence": "ui/apps/console/src/components/RootLandingRedirect.tsx (:46 at objectui @940ba24)", "note": "ROUTING semantics: the root landing redirects to the app marked default (it was once a display-only badge — the file says so)." }, "hidden": { "status": "live", "verifiedAt": "2026-09-27", - "evidenceScope": "cross-repo", - "evidence": "objectui @f8a9d0fb: packages/app-shell/src/layout/AppSwitcher.tsx#AppSwitcher (`a.hidden !== true` keeps a hidden app out of the switcher list); objectui @f8a9d0fb: packages/app-shell/src/layout/UnifiedSidebar.tsx#UnifiedSidebar (the same filter; its active-app lookup still spans hidden apps, so /apps/account renders); objectui @f8a9d0fb: packages/app-shell/src/utils/appRoute.ts#filterActiveApps (Home's launcher and the `app-launcher` renderer); objectui @f8a9d0fb: packages/app-shell/src/layout/AppHeader.tsx#AppHeader (the avatar menu lists the apps with `hidden === true`, Account excepted because the Profile link already stands for it); packages/platform-objects/src/apps/account.app.ts#ACCOUNT_APP (the canonical author — `hidden: true` under the comment that states why: surface via the avatar dropdown, not the App Switcher)", + "evidenceScope": "in-repo", + "evidence": "ui/packages/app-shell/src/layout/AppSwitcher.tsx#AppSwitcher (`a.hidden !== true` keeps a hidden app out of the switcher list); ui/packages/app-shell/src/layout/UnifiedSidebar.tsx#UnifiedSidebar (the same filter; its active-app lookup still spans hidden apps, so /apps/account renders); ui/packages/app-shell/src/utils/appRoute.ts#filterActiveApps (Home's launcher and the `app-launcher` renderer); ui/packages/app-shell/src/layout/AppHeader.tsx#AppHeader (the avatar menu lists the apps with `hidden === true`, Account excepted because the Profile link already stands for it); packages/platform-objects/src/apps/account.app.ts#ACCOUNT_APP (the canonical author — `hidden: true` under the comment that states why: surface via the avatar dropdown, not the App Switcher)", "note": "NAVIGATION PRESENTATION ONLY, and re-verified as such at #4829: keep the app out of the App Switcher; the shell surfaces it from the avatar menu (the built-in Account app is the canonical author). It is NOT an access gate — a hidden app stays fully routable and permission-checked for every user, which is its birth contract in app.zod.ts. Between ADR-0045 (2026-06-12) and its 2026-08-09 amendment the REST gate ALSO read this key as \"unpublished\", which erased the Account app from GET /meta/app for every non-builder; that reading now lives on `_unpublished`. The consumer is therefore the client switcher alone. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) — the framework pointer was ACCURATE (`account.app.ts:40` is still `hidden: true`), so this leg is the grammar migration and not a repair. What the anchor buys is that it survives the file growing above it, which is exactly how this ledger's sibling citations rotted. Re-closed by hand against 8cb96ec41. 2026-09-27 (#20146): REPOINTED off objectui's `AppSidebar.tsx` — deprecated, never mounted (the console renders `UnifiedSidebar`), and removed from objectui main by objectui PR #10617 — to the mounted reader(s) above, each re-read at the `.objectui-sha` pin f8a9d0fb and unchanged at objectui main fb91ac9b0. The avatar-menu surface this note names is now cited too (`AppHeader`)." }, "navigation": { @@ -88,44 +88,44 @@ "id": { "status": "live", "verifiedAt": "2026-09-27", - "evidenceScope": "cross-repo", - "evidence": "objectui @f8a9d0fb: packages/app-shell/src/layout/UnifiedSidebar.tsx#useNavOrder (persists the drag-reordered item ids per app); objectui @f8a9d0fb: packages/app-shell/src/layout/UnifiedSidebar.tsx#UnifiedSidebar (`useNavPins` pins by item id); objectui @f8a9d0fb: packages/layout/src/NavigationRenderer.tsx#NavigationRenderer (the sortable id, pin toggles, render keys, and the active-row match); objectui @f8a9d0fb: packages/app-shell/src/views/nav-menu-renderer.tsx#NavMenuRenderer (row keys, and the active row through `resolveActiveNavItem`)", - "note": "item identity: render keys, pin/reorder persistence, i18n nav key (`apps..navigation..label`). 2026-09-27 (#20146): REPOINTED off objectui's `AppSidebar.tsx` — deprecated, never mounted (the console renders `UnifiedSidebar`), and removed from objectui main by objectui PR #10617 — to the mounted reader(s) above, each re-read at the `.objectui-sha` pin f8a9d0fb and unchanged at objectui main fb91ac9b0." + "evidenceScope": "in-repo", + "evidence": "ui/packages/app-shell/src/layout/UnifiedSidebar.tsx#UnifiedSidebar (`useNavPins` pins by item id); ui/packages/layout/src/NavigationRenderer.tsx#NavigationRenderer (the sortable id, pin toggles, render keys, and the active-row match); ui/packages/app-shell/src/views/nav-menu-renderer.tsx#NavMenuRenderer (row keys, and the active row through `resolveActiveNavItem`)", + "note": "item identity: render keys, pin/reorder persistence, i18n nav key (`apps..navigation..label`). 2026-09-27 (#20146): REPOINTED off objectui's `AppSidebar.tsx` — deprecated, never mounted (the console renders `UnifiedSidebar`), and removed from objectui main by objectui PR #10617 — to the mounted reader(s) above, each re-read at the `.objectui-sha` pin f8a9d0fb and unchanged at objectui main fb91ac9b0. 2026-10-26 (#25712, objectui merged into this repo as ui/): the `UnifiedSidebar.tsx#useNavOrder` clause (\"persists the drag-reordered item ids per app\") was dropped, not repointed: objectui#12059 made the runtime sidebar menu non-reorderable (the Pinned section keeps the user's order), and the hook left with it. The other three readers were re-rooted under ui/ as cited." }, "label": { "status": "live", "verifiedAt": "2026-08-28", - "evidence": "packages/spec/src/system/i18n-resolver.ts#lookupNavLabel (reads `apps..navigation..label` — keyed by the node id, one flat keyspace regardless of tree depth); packages/spec/src/system/i18n-resolver.ts#translateApp (walks the tree and swaps the label in); objectui @940ba24: packages/layout/src/NavigationRenderer.tsx:284-316 (resolveNavItemLabel)", + "evidence": "packages/spec/src/system/i18n-resolver.ts#lookupNavLabel (reads `apps..navigation..label` — keyed by the node id, one flat keyspace regardless of tree depth); packages/spec/src/system/i18n-resolver.ts#translateApp (walks the tree and swaps the label in); ui/packages/layout/src/NavigationRenderer.tsx (:284-316 at objectui @940ba24) (resolveNavItemLabel)", "note": "rendered everywhere; translateApp swaps in the per-locale label by node id. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:456` had rotted onto `opts?: ResolveOptions` in `lookupActionResultDialogText`'s signature, ~195 lines above `lookupNavLabel`. Re-closed by hand against 8cb96ec41." }, "icon": { "status": "live", "verifiedAt": "2026-08-01", - "evidence": "objectui @940ba24: packages/layout/src/NavigationRenderer.tsx:961", + "evidence": "ui/packages/layout/src/NavigationRenderer.tsx (:961 at objectui @940ba24)", "note": "every variant branch resolves and renders it." }, "order": { "status": "live", "verifiedAt": "2026-08-01", - "evidence": "objectui @940ba24: packages/layout/src/NavigationRenderer.tsx:1154, :913", + "evidence": "ui/packages/layout/src/NavigationRenderer.tsx (:1154 at objectui @940ba24), :913", "note": "low-first stable sort at the top level and inside each group." }, "badge": { "status": "live", "verifiedAt": "2026-08-01", - "evidence": "objectui @940ba24: packages/layout/src/NavigationRenderer.tsx:983-985" + "evidence": "ui/packages/layout/src/NavigationRenderer.tsx (:983-985 at objectui @940ba24)" }, "badgeVariant": { "status": "live", "verifiedAt": "2026-08-01", - "evidence": "objectui @940ba24: packages/layout/src/NavigationRenderer.tsx:983, :1024", + "evidence": "ui/packages/layout/src/NavigationRenderer.tsx (:983 at objectui @940ba24), :1024", "note": "declared to match this exact read (inverse-drift fix, audit #1878/#1891/#1894)." }, "visible": { "status": "live", "verifiedAt": "2026-09-27", - "evidenceScope": "cross-repo", - "evidence": "objectui @940ba24: packages/layout/src/NavigationRenderer.tsx:891 (item gate); objectui @f8a9d0fb: packages/app-shell/src/layout/UnifiedSidebar.tsx#UnifiedSidebar (`evalVis`, the CEL evaluation through the ExpressionProvider's `evaluateVisibility`, handed to `NavigationRenderer` and to `hasVisibleNavigationItems`); objectui @f8a9d0fb: packages/app-shell/src/views/nav-menu-renderer.tsx#NavMenuRenderer (the same `evalVis`, and each row returns nothing when `evalVis(item.visible)` is false)", + "evidenceScope": "in-repo", + "evidence": "ui/packages/layout/src/NavigationRenderer.tsx (:891 at objectui @940ba24) (item gate); ui/packages/app-shell/src/layout/UnifiedSidebar.tsx#UnifiedSidebar (`evalVis`, the CEL evaluation through the ExpressionProvider's `evaluateVisibility`, handed to `NavigationRenderer` and to `hasVisibleNavigationItems`); ui/packages/app-shell/src/views/nav-menu-renderer.tsx#NavMenuRenderer (the same `evalVis`, and each row returns nothing when `evalVis(item.visible)` is false)", "note": "the CEL visibility gate — enforced per item. This is the layer the retired `areas[].visible` (#4651) prescribes moving to: same CEL dialect, one level down, and actually evaluated. CLIENT-side only, at both levels, and deliberately so: #4722 made the server enforce `requiredPermissions` / `requiresService` inside `areas[]` but left CEL where it was, because evaluating it server-side needs a bound `user` context the REST read layer does not have. Anything that must never reach the browser belongs in `requiredPermissions`, not `visible` — the asymmetry is pinned in packages/rest/src/rest.test.ts. 2026-09-27 (#20146): REPOINTED off objectui's `AppSidebar.tsx` — deprecated, never mounted (the console renders `UnifiedSidebar`), and removed from objectui main by objectui PR #10617 — to the mounted reader(s) above, each re-read at the `.objectui-sha` pin f8a9d0fb and unchanged at objectui main fb91ac9b0. The `NavigationRenderer` item-gate leg is unchanged by this re-point." }, "dataSource": { @@ -168,67 +168,67 @@ "requiredPermissions": { "status": "live", "verifiedAt": "2026-09-27", - "evidence": "packages/rest/src/meta-item-read-gate.ts#filterNav (the server strips an entry whose `requiredPermissions` are not a subset of the caller's system permissions — from the top-level tree AND, since #4722, from every `areas[].navigation` tree through this same closure); objectui @940ba24: packages/layout/src/NavigationRenderer.tsx:894", + "evidence": "packages/rest/src/meta-item-read-gate.ts#filterNav (the server strips an entry whose `requiredPermissions` are not a subset of the caller's system permissions — from the top-level tree AND, since #4722, from every `areas[].navigation` tree through this same closure); ui/packages/layout/src/NavigationRenderer.tsx (:894 at objectui @940ba24)", "note": "enforced in BOTH layers: the server strips unsatisfied entries before serving — from the top-level navigation tree AND, since #4722, from every `areas[].navigation` tree through the same filterNav — and the client re-gates per item. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:1844` had rotted onto a bare `try {` inside `computeExecCtx`'s auth-service lookup, ~320 lines above the gate. Re-closed by hand against 8cb96ec41." }, "requiresObject": { "status": "live", "verifiedAt": "2026-08-01", - "evidence": "objectui @940ba24: packages/layout/src/NavigationRenderer.tsx:899-901", + "evidence": "ui/packages/layout/src/NavigationRenderer.tsx (:899-901 at objectui @940ba24)", "note": "runtime-capability gate against the SchemaRegistry (client-side; the server gates only requiresService)." }, "requiresService": { "status": "live", "verifiedAt": "2026-09-27", - "evidence": "packages/rest/src/meta-item-read-gate.ts#filterNav (ADR-0057 D10 — an entry naming a service the kernel has not registered is dropped before the tree is served; fail-open when the kernel cannot be probed); objectui @940ba24: packages/layout/src/NavigationRenderer.tsx:900-902", + "evidence": "packages/rest/src/meta-item-read-gate.ts#filterNav (ADR-0057 D10 — an entry naming a service the kernel has not registered is dropped before the tree is served; fail-open when the kernel cannot be probed); ui/packages/layout/src/NavigationRenderer.tsx (:900-902 at objectui @940ba24)", "note": "ADR-0057 D10 capability gate, server + client. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:1832` had rotted onto `environmentId = await this.resolveRequestEnvironmentId(environmentId, req)` in `computeExecCtx`, its sibling `requiredPermissions` twelve lines below it in the same unrelated block. Re-closed by hand against 8cb96ec41." }, "type": { "status": "live", "verifiedAt": "2026-08-02", - "evidence": "objectui @e8bec83: packages/layout/src/NavigationRenderer.tsx:905-1020 (branch dispatch), :397-471 (href resolution per variant), :963-971 (action branch — hidden when the host passes no dispatcher)", + "evidence": "ui/packages/layout/src/NavigationRenderer.tsx (:905-1020 at objectui @e8bec83) (branch dispatch), :397-471 (href resolution per variant), :963-971 (action branch — hidden when the host passes no dispatcher)", "note": "the discriminant. Variant payload keys outside this walk are covered in the type note — all live, the `actionDef` dispatch gap included since #4509 wired it to the console action runtime." }, "objectName": { "status": "live", "verifiedAt": "2026-08-01", - "evidence": "objectui @940ba24: packages/layout/src/NavigationRenderer.tsx:296, :397-418" + "evidence": "ui/packages/layout/src/NavigationRenderer.tsx (:296 at objectui @940ba24), :397-418" }, "viewName": { "status": "live", "verifiedAt": "2026-08-01", - "evidence": "objectui @940ba24: packages/layout/src/NavigationRenderer.tsx:294-296, :418", + "evidence": "ui/packages/layout/src/NavigationRenderer.tsx:401-403, :525", "note": "target precedence recordId → filters → viewName; also keys the view-label i18n lookup. With no `viewName` (packages/spec/src/ui/app.zod.ts, ObjectNavItemSchema; moved out of spec source, #23434): the entry links to the bare object route, where objectui @b9814b334 packages/app-shell/src/views/ObjectView.tsx#activeViewId opens `defaultViewId || views[0]`, the view objectui @b9814b334 packages/app-shell/src/views/ObjectView.tsx#buildViewTabs marks default, and the `all` tab that function adds only for an object with no list view (#21973); first read at objectui @0abd4f9f8769 (the same two symbols), re-read 2026-10-15." }, "recordId": { "status": "live", "verifiedAt": "2026-08-01", - "evidence": "objectui @940ba24: packages/layout/src/NavigationRenderer.tsx:397-406, :598", + "evidence": "ui/packages/layout/src/NavigationRenderer.tsx (:397-406 at objectui @940ba24), :598", "note": "direct-to-record deep link; {current_user_id}/{current_org_id} and context-selector {} template vars substituted by the shell." }, "recordMode": { "status": "live", "verifiedAt": "2026-08-01", - "evidence": "objectui @940ba24: packages/layout/src/NavigationRenderer.tsx:406" + "evidence": "ui/packages/layout/src/NavigationRenderer.tsx (:406 at objectui @940ba24)" }, "filters": { "status": "live", "verifiedAt": "2026-08-01", - "evidence": "objectui @940ba24: packages/layout/src/NavigationRenderer.tsx:416-418", + "evidence": "ui/packages/layout/src/NavigationRenderer.tsx (:416-418 at objectui @940ba24)", "note": "serialized as filter[]= params onto the bare /data surface (objectui ADR-0055); exclusivity with recordId/viewName is parse-rejected (objectNavTargetExclusivity)." }, "runAction": { "status": "live", "verifiedAt": "2026-08-20", - "evidenceScope": "cross-repo", - "evidence": "objectui @9a3daf8: packages/app-shell/src/hooks/useNavRunAction.ts:64-90 — the single read-once/consume-once CONSUMER, reading the param at mount through useState's initializer and stripping it with history.replaceState only once the caller's `armed` predicate confirms the action is present and runnable. Wired generically at objectui @9a3daf8: packages/app-shell/src/views/ObjectView.tsx:944-960 — EVERY object list, marking exactly the requested action autoTrigger so param dialogs, confirms and entitlement gates all still apply. Entitlement-gated wiring at objectui @9a3daf8: packages/app-shell/src/environment/EnvironmentListToolbar.tsx:108-114 — useAutoRunCreate additionally waits for entitlements to resolve, and ObjectView deliberately excludes sys_environment from its generic arming so two consumers cannot race to strip one param.", - "producer": "objectui @9a3daf8: packages/layout/src/NavigationRenderer.tsx:487 defines NAV_RUN_ACTION_PARAM (the wire name's ONE definition, replacing the two bare 'runAction' literals cloud#844 shipped), and :507-512 withRunAction encodes the declared slot onto the href inside resolveHref's object branch — applied at :563 (filters slice) and :568 (bare/named view), the LIST landings only, never the recordId branch above them. It WRITES the deep link and runs nothing: the consumer cited in `evidence` is the second input, which is why this row needs both halves (#4837).", + "evidenceScope": "in-repo", + "evidence": "ui/packages/app-shell/src/hooks/useNavRunAction.ts (:64-90 at objectui @9a3daf8) — the single read-once/consume-once CONSUMER, reading the param at mount through useState's initializer and stripping it with history.replaceState only once the caller's `armed` predicate confirms the action is present and runnable. Wired generically at ui/packages/app-shell/src/views/ObjectView.tsx (:944-960 at objectui @9a3daf8) — EVERY object list, marking exactly the requested action autoTrigger so param dialogs, confirms and entitlement gates all still apply. Entitlement-gated wiring at ui/packages/app-shell/src/environment/EnvironmentListToolbar.tsx:112-118 — useAutoRunCreate additionally waits for entitlements to resolve, and ObjectView deliberately excludes sys_environment from its generic arming so two consumers cannot race to strip one param.", + "producer": "ui/packages/layout/src/NavigationRenderer.tsx:850 defines NAV_RUN_ACTION_PARAM (the wire name's ONE definition, replacing the two bare 'runAction' literals cloud#844 shipped), and :870-875 withRunAction encodes the declared slot onto the href inside resolveHref's object branch — applied at :963 (filters slice) and :968 (bare/named view), the LIST landings only, never the recordId branch above them. It WRITES the deep link and runs nothing: the consumer cited in `evidence` is the second input, which is why this row needs both halves (#4837).", "note": "[#4848] SDUI deep-link 'navigate = run action'. FLIPPED planned → live 2026-08-20 (#10068): the objectui half landed as objectui#5216 via objectui PR #5354 (merged 2026-08-19), absorbed here by the .objectui-sha pin 9a3daf8 — the pin postdates that merge, so both pointers above were read at the pin this repo builds against, not at objectui main. The authoring tier was always enforced (framework packages/spec/src/stack.zod.ts validateCrossReferences nav walk; packages/lint/src/validate-action-name-refs.ts nav runAction arm), and what was missing until now was a shipped shell that reads the DECLARED slot rather than the transitional raw `?runAction=` query literal — hence `planned` + authorWarn, both now dropped. TWO pointers, not one, and the split is not cosmetic: a NavigationRenderer-only pointer (what the original work-list anticipated) would say the slot is live because something EMITS it, while what makes authoring it change runtime behaviour is that a shell CONSUMES it — and that lives in app-shell, not layout. ⚠️ ENFORCEMENT vs CONSUMPTION, measured from objectui against the installed package: the published @objectstack/spec@17.0.0 does NOT enforce the runAction × recordId exclusivity — the objectNavTargetExclusivity superRefine exists on this repo's main (packages/spec/src/ui/app.zod.ts, message 'runAction cannot be combined with recordId') but the pinned GA build accepts the combination, and it also accepts `runAction: ''`. That is the merged-but-unpublished window, not a defect, and nothing was filed as one. The consequence this row records: objectui's list-surface-only precedence and its empty-string-is-absent handling are LOAD-BEARING rather than defensive, because the pinned schema will not refuse either input for it. Generalising (objectui hit it three times in one day, tracked at objectui#5328): merged upstream ≠ published ≠ pinned downstream, and the rule covers refinements and error messages, not only fields — the refinement case FAILS SILENT, because a missing key announces itself (the consumer will not compile, or safeParse rejects) while a missing refinement lets the input through and the consumer proceeds. The cloud half (#7251) is unblocked by this landing but is its own card; cloud's transitional pin on the create_environment action name was deliberately left untouched." }, "children": { "status": "live", "verifiedAt": "2026-08-01", - "evidence": "objectui @940ba24: packages/layout/src/NavigationRenderer.tsx:910-913", + "evidence": "ui/packages/layout/src/NavigationRenderer.tsx (:910-913 at objectui @940ba24)", "note": "recursive render with per-group order sort; the server's filterNav collapses groups emptied by permission stripping (rest-server.ts:1837)." } }, @@ -239,37 +239,37 @@ "id": { "status": "live", "verifiedAt": "2026-09-27", - "evidenceScope": "cross-repo", - "evidence": "objectui @f8a9d0fb: packages/app-shell/src/layout/UnifiedSidebar.tsx#UnifiedSidebar (`activeAreaId` state, the active-area lookup by `a.id === activeAreaId`, and each area-switcher row's key and click target)", + "evidenceScope": "in-repo", + "evidence": "ui/packages/app-shell/src/layout/UnifiedSidebar.tsx#UnifiedSidebar (`activeAreaId` state, the active-area lookup by `a.id === activeAreaId`, and each area-switcher row's key and click target)", "note": "area-switcher identity and active-area state key. 2026-09-27 (#20146): REPOINTED off objectui's `AppSidebar.tsx` — deprecated, never mounted (the console renders `UnifiedSidebar`), and removed from objectui main by objectui PR #10617 — to the mounted reader(s) above, each re-read at the `.objectui-sha` pin f8a9d0fb and unchanged at objectui main fb91ac9b0." }, "label": { "status": "live", "verifiedAt": "2026-09-27", - "evidenceScope": "cross-repo", - "evidence": "objectui @f8a9d0fb: packages/app-shell/src/layout/UnifiedSidebar.tsx#UnifiedSidebar (`resolveInlineI18nLabel(area.label, language)` on each area-switcher row)", + "evidenceScope": "in-repo", + "evidence": "ui/packages/app-shell/src/layout/UnifiedSidebar.tsx#UnifiedSidebar (`resolveInlineI18nLabel(area.label, language)` on each area-switcher row)", "note": "2026-09-27 (#20146): REPOINTED off objectui's `AppSidebar.tsx` — deprecated, never mounted (the console renders `UnifiedSidebar`), and removed from objectui main by objectui PR #10617 — to the mounted reader(s) above, each re-read at the `.objectui-sha` pin f8a9d0fb and unchanged at objectui main fb91ac9b0." }, "icon": { "status": "live", "verifiedAt": "2026-09-27", - "evidenceScope": "cross-repo", - "evidence": "objectui @f8a9d0fb: packages/app-shell/src/layout/UnifiedSidebar.tsx#UnifiedSidebar (`getIcon(area.icon)` on each area-switcher row)", + "evidenceScope": "in-repo", + "evidence": "ui/packages/app-shell/src/layout/UnifiedSidebar.tsx#UnifiedSidebar (`getIcon(area.icon)` on each area-switcher row)", "note": "2026-09-27 (#20146): REPOINTED off objectui's `AppSidebar.tsx` — deprecated, never mounted (the console renders `UnifiedSidebar`), and removed from objectui main by objectui PR #10617 — to the mounted reader(s) above, each re-read at the `.objectui-sha` pin f8a9d0fb and unchanged at objectui main fb91ac9b0." }, "description": { "status": "live", "verifiedAt": "2026-09-30", - "evidenceScope": "cross-repo", - "evidence": "objectui @db11afd4967: packages/app-shell/src/views/metadata-admin/previews/AppPreview.tsx#readAreas (resolves each area's `description` through `resolveI18nLabel` in the designer locale; unauthored stays undefined and draws nothing); objectui @db11afd4967: packages/app-shell/src/views/metadata-admin/previews/AppPreview.tsx#AppPreview (draws it under the area's label in the preview's Areas list, in read and design mode)", - "producer": "objectui @db11afd4967: packages/app-shell/src/views/metadata-admin/previews/index.ts#registerBuiltinPreviews (registers AppPreview for `app`); objectui @db11afd4967: packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx#MetadataResourceEditPageImpl (`app` registers no custom EditPage, so the generic edit route resolves `getMetadataPreview('app')` and hands it the draft); framework: packages/rest/src/meta-item-read-gate.ts#filterAppForUserWithReason (the served app keeps every key of each area it serves)", + "evidenceScope": "in-repo", + "evidence": "ui/packages/app-shell/src/views/metadata-admin/previews/AppPreview.tsx#readAreas (resolves each area's `description` through `resolveI18nLabel` in the designer locale; unauthored stays undefined and draws nothing); ui/packages/app-shell/src/views/metadata-admin/previews/AppPreview.tsx#AppPreview (draws it under the area's label in the preview's Areas list, in read and design mode)", + "producer": "ui/packages/app-shell/src/views/metadata-admin/previews/index.ts#registerBuiltinPreviews (registers AppPreview for `app`); ui/packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx#MetadataResourceEditPageImpl (`app` registers no custom EditPage, so the generic edit route resolves `getMetadataPreview('app')` and hands it the draft); framework: packages/rest/src/meta-item-read-gate.ts#filterAppForUserWithReason (the served app keeps every key of each area it serves)", "note": "RE-GRADED dead → live 2026-09-30 (#20299) under the #7131 previews ruling (README, 'Designer previews count as consumers'): for a display key, being shown to a human is the whole claimed effect. objectui#11027, read at the `.objectui-sha` pin db11afd4967, added the Areas list; the superseded note measured AppPreview at @e9ab52f9 with zero `areas` reads. UNCHANGED: still docs-shaped, deliberately KEPT (ADR-0033) and not authorWarn'd." }, "navigation": { "status": "live", "verifiedAt": "2026-09-27", - "evidenceScope": "cross-repo", - "evidence": "objectui @f8a9d0fb: packages/app-shell/src/layout/UnifiedSidebar.tsx#UnifiedSidebar (`activeArea?.navigation || activeApp?.navigation`: the active area's tree replaces the top-level one); objectui @940ba24: packages/layout/src/AppSchemaRenderer.tsx:469", + "evidenceScope": "in-repo", + "evidence": "ui/packages/app-shell/src/layout/UnifiedSidebar.tsx#UnifiedSidebar (`activeArea?.navigation || activeApp?.navigation`: the active area's tree replaces the top-level one); ui/packages/layout/src/AppSchemaRenderer.tsx:545", "note": "the active area's tree replaces the top-level navigation. Since #4722 area trees ARE server-side gated: filterAppForUser (packages/rest/src/rest-server.ts:1870) runs the SAME filterNav over every `areas[].navigation`, so an item's `requiredPermissions` / `requiresService` is enforced identically in both trees and a gated entry (with its objectName/pageName/componentRef target) never reaches the browser. An area emptied BY the gate is dropped, mirroring the top-level group collapse; an area authored empty is passed through. Still client-only at both levels: `visible` (CEL — needs a bound user context the read layer lacks) and `requiresObject`. The area-LEVEL keys stay retired (#4651) — this enforces the items inside, not a revived area gate. 2026-09-27 (#20146): REPOINTED off objectui's `AppSidebar.tsx` — deprecated, never mounted (the console renders `UnifiedSidebar`), and removed from objectui main by objectui PR #10617 — to the mounted reader(s) above, each re-read at the `.objectui-sha` pin f8a9d0fb and unchanged at objectui main fb91ac9b0. The `AppSchemaRenderer` leg is unchanged by this re-point." } }, @@ -280,35 +280,35 @@ "id": { "status": "live", "verifiedAt": "2026-08-01", - "evidence": "objectui @940ba24: packages/app-shell/src/layout/ContextSelectors.tsx:199", + "evidence": "ui/packages/app-shell/src/layout/ContextSelectors.tsx (:199 at objectui @940ba24)", "note": "also the nav template-variable name ({} substitution into recordId/params)." }, "label": { "status": "live", "verifiedAt": "2026-08-01", - "evidence": "objectui @940ba24: packages/app-shell/src/layout/ContextSelectors.tsx:232" + "evidence": "ui/packages/app-shell/src/layout/ContextSelectors.tsx (:232 at objectui @940ba24)" }, "icon": { "status": "live", "verifiedAt": "2026-08-01", - "evidence": "objectui @940ba24: packages/app-shell/src/layout/ContextSelectors.tsx:231" + "evidence": "ui/packages/app-shell/src/layout/ContextSelectors.tsx (:231 at objectui @940ba24)" }, "optionsSource": { "status": "live", "verifiedAt": "2026-08-01", - "evidence": "objectui @940ba24: packages/app-shell/src/layout/ContextSelectors.tsx:90-125", + "evidence": "ui/packages/app-shell/src/layout/ContextSelectors.tsx (:90-125 at objectui @940ba24)", "note": "endpoint fetched, valueKey/labelKey dotted-path mapped, `filter` predicates applied per row (rowPasses, :69-71)." }, "allValue": { "status": "live", "verifiedAt": "2026-08-02", - "evidence": "objectui @940ba24: packages/app-shell/src/layout/ContextSelectors.tsx:177, :199, :246", + "evidence": "ui/packages/app-shell/src/layout/ContextSelectors.tsx (:177 at objectui @940ba24), :199, :246", "note": "RE-VERIFIED 2026-08-02 (#4509) — the re-check the previous note asked for, now that includeAll is gone. Still live, and the reading is unchanged: it is the 'nothing concrete selected' sentinel for auto-selection and query-param defaulting. What changed is the DECLARATION: the describe() no longer calls it 'the value emitted when All is selected', because that event cannot occur and never could — selectors are mandatory-scope and the renderer never offered an All row. Live by the letter AND by the letter now; the vestigial-by-intent caveat retired with includeAll." }, "persist": { "status": "live", "verifiedAt": "2026-08-01", - "evidence": "objectui @940ba24: packages/app-shell/src/layout/ContextSelectors.tsx:164", + "evidence": "ui/packages/app-shell/src/layout/ContextSelectors.tsx (:164 at objectui @940ba24)", "note": "'none' opts out of persistence; query/session honored." } }, @@ -329,7 +329,7 @@ "defaultAgent": { "status": "live", "verifiedAt": "2026-08-01", - "evidence": "objectui @940ba24: packages/app-shell/src/hooks/surfaceAgent.ts:81, packages/app-shell/src/layout/ChatDock.tsx:253", + "evidence": "ui/packages/app-shell/src/hooks/surfaceAgent.ts (:81 at objectui @940ba24), ui/packages/app-shell/src/layout/ChatDock.tsx (:253 at objectui @940ba24)", "note": "bounded surface-binding knob (ADR-0063): resolved to the two platform agents (ask/build), alias-aware, anything else rejected — exactly as the schema documents." }, "version": { diff --git a/packages/spec/liveness/book.json b/packages/spec/liveness/book.json index b9035cb439c..bc74e1eebac 100644 --- a/packages/spec/liveness/book.json +++ b/packages/spec/liveness/book.json @@ -17,25 +17,25 @@ "description": { "status": "live", "verifiedAt": "2026-08-01", - "evidence": "objectui @940ba24: apps/console/src/pages/book-nav.ts:341 (buildBookCards)", + "evidence": "ui/apps/console/src/pages/book-nav.ts (:341 at objectui @940ba24) (buildBookCards)", "note": "portal landing-card subtitle. The framework tree omits it; the portal reads it off the raw /meta/book item." }, "slug": { "status": "live", "verifiedAt": "2026-08-01", - "evidence": "objectui @940ba24: apps/console/src/pages/book-nav.ts:132 (bookSlug), :339", + "evidence": "ui/apps/console/src/pages/book-nav.ts (:132 at objectui @940ba24) (bookSlug), :339", "note": "portal URL segment (`/docs/`), defaulting to `name`. Portal-side consumer only — the framework tree endpoint routes by `name`." }, "icon": { "status": "live", "verifiedAt": "2026-08-01", - "evidence": "objectui @940ba24: apps/console/src/pages/book-nav.ts:345 (buildBookCards)", + "evidence": "ui/apps/console/src/pages/book-nav.ts (:345 at objectui @940ba24) (buildBookCards)", "note": "portal landing-card icon." }, "order": { "status": "live", "verifiedAt": "2026-08-01", - "evidence": "objectui @940ba24: apps/console/src/pages/book-nav.ts:302-312 (sortBooks)", + "evidence": "ui/apps/console/src/pages/book-nav.ts (:302-312 at objectui @940ba24) (sortBooks)", "note": "orders books on the portal landing (then label, stable). Authored books always sort ahead of synthetic per-package ones regardless of `order`." }, "audience": { diff --git a/packages/spec/liveness/capability.json b/packages/spec/liveness/capability.json index 6500912f575..665359cb1ca 100644 --- a/packages/spec/liveness/capability.json +++ b/packages/spec/liveness/capability.json @@ -11,19 +11,19 @@ "label": { "status": "live", "verifiedAt": "2026-10-10", - "evidence": "objectui: packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx (the default `label` / `description` columns of every metadata type's list, the capability list from `GET /api/v1/meta/capability` included; read at the pinned `.objectui-sha` 20c6d351ad)", + "evidence": "ui/packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx (the default `label` / `description` columns of every metadata type's list, the capability list from `GET /api/v1/meta/capability` included; read at the pinned `.objectui-sha` 20c6d351ad)", "note": "display (Setup's capability list/detail, rendered from the sys_capability row). 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:122` had rotted onto a docblock sentence about the `unchanged` outcome counter, ~45 lines above `capabilityRowFields`. Its two siblings cited `:123` and `:124`, the next two lines of that same docblock: the three row-field pointers had drifted as ONE block, which is why none of them looked wrong beside the others. Re-closed by hand against 8cb96ec41." }, "description": { "status": "live", "verifiedAt": "2026-10-10", - "evidence": "objectui: packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx (the default `label` / `description` columns of every metadata type's list, the capability list from `GET /api/v1/meta/capability` included; read at the pinned `.objectui-sha` 20c6d351ad)", + "evidence": "ui/packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx (the default `label` / `description` columns of every metadata type's list, the capability list from `GET /api/v1/meta/capability` included; read at the pinned `.objectui-sha` 20c6d351ad)", "note": "display. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:123` had rotted onto the closing `*/` of that docblock (see the `label` entry: all three of this file's row-field pointers moved together). Re-closed by hand against 8cb96ec41." }, "scope": { "status": "live", "verifiedAt": "2026-10-10", - "evidence": "objectui: packages/app-shell/src/views/metadata-admin/SchemaForm.tsx (the item page renders each property of the type's served schema, `scope` included, for a capability read from `GET /api/v1/meta/capability/:name`; read at the pinned `.objectui-sha` 20c6d351ad) — display", + "evidence": "ui/packages/app-shell/src/views/metadata-admin/SchemaForm.tsx (the item page renders each property of the type's served schema, `scope` included, for a capability read from `GET /api/v1/meta/capability/:name`; read at the pinned `.objectui-sha` 20c6d351ad) — display", "note": "platform vs org decides whether holding the capability is a global power or one scoped to the caller's organization — the same distinction PLATFORM_CAPABILITIES carries on the curated side. Not display: it is the field that would be escalated if a tenant could overlay a package-shipped declaration, which is why the #5961 registry entry sets `supportsOverlay: false` / `allowEnvironmentOverlay: false`. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:124` had rotted onto `unchanged: number;`, the counter field itself (see the `label` entry). Re-closed by hand against 8cb96ec41." }, "packageId": { diff --git a/packages/spec/liveness/connector.json b/packages/spec/liveness/connector.json index 957ccdc1726..b3c425172af 100644 --- a/packages/spec/liveness/connector.json +++ b/packages/spec/liveness/connector.json @@ -12,8 +12,8 @@ "label": { "status": "live", "verifiedAt": "2026-09-17", - "evidenceScope": "cross-repo", - "evidence": "packages/services/service-automation/src/plugin.ts#materializeDeclaredConnectors — `label: entry.label ?? name` puts the authored label on `ConnectorProviderContext`; packages/connectors/connector-openapi/src/openapi-provider.ts, packages/connectors/connector-rest/src/rest-provider.ts and packages/connectors/connector-mcp/src/mcp-provider.ts each copy `label: ctx.label` onto the def they return; packages/services/service-automation/src/engine.ts#getConnectorDescriptors then publishes `label: def.label`; objectui @dda8f3815 packages/app-shell/src/views/metadata-admin/inspectors/FlowReferenceField.tsx#connectorsToOptions renders it as the connector picker's option text (`${label} (${name})`) in the flow designer.", + "evidenceScope": "in-repo", + "evidence": "packages/services/service-automation/src/plugin.ts#materializeDeclaredConnectors — `label: entry.label ?? name` puts the authored label on `ConnectorProviderContext`; packages/connectors/connector-openapi/src/openapi-provider.ts, packages/connectors/connector-rest/src/rest-provider.ts and packages/connectors/connector-mcp/src/mcp-provider.ts each copy `label: ctx.label` onto the def they return; packages/services/service-automation/src/engine.ts#getConnectorDescriptors then publishes `label: def.label`; ui/packages/app-shell/src/views/metadata-admin/inspectors/FlowReferenceField.tsx#connectorsToOptions renders it as the connector picker's option text (`${label} (${name})`) in the flow designer.", "producer": "packages/services/service-automation/src/plugin.ts#materializeDeclaredConnectors — the ADR-0097 reconcile: it builds the desired set from the declared `connectors:` items, resolves the provider factory and calls `engine.registerConnector(def, handlers, 'declarative')`. This is the ONLY seam by which an authored entry reaches the connector registry, so it is the producer every `live` row below depends on (#4837): without it a declared connector is a stored document and nothing else.", "note": "Display-shaped, so the #7131 split settles it: being shown to a human IS the whole claimed effect. All three shipped provider factories carry it, so this is the one authored display key that survives materialization intact — contrast `icon` and `type` below, which reach the same context object and are dropped." }, @@ -71,41 +71,41 @@ "key": { "status": "live", "verifiedAt": "2026-09-17", - "evidenceScope": "cross-repo", - "evidence": "packages/services/service-automation/src/plugin-extension-api.ts#registerConnector — `if (typeof handlers[action.key] !== 'function') throw` refuses a def whose declared action has no handler, so the key is the contract between declaration and implementation; packages/services/service-automation/src/engine.ts#resolveConnectorActionEffect looks the action up by `a.key === actionId`, and `resolveConnectorAction` returns `handlers[actionId]` for the node to dispatch; packages/services/service-automation/src/engine.ts#getConnectorDescriptors publishes it; objectui @dda8f3815 packages/app-shell/src/views/metadata-admin/inspectors/FlowReferenceField.tsx (`connectorActionsToOptions`) and .../connector-input-fields.ts#connectorActionInputSchema both resolve an action by `a.key === actionKey`.", + "evidenceScope": "in-repo", + "evidence": "packages/services/service-automation/src/plugin-extension-api.ts#registerConnector — `if (typeof handlers[action.key] !== 'function') throw` refuses a def whose declared action has no handler, so the key is the contract between declaration and implementation; packages/services/service-automation/src/engine.ts#resolveConnectorActionEffect looks the action up by `a.key === actionId`, and `resolveConnectorAction` returns `handlers[actionId]` for the node to dispatch; packages/services/service-automation/src/engine.ts#getConnectorDescriptors publishes it; ui/packages/app-shell/src/views/metadata-admin/inspectors/FlowReferenceField.tsx (`connectorActionsToOptions`) and .../connector-input-fields.ts#connectorActionInputSchema both resolve an action by `a.key === actionKey`.", "producer": "packages/connectors/connector-mcp/src/mcp-connector.ts — `actions: selected.map((tool) => ({ key: tool.name, … }))` derives the action set from the upstream `tools/list`; packages/connectors/connector-openapi/src/openapi-connector.ts derives one action per OpenAPI operation. ⚠️ THE METADATA AUTHOR IS NOT A PRODUCER HERE: packages/spec/src/integration/connector.zod.ts#DeclarativeConnectorEntrySchema REFUSES `actions` on a provider-bound instance (ADR-0097 §5 — the provider derives them), and on a descriptor (no `provider`) the entry never reaches the registry at all.", "note": "Live on the PLUGIN/provider door, inert-and-audited on the authoring door — the two-door fact in the file note, at its sharpest. What an authored `actions[]` on a descriptor DOES do is trigger a boot warning: packages/services/service-automation/src/plugin.ts#findInertDeclaredConnectors returns every declared connector that has at least one action, no runtime registration and no `enabled: false` opt-out, and `auditDeclaredConnectors` warns once per boot naming them and the remedy. That warning is the #2612 contract made audible; it is not dispatch." }, "label": { "status": "live", "verifiedAt": "2026-09-17", - "evidenceScope": "cross-repo", - "evidence": "packages/services/service-automation/src/engine.ts#getConnectorDescriptors — `label: a.label` on each projected action; objectui @dda8f3815 packages/app-shell/src/views/metadata-admin/inspectors/FlowReferenceField.tsx (`connectorActionsToOptions`, fed by `useConnectorActionOptions` off `GET /api/v1/automation/connectors`) renders it as the action picker's option text in the flow designer.", + "evidenceScope": "in-repo", + "evidence": "packages/services/service-automation/src/engine.ts#getConnectorDescriptors — `label: a.label` on each projected action; ui/packages/app-shell/src/views/metadata-admin/inspectors/FlowReferenceField.tsx (`connectorActionsToOptions`, fed by `useConnectorActionOptions` off `GET /api/v1/automation/connectors`) renders it as the action picker's option text in the flow designer.", "producer": "packages/connectors/connector-mcp/src/mcp-connector.ts — `label: titleize(slugify(tool.name))`; packages/connectors/connector-openapi/src/openapi-connector.ts derives one per operation. Same two-door caveat as `actions.key`.", "note": "Display-shaped and settled by the #7131 split — the designer's action picker IS the claimed effect. REQUIRED, so there is no empty state." }, "description": { "status": "live", "verifiedAt": "2026-09-30", - "evidenceScope": "cross-repo", - "evidence": "packages/services/service-automation/src/engine.ts#getConnectorDescriptors — `description: a.description` on each projected action, the `GET /api/v1/automation/connectors` payload; objectui @db11afd4967: packages/app-shell/src/views/metadata-admin/inspectors/FlowReferenceField.tsx#connectorActionsToOptions (a non-blank string `description` becomes the action option's `hint`, fed by `useConnectorActionOptions` off that payload); objectui @db11afd4967: packages/app-shell/src/views/metadata-admin/inspectors/FlowReferenceField.tsx#ReferenceCombobox (draws each option as `label — hint` in the `connector_action` node's Action picker)", - "producer": "packages/connectors/connector-slack/src/slack-connector.ts#createSlackConnector — a `description` on each of its three actions; packages/connectors/connector-openapi/src/openapi-connector.ts#createOpenApiConnector — `description: op.description`; packages/connectors/connector-mcp/src/mcp-connector.ts#createMcpConnector — the server's tool description verbatim; objectui @db11afd4967: packages/app-shell/src/views/metadata-admin/inspectors/flow-node-config.ts#FLOW_NODE_CONFIG (the `connector_action` node's `actionId` field is a `connector-action` reference, which is what mounts that picker). Same two-door caveat as `actions.key`.", + "evidenceScope": "in-repo", + "evidence": "packages/services/service-automation/src/engine.ts#getConnectorDescriptors — `description: a.description` on each projected action, the `GET /api/v1/automation/connectors` payload; ui/packages/app-shell/src/views/metadata-admin/inspectors/FlowReferenceField.tsx#connectorActionsToOptions (a non-blank string `description` becomes the action option's `hint`, fed by `useConnectorActionOptions` off that payload); ui/packages/app-shell/src/views/metadata-admin/inspectors/FlowReferenceField.tsx#ReferenceCombobox (draws each option as `label — hint` in the `connector_action` node's Action picker)", + "producer": "packages/connectors/connector-slack/src/slack-connector.ts#createSlackConnector — a `description` on each of its three actions; packages/connectors/connector-openapi/src/openapi-connector.ts#createOpenApiConnector — `description: op.description`; packages/connectors/connector-mcp/src/mcp-connector.ts#createMcpConnector — the server's tool description verbatim; ui/packages/app-shell/src/views/metadata-admin/inspectors/flow-node-config.ts#FLOW_NODE_CONFIG (the `connector_action` node's `actionId` field is a `connector-action` reference, which is what mounts that picker). Same two-door caveat as `actions.key`.", "note": "RE-GRADED dead → live 2026-09-30 (#20287): objectui#11028, read at the `.objectui-sha` pin db11afd4967, shows it beside the action's label in the flow designer's Action picker. Display-shaped, so the picker IS the claimed effect (the #7131 split), as for `actions.label`. The superseded note — no consumer in either repo reads it back — was true until objectui#11028." }, "inputSchema": { "status": "live", "verifiedAt": "2026-09-17", - "evidenceScope": "cross-repo", - "evidence": "packages/services/service-automation/src/engine.ts#getConnectorDescriptors — `inputSchema: a.inputSchema` on the projected action; objectui @dda8f3815 packages/app-shell/src/views/metadata-admin/inspectors/connector-input-fields.ts#connectorActionInputSchema finds the committed action in that payload and returns its `inputSchema`, and `FlowNodeInspector.tsx` types the connector node's whole Input section from it (`connectorInputFields(connectorActionInputSchema(...))`, objectui #4305) — an action that declares none falls back to the generic key/value repeater, which is the observable difference.", + "evidenceScope": "in-repo", + "evidence": "packages/services/service-automation/src/engine.ts#getConnectorDescriptors — `inputSchema: a.inputSchema` on the projected action; ui/packages/app-shell/src/views/metadata-admin/inspectors/connector-input-fields.ts#connectorActionInputSchema finds the committed action in that payload and returns its `inputSchema`, and `FlowNodeInspector.tsx` types the connector node's whole Input section from it (`connectorInputFields(connectorActionInputSchema(...))`, objectui #4305) — an action that declares none falls back to the generic key/value repeater, which is the observable difference.", "producer": "packages/connectors/connector-mcp/src/mcp-connector.ts — the MCP tool's own JSON Schema is passed straight through ('The MCP inputSchema is already JSON Schema'); packages/connectors/connector-openapi/src/openapi-connector.ts derives it from the operation's parameters. Same two-door caveat as `actions.key`.", "note": "A structural (not display) consumer, cross-repo like `outputSchema`'s: the designer builds a typed form from it. Declared `z.record(z.string(), z.unknown())` — JSON Schema by convention, unvalidated here — so it has no child shape and nothing rides on a blanket verdict." }, "outputSchema": { "status": "live", "verifiedAt": "2026-09-30", - "evidenceScope": "cross-repo", - "evidence": "packages/services/service-automation/src/engine.ts#getConnectorDescriptors — `outputSchema: a.outputSchema` on each projected action, the `GET /api/v1/automation/connectors` payload; objectui @db11afd4967: packages/app-shell/src/views/metadata-admin/inspectors/flow-scope.ts#nodeOutputRefs (a committed `connector_action` node offers one `nodeId.key` reference per top-level `properties` key of its action's `outputSchema`, through `connectorActionOutputSchema` and `connectorActionOutputKeys`; no schema, no references); objectui @db11afd4967: packages/app-shell/src/views/metadata-admin/inspectors/flow-scope.ts#resolveFlowScope (hands those references to every downstream node's and edge's data picker)", - "producer": "objectui @db11afd4967: packages/app-shell/src/views/metadata-admin/inspectors/connector-input-fields.ts#useConnectorRegistry (reads that payload); objectui @db11afd4967: packages/app-shell/src/views/metadata-admin/inspectors/FlowNodeInspector.tsx#FlowNodeInspector and objectui @db11afd4967: packages/app-shell/src/views/metadata-admin/inspectors/FlowEdgeInspector.tsx#FlowEdgeInspector (each reads the registry when the flow holds a committed connector action and passes it to `useFlowScope` — the input the read depends on); framework: packages/connectors/connector-slack/src/slack-connector.ts#createSlackConnector — `outputSchema: slackOutputSchema()` on every action; packages/connectors/connector-openapi/src/openapi-connector.ts#buildOutputSchema; packages/connectors/connector-mcp/src/mcp-connector.ts#createMcpConnector — the tool's `outputSchema` when the server declares one. Same two-door caveat as `actions.key`.", + "evidenceScope": "in-repo", + "evidence": "packages/services/service-automation/src/engine.ts#getConnectorDescriptors — `outputSchema: a.outputSchema` on each projected action, the `GET /api/v1/automation/connectors` payload; ui/packages/app-shell/src/views/metadata-admin/inspectors/flow-scope.ts#nodeOutputRefs (a committed `connector_action` node offers one `nodeId.key` reference per top-level `properties` key of its action's `outputSchema`, through `connectorActionOutputSchema` and `connectorActionOutputKeys`; no schema, no references); ui/packages/app-shell/src/views/metadata-admin/inspectors/flow-scope.ts#resolveFlowScope (hands those references to every downstream node's and edge's data picker)", + "producer": "ui/packages/app-shell/src/views/metadata-admin/inspectors/connector-input-fields.ts#useConnectorRegistry (reads that payload); ui/packages/app-shell/src/views/metadata-admin/inspectors/FlowNodeInspector.tsx#FlowNodeInspector and ui/packages/app-shell/src/views/metadata-admin/inspectors/FlowEdgeInspector.tsx#FlowEdgeInspector (each reads the registry when the flow holds a committed connector action and passes it to `useFlowScope` — the input the read depends on); framework: packages/connectors/connector-slack/src/slack-connector.ts#createSlackConnector — `outputSchema: slackOutputSchema()` on every action; packages/connectors/connector-openapi/src/openapi-connector.ts#buildOutputSchema; packages/connectors/connector-mcp/src/mcp-connector.ts#createMcpConnector — the tool's `outputSchema` when the server declares one. Same two-door caveat as `actions.key`.", "note": "RE-GRADED dead → live 2026-09-30 (#20287): objectui#11028, read at the `.objectui-sha` pin db11afd4967, types a connector node's downstream references from it — the repair the superseded note named. The engine stores each top-level key of a node's `output` as `nodeId.key`, so the offered references are the ones a run writes. The designer offers them and stops flagging them as out of scope; nothing validates a reference against the schema." }, "effect": { diff --git a/packages/spec/liveness/dashboard.json b/packages/spec/liveness/dashboard.json index 46f33a6218b..abf1975d9e6 100644 --- a/packages/spec/liveness/dashboard.json +++ b/packages/spec/liveness/dashboard.json @@ -24,32 +24,32 @@ "id": { "status": "live", "verifiedAt": "2026-08-03", - "evidence": "objectui @91757a7: packages/plugin-dashboard/src/DashboardRenderer.tsx:298-299 (widget-title i18n key), :401-402 (drag reorder match), :656-667 (render key + design-mode selection / data-widget-id), :733 (sortable id)", + "evidence": "ui/packages/plugin-dashboard/src/DashboardRenderer.tsx (:298-299 at objectui @91757a7) (widget-title i18n key), :401-402 (drag reorder match), :656-667 (render key + design-mode selection / data-widget-id), :733 (sortable id)", "note": "widget identity — the key every per-widget affordance (i18n, reorder, selection, drill) is looked up by." }, "title": { "status": "live", "verifiedAt": "2026-08-03", - "evidence": "objectui @91757a7: packages/plugin-dashboard/src/DashboardRenderer.tsx:297 + :525 (metric label fallback); packages/plugin-dashboard/src/DatasetWidget.tsx:383,389 (drill-drawer title), :418 (CSV export filename)", + "evidence": "ui/packages/plugin-dashboard/src/DashboardRenderer.tsx (:297 at objectui @91757a7) + :525 (metric label fallback); packages/plugin-dashboard/src/DatasetWidget.tsx:383,389 (drill-drawer title), :418 (CSV export filename)", "note": "rendered as the widget card heading; also the drill-drawer title and the export filename." }, "description": { "status": "live", "verifiedAt": "2026-08-03", - "evidence": "objectui @91757a7: packages/plugin-dashboard/src/DashboardRenderer.tsx:306-308 (tWidgetDescription → card sub-heading)", + "evidence": "ui/packages/plugin-dashboard/src/DashboardRenderer.tsx (:306-308 at objectui @91757a7) (tWidgetDescription → card sub-heading)", "note": "rendered under the widget title, localized per dashboard+widget id." }, "type": { "status": "live", "verifiedAt": "2026-08-03", - "evidence": "objectui @91757a7: packages/plugin-dashboard/src/widgetDispatch.ts (classifyWidgetType — series/metric/table/custom families + spec aliases); objectui: packages/plugin-dashboard/src/DashboardRenderer.tsx:419 and :438; objectui: packages/plugin-dashboard/src/DatasetWidget.tsx:169-175", + "evidence": "ui/packages/plugin-dashboard/src/widgetDispatch.ts (classifyWidgetType — series/metric/table/custom families + spec aliases); ui/packages/plugin-dashboard/src/DashboardRenderer.tsx (:419 at objectui @91757a7) and :438; ui/packages/plugin-dashboard/src/DatasetWidget.tsx (:169-175 at objectui @91757a7)", "note": "the render dispatch key — chooses the renderer family and, with it, the grid span." }, "chartConfig": { "status": "live", "verifiedAt": "2026-09-20", - "evidenceScope": "cross-repo", - "evidence": "objectui @53ded82bf: packages/core/src/utils/chart-presentation.ts#chartConfigPresentation lowers the NINE appearance keys onto the chart schema, one guard per key — showLegend, showDataLabels, title, subtitle, description, height, annotations, interaction, and colors split two ways into the positional palette and categoryColors; objectui @53ded82bf: packages/plugin-dashboard/src/DatasetWidget.tsx#DatasetWidget (calls chartConfigPresentation and spreads the result onto the chart schema handed to SchemaRenderer); objectui @53ded82bf: packages/plugin-dashboard/src/__tests__/DatasetWidget.chartConfig.dom.test.tsx; objectui @53ded82bf: packages/plugin-dashboard/src/__tests__/DatasetWidget.chartConfig.test.tsx", + "evidenceScope": "in-repo", + "evidence": "ui/packages/core/src/utils/chart-presentation.ts#chartConfigPresentation lowers the NINE appearance keys onto the chart schema, one guard per key — showLegend, showDataLabels, title, subtitle, description, height, annotations, interaction, and colors split two ways into the positional palette and categoryColors; ui/packages/plugin-dashboard/src/DatasetWidget.tsx#DatasetWidget (calls chartConfigPresentation and spreads the result onto the chart schema handed to SchemaRenderer); ui/packages/plugin-dashboard/src/__tests__/DatasetWidget.chartConfig.dom.test.tsx; ui/packages/plugin-dashboard/src/__tests__/DatasetWidget.chartConfig.test.tsx", "note": "APPEARANCE ONLY on the dataset-bound chart path, as of 2026-09-20 — read PER KEY, never forwarded as a bag. This carrier is no longer `ChartConfigSchema`: the widget's `chartConfig` now parses as `DashboardWidgetChartConfigSchema`, a per-carrier `.extend()` that tombstones the four STRUCTURE keys (`type`, `xAxis`, `yAxis`, `series`) — ADR-0021 · ADR-0049, maintainer ruling 2026-09-12, decision batch #121 item 1, verbatim 「同意」. On a dataset-bound widget the dataset decides which series exist and which column each one reads, so those four are refused by name here and their rows below are graded `dead` with the removal recorded; the nine appearance keys stay live and are unchanged. ⚠️ THE EARLIER READING IS NOT WITHDRAWN, IT IS OVERRIDDEN: the 2026-09-12 per-key pass measured `xAxis`/`yAxis`/`series` LIVE on their presentation half (`mergeAuthoredPresentation` merges authored presentation onto the derived bindings, dropping `ChartAxis.field` and `ChartSeries.name`), and that measurement was correct. The ruling took the keys anyway, because `field` was a live MEMBERSHIP channel — the renderer synthesised a series from an authored axis when the chart declared none, so one authored field could silently re-point a dataset-bound series at another column while the chart still drew. The presentation those objects also carried (axis titles, formats, bounds, grid lines, per-series labels, colours and mark types) goes with them; a dataset-bound chart takes that from the dataset's own dimension and measure declarations. ⚠️ The objectui merge code at the pin `53ded82bf7a4` is therefore now unreachable metadata-side on this face and is the sibling's follow-up (objectui#4044), not evidence of a live key. `type` was already graded `dead` here and the removal executes that verdict; `aria` was removed in the same protocol-18 wave by `chart-config-aria-removed`. ⚠️ Coordinate, not key name: `ChartConfigSchema` itself keeps all four keys for the react `` tier (no dataset ⇒ the author's axes are the only ones), and `ReportChartSchema` keeps its own narrowed `xAxis`/`yAxis` — this row is `dashboard/widgets.chartConfig` and only that.", "children": { "type": { @@ -61,22 +61,22 @@ "title": { "status": "live", "verifiedAt": "2026-09-12", - "evidenceScope": "cross-repo", - "evidence": "objectui @53ded82bf: packages/core/src/utils/chart-presentation.ts#chartConfigPresentation; objectui @53ded82bf: packages/plugin-dashboard/src/DatasetWidget.tsx#DatasetWidget (calls mergeAuthoredPresentation, then chartConfigPresentation, and spreads both results onto the chart schema handed to SchemaRenderer); objectui @53ded82bf: packages/plugin-dashboard/src/__tests__/DatasetWidget.chartConfig.dom.test.tsx; objectui @53ded82bf: packages/plugin-dashboard/src/__tests__/DatasetWidget.chartConfig.test.tsx", + "evidenceScope": "in-repo", + "evidence": "ui/packages/core/src/utils/chart-presentation.ts#chartConfigPresentation; ui/packages/plugin-dashboard/src/DatasetWidget.tsx#DatasetWidget (calls mergeAuthoredPresentation, then chartConfigPresentation, and spreads both results onto the chart schema handed to SchemaRenderer); ui/packages/plugin-dashboard/src/__tests__/DatasetWidget.chartConfig.dom.test.tsx; ui/packages/plugin-dashboard/src/__tests__/DatasetWidget.chartConfig.test.tsx", "note": "Lowered onto the chart schema when it is a non-empty string, and drawn by `AdvancedChartImpl` in its ChartFrame. Pinned in the DOM: \"draws chartConfig.title / .subtitle above the plot\", with its own negative \"adds no title chrome when chartConfig declares none\"." }, "subtitle": { "status": "live", "verifiedAt": "2026-09-12", - "evidenceScope": "cross-repo", - "evidence": "objectui @53ded82bf: packages/core/src/utils/chart-presentation.ts#chartConfigPresentation; objectui @53ded82bf: packages/plugin-dashboard/src/DatasetWidget.tsx#DatasetWidget (calls mergeAuthoredPresentation, then chartConfigPresentation, and spreads both results onto the chart schema handed to SchemaRenderer); objectui @53ded82bf: packages/plugin-dashboard/src/__tests__/DatasetWidget.chartConfig.dom.test.tsx; objectui @53ded82bf: packages/plugin-dashboard/src/__tests__/DatasetWidget.chartConfig.test.tsx", + "evidenceScope": "in-repo", + "evidence": "ui/packages/core/src/utils/chart-presentation.ts#chartConfigPresentation; ui/packages/plugin-dashboard/src/DatasetWidget.tsx#DatasetWidget (calls mergeAuthoredPresentation, then chartConfigPresentation, and spreads both results onto the chart schema handed to SchemaRenderer); ui/packages/plugin-dashboard/src/__tests__/DatasetWidget.chartConfig.dom.test.tsx; ui/packages/plugin-dashboard/src/__tests__/DatasetWidget.chartConfig.test.tsx", "note": "Same lowering and the same DOM pin as `title` — a non-empty string reaches the ChartFrame." }, "description": { "status": "live", "verifiedAt": "2026-09-12", - "evidenceScope": "cross-repo", - "evidence": "objectui @53ded82bf: packages/core/src/utils/chart-presentation.ts#chartConfigPresentation; objectui @53ded82bf: packages/plugin-dashboard/src/DatasetWidget.tsx#DatasetWidget (calls mergeAuthoredPresentation, then chartConfigPresentation, and spreads both results onto the chart schema handed to SchemaRenderer); objectui @53ded82bf: packages/plugin-dashboard/src/__tests__/DatasetWidget.chartConfig.dom.test.tsx; objectui @53ded82bf: packages/plugin-dashboard/src/__tests__/DatasetWidget.chartConfig.test.tsx", + "evidenceScope": "in-repo", + "evidence": "ui/packages/core/src/utils/chart-presentation.ts#chartConfigPresentation; ui/packages/plugin-dashboard/src/DatasetWidget.tsx#DatasetWidget (calls mergeAuthoredPresentation, then chartConfigPresentation, and spreads both results onto the chart schema handed to SchemaRenderer); ui/packages/plugin-dashboard/src/__tests__/DatasetWidget.chartConfig.dom.test.tsx; ui/packages/plugin-dashboard/src/__tests__/DatasetWidget.chartConfig.test.tsx", "note": "Lowered as the chart container's accessible name — `role=\"img\"` plus `aria-label`. Pinned in the DOM by name: \"announces chartConfig.description as the chart graphic accessible name\", against the negative \"leaves the graphic unlabelled when no description is declared\". This is the accessibility channel that actually works on this face, and it is why `aria` beside it is left unforwarded rather than flattened into the same slot." }, "xAxis": { @@ -100,43 +100,43 @@ "colors": { "status": "live", "verifiedAt": "2026-09-12", - "evidenceScope": "cross-repo", - "evidence": "objectui @53ded82bf: packages/core/src/utils/chart-presentation.ts#chartConfigPresentation; objectui @53ded82bf: packages/plugin-dashboard/src/DatasetWidget.tsx#DatasetWidget (calls mergeAuthoredPresentation, then chartConfigPresentation, and spreads both results onto the chart schema handed to SchemaRenderer); objectui @53ded82bf: packages/plugin-dashboard/src/__tests__/DatasetWidget.chartConfig.test.tsx", + "evidenceScope": "in-repo", + "evidence": "ui/packages/core/src/utils/chart-presentation.ts#chartConfigPresentation; ui/packages/plugin-dashboard/src/DatasetWidget.tsx#DatasetWidget (calls mergeAuthoredPresentation, then chartConfigPresentation, and spreads both results onto the chart schema handed to SchemaRenderer); ui/packages/plugin-dashboard/src/__tests__/DatasetWidget.chartConfig.test.tsx", "note": "Both arms of the overloaded union reach the renderer, through two DIFFERENT props, and the split happens in the lowering: a `string[]` becomes the positional palette `colors`, a `{ value: color }` record becomes `categoryColors` merged OVER the category dimension field's own select/lookup option colours — the precedence the spec field comment states. Pinned in both directions: \"lowers an array `colors` as the positional palette\" / \"lowers a record `colors` as the per-category map, not as the palette\"." }, "height": { "status": "live", "verifiedAt": "2026-09-12", - "evidenceScope": "cross-repo", - "evidence": "objectui @53ded82bf: packages/core/src/utils/chart-presentation.ts#chartConfigPresentation; objectui @53ded82bf: packages/plugin-dashboard/src/DatasetWidget.tsx#DatasetWidget (calls mergeAuthoredPresentation, then chartConfigPresentation, and spreads both results onto the chart schema handed to SchemaRenderer); objectui @53ded82bf: packages/plugin-dashboard/src/__tests__/DatasetWidget.chartConfig.dom.test.tsx; objectui @53ded82bf: packages/plugin-dashboard/src/__tests__/DatasetWidget.chartConfig.test.tsx", + "evidenceScope": "in-repo", + "evidence": "ui/packages/core/src/utils/chart-presentation.ts#chartConfigPresentation; ui/packages/plugin-dashboard/src/DatasetWidget.tsx#DatasetWidget (calls mergeAuthoredPresentation, then chartConfigPresentation, and spreads both results onto the chart schema handed to SchemaRenderer); ui/packages/plugin-dashboard/src/__tests__/DatasetWidget.chartConfig.dom.test.tsx; ui/packages/plugin-dashboard/src/__tests__/DatasetWidget.chartConfig.test.tsx", "note": "Applied as the chart container's inline height when it is a finite number greater than zero; a non-positive value is dropped in favour of the container default rather than collapsing the plot. Pinned in the DOM (\"applies chartConfig.height over the container default\") and at the unit boundary (\"drops a non-positive or non-numeric height\")." }, "showLegend": { "status": "live", "verifiedAt": "2026-09-12", - "evidenceScope": "cross-repo", - "evidence": "objectui @53ded82bf: packages/core/src/utils/chart-presentation.ts#chartConfigPresentation; objectui @53ded82bf: packages/plugin-dashboard/src/DatasetWidget.tsx#DatasetWidget (calls mergeAuthoredPresentation, then chartConfigPresentation, and spreads both results onto the chart schema handed to SchemaRenderer); objectui @53ded82bf: packages/plugin-dashboard/src/__tests__/DatasetWidget.showLegend.test.tsx; objectui @53ded82bf: packages/plugin-dashboard/src/__tests__/DatasetWidget.chartConfig.test.tsx", + "evidenceScope": "in-repo", + "evidence": "ui/packages/core/src/utils/chart-presentation.ts#chartConfigPresentation; ui/packages/plugin-dashboard/src/DatasetWidget.tsx#DatasetWidget (calls mergeAuthoredPresentation, then chartConfigPresentation, and spreads both results onto the chart schema handed to SchemaRenderer); ui/packages/plugin-dashboard/src/__tests__/DatasetWidget.showLegend.test.tsx; ui/packages/plugin-dashboard/src/__tests__/DatasetWidget.chartConfig.test.tsx", "note": "The FIRST key ever lowered on this face (#3135) and for a long time the only one — the whole reason the row above it once read \"chart-config bag forwarded to the chart renderer\" and was false. Forwarded in both directions, pinned in both directions, with the third pin asserting the key is absent when no `chartConfig` is declared so the renderer default survives." }, "showDataLabels": { "status": "live", "verifiedAt": "2026-09-12", - "evidenceScope": "cross-repo", - "evidence": "objectui @53ded82bf: packages/core/src/utils/chart-presentation.ts#chartConfigPresentation; objectui @53ded82bf: packages/plugin-dashboard/src/DatasetWidget.tsx#DatasetWidget (calls mergeAuthoredPresentation, then chartConfigPresentation, and spreads both results onto the chart schema handed to SchemaRenderer); objectui @53ded82bf: packages/plugin-dashboard/src/__tests__/DatasetWidget.chartConfig.test.tsx", + "evidenceScope": "in-repo", + "evidence": "ui/packages/core/src/utils/chart-presentation.ts#chartConfigPresentation; ui/packages/plugin-dashboard/src/DatasetWidget.tsx#DatasetWidget (calls mergeAuthoredPresentation, then chartConfigPresentation, and spreads both results onto the chart schema handed to SchemaRenderer); ui/packages/plugin-dashboard/src/__tests__/DatasetWidget.chartConfig.test.tsx", "note": "Forwarded as a boolean in both directions and printed by the chart block as a Recharts `LabelList`. Pinned: \"forwards showDataLabels in both directions\"." }, "annotations": { "status": "live", "verifiedAt": "2026-09-12", - "evidenceScope": "cross-repo", - "evidence": "objectui @53ded82bf: packages/core/src/utils/chart-presentation.ts#chartConfigPresentation; objectui @53ded82bf: packages/plugin-dashboard/src/DatasetWidget.tsx#DatasetWidget (calls mergeAuthoredPresentation, then chartConfigPresentation, and spreads both results onto the chart schema handed to SchemaRenderer); objectui @53ded82bf: packages/plugin-dashboard/src/__tests__/DatasetWidget.chartConfig.test.tsx", + "evidenceScope": "in-repo", + "evidence": "ui/packages/core/src/utils/chart-presentation.ts#chartConfigPresentation; ui/packages/plugin-dashboard/src/DatasetWidget.tsx#DatasetWidget (calls mergeAuthoredPresentation, then chartConfigPresentation, and spreads both results onto the chart schema handed to SchemaRenderer); ui/packages/plugin-dashboard/src/__tests__/DatasetWidget.chartConfig.test.tsx", "note": "Forwarded when the array is non-empty, and drawn as ReferenceLine / ReferenceArea over the plot. The empty array is deliberately dropped instead of emitted — pinned as \"drops an empty annotations array instead of emitting a dead key\"." }, "interaction": { "status": "live", "verifiedAt": "2026-09-12", - "evidenceScope": "cross-repo", - "evidence": "objectui @53ded82bf: packages/core/src/utils/chart-presentation.ts#chartConfigPresentation; objectui @53ded82bf: packages/plugin-dashboard/src/DatasetWidget.tsx#DatasetWidget (calls mergeAuthoredPresentation, then chartConfigPresentation, and spreads both results onto the chart schema handed to SchemaRenderer); objectui @53ded82bf: packages/plugin-dashboard/src/__tests__/DatasetWidget.chartConfig.test.tsx", + "evidenceScope": "in-repo", + "evidence": "ui/packages/core/src/utils/chart-presentation.ts#chartConfigPresentation; ui/packages/plugin-dashboard/src/DatasetWidget.tsx#DatasetWidget (calls mergeAuthoredPresentation, then chartConfigPresentation, and spreads both results onto the chart schema handed to SchemaRenderer); ui/packages/plugin-dashboard/src/__tests__/DatasetWidget.chartConfig.test.tsx", "note": "Forwarded when it is a non-array object, and honoured by the chart block as the tooltip toggle plus `Brush`. Pinned with `annotations` in \"forwards annotations and the interaction toggles\"." }, "aria": { @@ -150,14 +150,14 @@ "colorVariant": { "status": "live", "verifiedAt": "2026-08-09", - "evidenceScope": "cross-repo", - "evidence": "objectui @09987b68: packages/plugin-dashboard/src/DatasetWidget.tsx:641 — metricAccentTextClass(widget?.colorVariant) tints the dataset-bound metric card's value; objectui @09987b68: packages/plugin-dashboard/src/colorVariants.ts:44-53 — VARIANT_TEXT_CLASSES, the eight tokens of WidgetColorVariantSchema; objectui @09987b68: packages/plugin-dashboard/src/__tests__/DatasetWidget.colorVariant.test.tsx — renders every token and derives the vocabulary from the spec enum at runtime; packages/platform-objects/src/apps/dashboards/system_overview.dashboard.ts — 7 of the 16 authored sites this now paints", + "evidenceScope": "in-repo", + "evidence": "ui/packages/plugin-dashboard/src/DatasetWidget.tsx:1296 — metricAccentTextClass(widget?.colorVariant) tints the dataset-bound metric card's value; ui/packages/plugin-dashboard/src/colorVariants.ts:44-53 — VARIANT_TEXT_CLASSES, the eight tokens of WidgetColorVariantSchema; ui/packages/plugin-dashboard/src/__tests__/DatasetWidget.colorVariant.test.tsx — renders every token and derives the vocabulary from the spec enum at runtime; packages/platform-objects/src/apps/dashboards/system_overview.dashboard.ts — 7 of the 16 authored sites this now paints", "note": "CORRECTED dead → live 2026-08-09 (#6774). #5010 ruling B chose ENFORCE over remove — the declaration stays and objectui implements it — and objectui#3359 / PR objectui#3799 (merge `c4c0ac897`, 2026-08-08) did: DatasetWidget resolves the declared token through the accent table MetricWidget already shared, so the 16 authored sites (7 in packages/platform-objects' system_overview, 9 across examples/app-showcase) paint the accent they declare. Absorbed by this repo's `.objectui-sha` pin `09987b68` — `git merge-base --is-ancestor c4c0ac897 09987b68` re-verified against this branch's base rather than inherited from the unlock comment. No declaration, and the enum's own `default`, still resolve to no class, so every widget that never authored the key renders byte-identically. The 2026-08-03 `dead` verdict was accurate when written and is kept here as history: every read of `widget.colorVariant` was then an AUTHORING surface (WidgetConfigPanel.tsx:407, plugin-designer/DashboardEditor.tsx:266-267, metadata-admin/inspectors/DashboardWidgetInspector.tsx:380, DashboardWithConfig.tsx:113), DashboardRenderer built the metric component schema explicitly, and only `options.colorVariant` ever reached MetricWidget. `authorWarn`/`authorHint` are dropped with the flip — the CLI advisory told authors to relocate a key that now works, and PR #5255's positive contrast pinning that warning is released in the same change." }, "requiresObject": { "status": "live", "verifiedAt": "2026-08-03", - "evidence": "objectui @91757a7: packages/app-shell/src/views/DashboardView.tsx:143-147 (prunes widgets whose requiresObject is not in the runtime SchemaRegistry)", + "evidence": "ui/packages/app-shell/src/views/DashboardView.tsx:215-219 (prunes widgets whose requiresObject is not in the runtime SchemaRegistry)", "note": "client-side capability gate. NOT gated server-side — packages/rest/src/rest-server.ts:1818 names `requiresObject` as deliberately not filtered there, unlike its `requiresService` sibling." }, "requiresService": { @@ -184,49 +184,49 @@ "filter": { "status": "live", "verifiedAt": "2026-08-03", - "evidence": "objectui @91757a7: packages/plugin-dashboard/src/DatasetWidget.tsx:221 (→ DatasetSelection.runtimeFilter); packages/plugin-dashboard/src/DashboardRenderer.tsx:484,539,567 (inline paths) and :646 (AND-merged with the dashboard-scoped filter)", + "evidence": "ui/packages/plugin-dashboard/src/DatasetWidget.tsx (:221 at objectui @91757a7) (→ DatasetSelection.runtimeFilter); packages/plugin-dashboard/src/DashboardRenderer.tsx:484,539,567 (inline paths) and :646 (AND-merged with the dashboard-scoped filter)", "note": "presentation-scope filter, ANDed into the dataset query; also the base a dashboard global filter merges into." }, "compareTo": { "status": "live", "verifiedAt": "2026-08-04", - "evidence": "packages/services/service-analytics/src/dataset-executor.ts — runCompare() reads `compareTo.kind` (shiftRange) and resolves `compareTo.dimension` via resolveCompareDimension(); packages/spec/src/contracts/analytics-service.ts DatasetCompareTo is the same `{ kind, dimension? }` shape the widget now declares; objectui @91757a7: packages/plugin-dashboard/src/DatasetWidget.tsx:163-168 (forwards the structured object into DatasetSelection.compareTo)", + "evidence": "packages/services/service-analytics/src/dataset-executor.ts — runCompare() reads `compareTo.kind` (shiftRange) and resolves `compareTo.dimension` via resolveCompareDimension(); packages/spec/src/contracts/analytics-service.ts DatasetCompareTo is the same `{ kind, dimension? }` shape the widget now declares; ui/packages/plugin-dashboard/src/DatasetWidget.tsx (:163-168 at objectui @91757a7) (forwards the structured object into DatasetSelection.compareTo)", "note": "CONVERGED 2026-08-04 (#5011) — this entry SUPERSEDES the path-split record it carried, which is now history rather than the shape. What it recorded was real: the widget declared three arms ('previousPeriod' / 'previousYear' / { offset }) that only the LEGACY inline object-provider chart path could run (objectui packages/core/src/utils/compare-to.ts shiftFilterByCompareTo), while on the ADR-0021 dataset path — the one the spec calls the single author-facing analytics shape — DatasetWidget deliberately DROPPED the two string arms and forwarded `{ offset }` into a contract with no `offset` in it, so dataset-executor.ts threw 'compareTo requires a timeDimension \"undefined\"'. Same key, two fates, the failing one blessed. The fix converged the widget's vocabulary onto the executor's: `compareTo` is now `{ kind, dimension? }`, a thin projection of DatasetSelection.compareTo, so `declared = enforced` holds by construction with no second vocabulary left to drift. `dimension` is optional and resolved BY THE EXECUTOR (exactly one dated time dimension → that one; zero or several → a loud error listing candidates) — a producer-side resolution rule, not the consumer-side tolerance PD #12 forbids. `{ offset }` retired via the ADR-0087 `dashboard-widget-compareto-converged` conversion (+ the `dashboard-widget-compareto-offset` semantic migration for durations with no faithful target). Still LIVE, and now on the canonical path: the reason the verdict is unchanged is that the CONSUMER was never missing, only the agreement about what it consumes. ⚠️ One half is out of this repo: objectui's legacy inline chart path (DashboardRenderer.tsx:495 → ObjectChart, CompareToConfig in packages/core/src/utils/compare-to.ts) still expects the retired three-arm shape and adapts in objectui#3337, which also deletes the now-unnecessary DatasetWidget.tsx:163-168 string-drop workaround. Until that lands, read this `live` as 'the dataset path honours it'; the inline path is mid-handoff, not unread." }, "dataset": { "status": "live", "verifiedAt": "2026-08-03", - "evidence": "objectui @91757a7: packages/plugin-dashboard/src/DatasetWidget.tsx:160 (dataset name → queryDataset)", + "evidence": "ui/packages/plugin-dashboard/src/DatasetWidget.tsx:620 (dataset name → queryDataset)", "note": "ADR-0021 binding — the semantic-layer dataset the widget selects from." }, "dimensions": { "status": "live", "verifiedAt": "2026-08-03", - "evidence": "objectui @91757a7: packages/plugin-dashboard/src/DatasetWidget.tsx:161, :170-175 (also decides metric vs matrix shape), :267 (posted selection)", + "evidence": "ui/packages/plugin-dashboard/src/DatasetWidget.tsx (:161 at objectui @91757a7), :170-175 (also decides metric vs matrix shape), :267 (posted selection)", "note": "ADR-0021 dimension selection by name." }, "values": { "status": "live", "verifiedAt": "2026-08-03", - "evidence": "objectui @91757a7: packages/plugin-dashboard/src/DatasetWidget.tsx:162 (measure names → DatasetSelection.measures)", + "evidence": "ui/packages/plugin-dashboard/src/DatasetWidget.tsx:622 (measure names → DatasetSelection.measures)", "note": "ADR-0021 measure selection by name. Single-series chart types (packages/spec/src/ui/dashboard.zod.ts, the dimensioned arm of checkDashboardWidgetChartMeasureArity; moved out of spec source, #23434): in objectui @89cad75d5570 packages/plugin-charts/src/AdvancedChartImpl.tsx#AdvancedChartImplInner the `pie` / `donut`, `funnel`, `treemap` and `sankey` arms each bind `series[0]?.dataKey` and read no other series; `radar` draws every series and `scatter` refuses a second series on the chart." }, "layout": { "status": "live", "verifiedAt": "2026-08-03", - "evidence": "objectui @91757a7: packages/plugin-dashboard/src/DashboardRenderer.tsx:210-212 — column-count inference, and :423 the effective grid span with an auto-flow fallback when absent; objectui: packages/plugin-dashboard/src/DashboardGridLayout.tsx", + "evidence": "ui/packages/plugin-dashboard/src/DashboardRenderer.tsx:369-371 — column-count inference, and :703 the effective grid span with an auto-flow fallback when absent; ui/packages/plugin-dashboard/src/DashboardGridLayout.tsx", "note": "grid position; absence means auto-place, which is the Studio designer's default path." }, "options": { "status": "live", "verifiedAt": "2026-08-03", - "evidence": "objectui @91757a7: packages/plugin-dashboard/src/DashboardRenderer.tsx:439 — spread into every component schema; objectui: packages/plugin-dashboard/src/DatasetWidget.tsx:190-191 — declared query keys lowered into the DatasetSelection", + "evidence": "ui/packages/plugin-dashboard/src/DashboardRenderer.tsx:755 — spread into every component schema; ui/packages/plugin-dashboard/src/DatasetWidget.tsx:714-715 — declared query keys lowered into the DatasetSelection", "note": "the renderer-extras bag; its four DECLARED keys (dateGranularity / sortBy / sortOrder / limit, framework#3588) reach the analytics query, the rest pass through to the renderer. The #20960 census (packages/spec/src/ui/dashboard.zod.ts; moved out of spec source, #23434): on the dataset-bound path objectui main @5262f7dd packages/plugin-dashboard/src/DatasetWidget.tsx#DatasetWidget reads the declared query keys (`dateGranularity`, `sortBy`, `sortOrder`, `limit`) and the metric tile's `description` (#resolvedSubCaption) and nothing else of the bag; none of the keys this repo's examples wrote (`format`, `currency`, `color`, `suffix`, `showLegend`, `horizontal`, `showDataLabels`) is read there, only the legacy inline arm spreads the bag, and each was removed from its producer." }, "filterBindings": { "status": "live", "verifiedAt": "2026-08-28", - "evidence": "objectui @91757a7: packages/core/src/utils/dashboard-filters.ts:343 (resolveBoundField), :380-386 (explicit binding honoured, unbound reported); framework: packages/lint/src/validate-widget-bindings.ts#effectiveFilterField", + "evidence": "ui/packages/core/src/utils/dashboard-filters.ts:697 (resolveBoundField), :734-740 (explicit binding honoured, unbound reported); framework: packages/lint/src/validate-widget-bindings.ts#effectiveFilterField", "note": "per-widget binding of a dashboard-level filter to one of this widget's fields, or `false` to opt out (framework#2501). 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED on the framework leg — `:280` had rotted onto the closing brace of `dashboardFilterDefs`; the reader is ten lines below it in `effectiveFilterField`, where a string re-targets, `false` opts out, and both win over the legacy `targetWidgets` allow-list. Re-closed by hand against 8cb96ec41." }, "suppressWarnings": { diff --git a/packages/spec/liveness/dataset.json b/packages/spec/liveness/dataset.json index f17cc307af2..79bb9a35d82 100644 --- a/packages/spec/liveness/dataset.json +++ b/packages/spec/liveness/dataset.json @@ -72,8 +72,8 @@ "nullLabel": { "status": "live", "verifiedAt": "2026-10-23", - "evidenceScope": "cross-repo", - "evidence": "objectui @1d178e06ac5e (the `.objectui-sha` pin, objectui#13354) packages/plugin-dashboard/src/DatasetWidget.tsx#declaredNullCategoryLabel reads the category dimension's `fields[].nullLabel` off the dataset answer, and the widget passes it as the chart's `nullCategoryLabel` over its global `chart.nullCategory` default, so the null bucket is drawn, and drilled, under the declared name. On a two-dimension pivot it applies only when both dimensions declare the same name (`buildChartSeries` takes one null label for both arms) — re-read 2026-10-23", + "evidenceScope": "in-repo", + "evidence": "objectui @1d178e06ac5e (the `.objectui-sha` pin, ui/packages/plugin-dashboard/src/DatasetWidget.tsx#declaredNullCategoryLabel reads the category dimension's `fields[].nullLabel` off the dataset answer, and the widget passes it as the chart's `nullCategoryLabel` over its global `chart.nullCategory` default, so the null bucket is drawn, and drilled, under the declared name. On a two-dimension pivot it applies only when both dimensions declare the same name (`buildChartSeries` takes one null label for both arms) — re-read 2026-10-23", "producer": "packages/services/service-analytics/src/analytics-service.ts#enrichResultColumns — the column-description pass shared by the live query and the draft-data preview resolves the dimension's `nullLabel` at the request locale (resolveI18nLabel, the `label` resolver) and writes it onto the dimension column's descriptor as `fields[].nullLabel` (objectstack#25402), the channel the renderer reads; a measure column never carries it", "note": "FLIPPED planned → live 2026-10-23 (#25330, ruling 6358843349 letter A), the `print` (#23710) and `runAction` (#10068) shape: declared spec-first by #25391 with `authorWarn`, its consumer landed in three steps — objectstack#25402 carries the name on the answer's column descriptor (the producer), objectui#13354 draws the bucket under it, and this change moves `.objectui-sha` past that merge and re-measures at the pin. `authorWarn` goes with the flip. Display copy only: grouping is unchanged. First author: `packages/platform-objects` `system.datasets.ts`, whose `user_id` dimension names System Overview's null user bucket \"System\". Readers that still use only the global default (noted, not carried): objectui's `DatasetReportRenderer` and `ObjectChart`, and the flat table, which draws a null dimension value as `—`." } diff --git a/packages/spec/liveness/datasource.json b/packages/spec/liveness/datasource.json index be6e63358fc..2a3e6c2fba7 100644 --- a/packages/spec/liveness/datasource.json +++ b/packages/spec/liveness/datasource.json @@ -11,9 +11,9 @@ "label": { "status": "live", "verifiedAt": "2026-08-10", - "evidenceScope": "cross-repo", - "evidence": "objectui @e9ab52f9: packages/app-shell/src/views/metadata-admin/previews/DatasourcePreview.tsx:96 takes `label` off the datasource draft with the datasource's own `name` as fallback, and packages/app-shell/src/views/metadata-admin/previews/DatasourcePreview.tsx:146 renders it as the preview card's title", - "producer": "objectui @e9ab52f9: packages/app-shell/src/views/metadata-admin/previews/index.ts:44 registers DatasourcePreview against the `datasource` metadata type, and packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx:949 resolves that registration and hands the component the draft being edited — the read point is reachable, not merely present", + "evidenceScope": "in-repo", + "evidence": "ui/packages/app-shell/src/views/metadata-admin/previews/DatasourcePreview.tsx:92 takes `label` off the datasource draft with the datasource's own `name` as fallback, and ui/packages/app-shell/src/views/metadata-admin/previews/DatasourcePreview.tsx:132 renders it as the preview card's title", + "producer": "ui/packages/app-shell/src/views/metadata-admin/previews/index.ts:46 registers DatasourcePreview against the `datasource` metadata type, and ui/packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx (:949 at objectui @e9ab52f9) resolves that registration and hands the component the draft being edited — the read point is reachable, not merely present", "note": "display metadata (Setup → Datasources list). ADR-0033 docs-shaped, deliberately kept, not authorWarn'd. PROSE RECONCILED 2026-08-10 (#7427): the old clause 'No runtime consumer by design' is superseded as a whole-system claim — a designer preview rendering a key to a human IS a runtime consumer (maintainer ruling 2026-08-10, #7131; README, 'Designer previews count as consumers'), and the metadata-admin datasource preview has been showing this value as the card title the whole time. What the old clause got right, and what is preserved: NO CONNECTION-PATH CONSUMER reads it — nothing in `ConnectableDatasource` / `DatasourceConnectionSpec` / the driver factory takes `label`, and `live` here does not mean a driver acquired a use for it. UNCHANGED by the reconciliation: still ADR-0033 docs-shaped, still deliberately KEPT, still not authorWarn'd. Status did not move (this row was already `live`); what it gained is a falsifiable pointer, which is the thing a `live` verdict is supposed to be. Reachability checked rather than assumed: `datasource` IS a registered metadata kind (the `datasource: DatasourceSchema` row of `BUILTIN_METADATA_TYPE_SCHEMAS` in packages/spec/src/kernel/metadata-type-schemas.ts), so the metadata-admin route that resolves this preview is a live authoring path — contrast `validation.label`, whose preview renders the same way into a kind ADR-0088 retired. 2026-08-28 (commit 9ee2dcfbd): that reachability pointer was written `metadata-type-schemas.ts:119` and had drifted onto the `flow: FlowSchema` row of the same map — the right map, the wrong kind, which is the one rot shape a reader is least likely to catch by eye. Restated by symbol. The objectui-realm citations in `evidence` / `producer` are pinned to @e9ab52f9 and are not re-graded here (this batch reads the framework tree only)." }, "driver": { @@ -95,9 +95,9 @@ "description": { "status": "live", "verifiedAt": "2026-08-10", - "evidenceScope": "cross-repo", - "evidence": "objectui @e9ab52f9: packages/app-shell/src/views/metadata-admin/previews/DatasourcePreview.tsx:97 takes `description` off the datasource draft, and packages/app-shell/src/views/metadata-admin/previews/DatasourcePreview.tsx:150 renders it beneath the title whenever it is non-empty", - "producer": "objectui @e9ab52f9: packages/app-shell/src/views/metadata-admin/previews/index.ts:44 registers DatasourcePreview against the `datasource` metadata type, and packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx:949 resolves that registration and hands the component the draft being edited", + "evidenceScope": "in-repo", + "evidence": "ui/packages/app-shell/src/views/metadata-admin/previews/DatasourcePreview.tsx:93 takes `description` off the datasource draft, and ui/packages/app-shell/src/views/metadata-admin/previews/DatasourcePreview.tsx:136 renders it beneath the title whenever it is non-empty", + "producer": "ui/packages/app-shell/src/views/metadata-admin/previews/index.ts:46 registers DatasourcePreview against the `datasource` metadata type, and ui/packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx (:949 at objectui @e9ab52f9) resolves that registration and hands the component the draft being edited", "note": "internal documentation. ADR-0033 docs-shaped, deliberately kept, not authorWarn'd — and displayed under the title in the same preview card as `label`. Prose reconciled with `label` on 2026-08-10 (#7427), same ruling, same preserved caveat: no connection-path consumer reads it, and `live` does not mean one acquired a use for it." }, "active": { diff --git a/packages/spec/liveness/email_template.json b/packages/spec/liveness/email_template.json index 1e6bb6f9451..3aace7ee6e8 100644 --- a/packages/spec/liveness/email_template.json +++ b/packages/spec/liveness/email_template.json @@ -12,9 +12,9 @@ "label": { "status": "live", "verifiedAt": "2026-10-11", - "evidence": "objectui @4997995022f8: packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx#defaultColumns:838 (the Studio metadata list's default `label` column, which line 421 falls back to when a type registers no `listColumns`, and whose cell line 687 reads as `row.item[c.key]`); objectui @4997995022f8: packages/app-shell/src/views/metadata-admin/default-schemas.ts#registerDefaultMetadataSchemas:336 (`email_template` is registered with a `defaultSchema` and a `fieldOrder` and no `listColumns` of its own, so every row of its list renders the authored label)", + "evidence": "ui/packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx#defaultColumns:1292 (the Studio metadata list's default `label` column, which line 573 falls back to when a type registers no `listColumns`, and whose cell line 902 reads as `row.item[c.key]`); ui/packages/app-shell/src/views/metadata-admin/default-schemas.ts#registerDefaultMetadataSchemas:331 (`email_template` is registered with a `defaultSchema` and a `fieldOrder` and no `listColumns` of its own, so every row of its list renders the authored label)", "note": "Docs-shaped: shown to a human in the Studio metadata list, never by the send path. Until 2026-10-11 it was materialized into the `sys_email_template.label` column (the Setup record title); that projection retired with the rows (ADR-0131 D6/D10), and the Studio list read — measured at the `.objectui-sha` pin 4997995022f8 — is the consumer now, under the designer-previews-count-as-consumers ruling (#7131).", - "evidenceScope": "cross-repo" + "evidenceScope": "in-repo" }, "category": { "status": "dead", @@ -86,9 +86,9 @@ "description": { "status": "live", "verifiedAt": "2026-10-11", - "evidence": "objectui @4997995022f8: packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx#defaultColumns:839 (the Studio metadata list's default `description` column, which line 421 falls back to when a type registers no `listColumns`, and whose cell line 687 reads as `row.item[c.key]`); objectui @4997995022f8: packages/app-shell/src/views/metadata-admin/default-schemas.ts#registerDefaultMetadataSchemas:336 (`email_template` is registered with a `defaultSchema` and a `fieldOrder` and no `listColumns` of its own)", + "evidence": "ui/packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx#defaultColumns:1293 (the Studio metadata list's default `description` column, which line 573 falls back to when a type registers no `listColumns`, and whose cell line 902 reads as `row.item[c.key]`); ui/packages/app-shell/src/views/metadata-admin/default-schemas.ts#registerDefaultMetadataSchemas:331 (`email_template` is registered with a `defaultSchema` and a `fieldOrder` and no `listColumns` of its own)", "note": "Docs-shaped: read by humans in Studio, never by the send path. Until 2026-10-11 it was materialized into `sys_email_template.description`; that projection retired with the rows (ADR-0131 D6/D10), and the Studio list read — measured at the `.objectui-sha` pin 4997995022f8 — is the consumer now (#7131).", - "evidenceScope": "cross-repo" + "evidenceScope": "in-repo" } } } diff --git a/packages/spec/liveness/field.json b/packages/spec/liveness/field.json index 382c36cca8d..aa761d7f5a3 100644 --- a/packages/spec/liveness/field.json +++ b/packages/spec/liveness/field.json @@ -58,7 +58,7 @@ "children": { "label": { "status": "live", - "evidence": "objectui @63d00da: packages/fields/src/index.tsx#SelectCellRenderer", + "evidence": "ui/packages/fields/src/index.tsx#SelectCellRenderer", "verifiedAt": "2026-10-16", "note": "The badge text of a select cell (`option?.label`), and every select / radio control's item label." }, @@ -70,13 +70,13 @@ }, "description": { "status": "live", - "evidence": "objectui @63d00da: packages/fields/src/widgets/LookupField.tsx#LookupField", + "evidence": "ui/packages/fields/src/widgets/LookupField.tsx#LookupField", "verifiedAt": "2026-10-16", "note": "Lookup option search matches it beside the label (objectui#6153)." }, "color": { "status": "live", - "evidence": "objectui @63d00da: packages/fields/src/index.tsx#SelectCellRenderer", + "evidence": "ui/packages/fields/src/index.tsx#SelectCellRenderer", "verifiedAt": "2026-10-16", "note": "The badge / dot colour of a select cell (`option?.color`)." }, @@ -146,8 +146,8 @@ "dueLike": { "status": "live", "verifiedAt": "2026-10-09", - "evidenceScope": "cross-repo", - "evidence": "objectui @55e90fd: packages/fields/src/index.tsx#resolveDueLike (`declared === true` on the cell's field, read by both the date and the datetime cell renderer: the relative overdue wording and the overdue colour once the date has passed); objectui @55e90fd: packages/plugin-detail/src/fieldEnrichment.ts#ENRICHED_FIELD_METADATA_KEYS (copies the object-schema field's `dueLike` onto the detail field, so the record detail sections hand it to that cell). The same two reads are present at the `.objectui-sha` pin f0268ad78", + "evidenceScope": "in-repo", + "evidence": "ui/packages/fields/src/index.tsx#resolveDueLike (`declared === true` on the cell's field, read by both the date and the datetime cell renderer: the relative overdue wording and the overdue colour once the date has passed); ui/packages/plugin-detail/src/fieldEnrichment.ts#ENRICHED_FIELD_METADATA_KEYS (copies the object-schema field's `dueLike` onto the detail field, so the record detail sections hand it to that cell). The same two reads are present at the `.objectui-sha` pin f0268ad78", "note": "Deadline semantic, `date` / `datetime` only (DEADLINE_FIELD_TYPES; the superRefine refuses it on every other type). Declared by #22227 as the protocol half of objectui#11815's maintainer ruling D (declared-only overdue). LIVE on the detail surfaces today: an authored TRUE paints the overdue affordance there even where the field name matches no pattern. PARTIAL until objectui#11815 (Blocked-by #22227) lands: objectui's grid copy set does not carry the key yet, and objectui still ALSO infers due-likeness from the field name (`DUE_LIKE_FIELD_NAME`), which that card retires — after it, absent means not a deadline everywhere, as the key's describe states. Display only: nothing on the write path reads it." }, "settledWhen": { @@ -195,9 +195,9 @@ "widget": { "status": "live", "verifiedAt": "2026-08-09", - "evidenceScope": "cross-repo", - "evidence": "objectui @09987b68: packages/plugin-form/src/ObjectForm.tsx:585-590 — threads the per-field widget override into the renderer, naming permission-facet-link; objectui @09987b68: packages/components/src/renderers/form/form.tsx:230 — resolveWidgetType resolves widget || field.widget || type; objectui @09987b68: packages/plugin-detail/src/index.tsx:563 — ComponentRegistry.register('permission-facet-link', withFieldCarrier(PermissionFacetLink), { namespace: 'field' }); objectui @09987b68: packages/fields/src/index.tsx:2203-2205 — fieldWidgetMap carries object-ref / filter-condition / recipient-picker", - "note": "objectui generic form widget override. ObjectForm (packages/plugin-form/src/ObjectForm.tsx) threads `field.widget` into the renderer; form.tsx resolves `widget || type` and looks it up as `field:` in the ComponentRegistry (renderFieldComponent). Used to render pick-not-type controls for sys_sharing_rule (object-ref / filter-condition / recipient-picker) and sys_permission_set (permission-facet-link). Degrades to the `type` renderer when the widget is unregistered. CORRECTED 2026-08-09 (#6773): this note named `capability-multiselect` as sys_permission_set's widget, and that half was never true. No producer stamped it — ADR-0056 P1 stamps `permission-facet-link` on all six facets through the single `applyFieldWidgetOverrides` choke point (objectui packages/data-objectstack/src/index.ts:2492-2501) — and `field:capability-multiselect` was registered only by the docs-site-only `registerFields()`, never by the live `registerAllFields()` walk over `fieldWidgetMap`, so authoring it always fell through to the `type` renderer. objectui#3308 / PR objectui#3793 then retired the name outright (ADR-0049 enforce-or-remove); it is absent from `packages/*/src` at pin `09987b68` apart from tombstone comments and a retirement pin test. The other three names were re-checked at the same pin and are still in `fieldWidgetMap`. The verdict itself is unchanged and was never at risk — `widget` is live on the sys_sharing_rule trio alone; what was wrong was one worked example, i.e. false evidence in the base the next enforce-or-remove audit reads (the #5175 lesson)." + "evidenceScope": "in-repo", + "evidence": "ui/packages/plugin-form/src/ObjectForm.tsx (:585-590 at objectui @09987b68) — threads the per-field widget override into the renderer, naming permission-facet-link; ui/packages/components/src/renderers/form/form.tsx (:230 at objectui @09987b68) — resolveWidgetType resolves widget || field.widget || type; ui/packages/fields/src/index.tsx (:2203-2205 at objectui @09987b68) — fieldWidgetMap carries object-ref / filter-condition / recipient-picker", + "note": "objectui generic form widget override. ObjectForm (packages/plugin-form/src/ObjectForm.tsx) threads `field.widget` into the renderer; form.tsx resolves `widget || type` and looks it up as `field:` in the ComponentRegistry (renderFieldComponent). Used to render pick-not-type controls for sys_sharing_rule (object-ref / filter-condition / recipient-picker) and sys_permission_set (permission-facet-link). Degrades to the `type` renderer when the widget is unregistered. CORRECTED 2026-08-09 (#6773): this note named `capability-multiselect` as sys_permission_set's widget, and that half was never true. No producer stamped it — ADR-0056 P1 stamps `permission-facet-link` on all six facets through the single `applyFieldWidgetOverrides` choke point (objectui packages/data-objectstack/src/index.ts:2492-2501) — and `field:capability-multiselect` was registered only by the docs-site-only `registerFields()`, never by the live `registerAllFields()` walk over `fieldWidgetMap`, so authoring it always fell through to the `type` renderer. objectui#3308 / PR objectui#3793 then retired the name outright (ADR-0049 enforce-or-remove); it is absent from `packages/*/src` at pin `09987b68` apart from tombstone comments and a retirement pin test. The other three names were re-checked at the same pin and are still in `fieldWidgetMap`. The verdict itself is unchanged and was never at risk — `widget` is live on the sys_sharing_rule trio alone; what was wrong was one worked example, i.e. false evidence in the base the next enforce-or-remove audit reads (the #5175 lesson). 2026-10-26 (#25712, objectui merged into this repo as ui/): the `plugin-detail/src/index.tsx:563` clause (the `permission-facet-link` widget registration) was dropped, not repointed: objectui#12998 removed that widget, and the key-mention check found the file no longer names `widget` once the path became in-repo. The other three readers keep their @09987b68 reading." }, "requiredPermissions": { "status": "live", @@ -270,9 +270,9 @@ "useGrouping": { "status": "live", "verifiedAt": "2026-09-30", - "evidenceScope": "cross-repo", - "evidence": "objectui: packages/core/src/utils/number-display.ts#shouldGroupDisplayNumber makes the one grouping decision, and an authored boolean `useGrouping` answers it first, in either direction, so only an ABSENT key reaches the interim heuristic, currency ⇒ grouped, else a declared `scale: 0` ⇒ ungrouped; objectui: packages/core/src/utils/number-display.ts#formatDisplayNumber takes the field's `useGrouping`, asks that decision and spells the answer for `Intl.NumberFormat`, an ungrouped answer as `useGrouping: false` and an authored `true` as `useGrouping: true`, the 1:1 mapping this key declares; objectui: packages/fields/src/index.tsx#NumberCellRenderer reads `field.useGrouping`, booleans only, and hands it to formatDisplayNumber — all three read at objectui @db11afd4967c, which is this repo's `.objectui-sha` pin, so the reader is the one inside the console this repo builds against rather than on objectui main", - "producer": "objectui: packages/plugin-grid/src/ObjectGrid.tsx#generateColumns copies the object definition's `useGrouping` onto the column's field metadata beside `scale` on each of its three column paths; objectui: packages/plugin-detail/src/fieldEnrichment.ts#ENRICHED_FIELD_METADATA_KEYS lists it among the object-metadata keys copied onto a detail-view field; objectui: packages/plugin-detail/src/RecordDetailPanel.tsx#buildRecordDetailFields, packages/plugin-detail/src/RelatedList.tsx#effectiveColumns and packages/plugin-list/src/ObjectGallery.tsx#ObjectGallery relay it beside `scale` the same way — seven hand-copy sites, read at objectui @db11afd4967c, the pin. They are the producer half because the cell reads the field metadata these sites build, never the object definition: a site that dropped the key would leave an authored `false` year reading `2,026` on that surface", + "evidenceScope": "in-repo", + "evidence": "ui/packages/core/src/utils/number-display.ts#shouldGroupDisplayNumber makes the one grouping decision, and an authored boolean `useGrouping` answers it first, in either direction, so only an ABSENT key reaches the interim heuristic, currency ⇒ grouped, else a declared `scale: 0` ⇒ ungrouped; ui/packages/core/src/utils/number-display.ts#formatDisplayNumber takes the field's `useGrouping`, asks that decision and spells the answer for `Intl.NumberFormat`, an ungrouped answer as `useGrouping: false` and an authored `true` as `useGrouping: true`, the 1:1 mapping this key declares; ui/packages/fields/src/index.tsx#NumberCellRenderer reads `field.useGrouping`, booleans only, and hands it to formatDisplayNumber — all three read at objectui @db11afd4967c, which is this repo's `.objectui-sha` pin, so the reader is the one inside the console this repo builds against rather than on objectui main", + "producer": "ui/packages/plugin-grid/src/ObjectGrid.tsx#generateColumns copies the object definition's `useGrouping` onto the column's field metadata beside `scale` on each of its three column paths; ui/packages/plugin-detail/src/fieldEnrichment.ts#ENRICHED_FIELD_METADATA_KEYS lists it among the object-metadata keys copied onto a detail-view field; ui/packages/plugin-detail/src/RecordDetailPanel.tsx#buildRecordDetailFields, ui/packages/plugin-detail/src/RelatedList.tsx#effectiveColumns and ui/packages/plugin-list/src/ObjectGallery.tsx#ObjectGallery relay it beside `scale` the same way — seven hand-copy sites, read at objectui @db11afd4967c, the pin. They are the producer half because the cell reads the field metadata these sites build, never the object definition: a site that dropped the key would leave an authored `false` year reading `2,026` on that surface", "note": "[#7768] Declared 2026-08-11 as Option A of that card's fork, the narrowest shape with measured pull. Maps 1:1 to `Intl.NumberFormat`'s `useGrouping`. FLIPPED `planned` → `live` 2026-09-30, on the condition this row's earlier note set, that the objectui consumer actually read the key: the renderer half landed as objectui ae582b70a, and objectui's compare `ae582b70a...db11afd4967c` answers ahead 21, behind 0, so it is inside the console this repo pins — measured at the pin, not on objectui main. An authored boolean decides in either direction. An ABSENT key is still the renderer's call: the interim heuristic objectui#4033 introduced stays for exactly that case, and it reads the declared `scale` alone, never `min` or `max` — a declared `scale: 0` with no currency is a discrete integer, a year or an ordinal, and renders ungrouped, while any other number keeps grouping. Never `authorWarn`'d, so the flip drops no warning, and an author who set the key while it was `planned` re-authors nothing. CAVEAT — display only, and only on the surfaces whose field metadata carries the key to the cell: the seven relay sites `producer` names." }, "reference": { @@ -339,8 +339,8 @@ "rows": { "status": "live", "verifiedAt": "2026-08-31", - "evidenceScope": "cross-repo", - "evidence": "objectui: packages/fields/src/widgets/RichTextField.tsx:283 (`const rows = richField?.rows || 8;`, applied at :321 as `rows={rows}` on the inline editor surface — the one widget behind the `markdown`/`html`/`richtext` registry keys, objectui#5498); objectui: packages/fields/src/widgets/TextAreaField.tsx:94 (`const rows = textareaField?.rows || 4;`, applied at :126) — the objectui#6140 measurement table, re-confirmed 2026-08-31 on a local objectui checkout (e3d117a)", + "evidenceScope": "in-repo", + "evidence": "ui/packages/fields/src/widgets/RichTextField.tsx (:283 in objectui, commit not pinned) (`const rows = richField?.rows || 8;`, applied at :321 as `rows={rows}` on the inline editor surface — the one widget behind the `markdown`/`html`/`richtext` registry keys, objectui#5498); ui/packages/fields/src/widgets/TextAreaField.tsx (:94 in objectui, commit not pinned) (`const rows = textareaField?.rows || 4;`, applied at :126) — the objectui#6140 measurement table, re-confirmed 2026-08-31 on a local objectui checkout (e3d117a)", "note": "Inline multiline-editor height (the HTML textarea rows attribute; fullscreen/dialog surfaces size themselves and ignore it). Declared 2026-08-31 (objectui#6140, maintainer ruling 2026-08-25 Option A): the consumers shipped first, reached through an `as any` on the metadata carrier — the declaration was the missing half, aligning the TextareaFieldMetadata precedent. Authorable only on the multiline editor types (textarea/markdown/html/richtext — MULTILINE_EDITOR_FIELD_TYPES superRefine in field.zod.ts)." }, "displayField": { @@ -379,19 +379,19 @@ }, "searchable": { "status": "live", - "evidence": "objectui: packages/plugin-dashboard/src/WidgetConfigPanel.tsx:458", + "evidence": "ui/packages/plugin-dashboard/src/WidgetConfigPanel.tsx (:458 in objectui, commit not pinned)", "note": "LIVE via objectui renderer — the 2026-06 audit mis-classified as dead (renderer side not re-verified). Corrected after checking ../objectui." }, "externalId": { "status": "live", - "evidence": "objectui: apps/console/src/utils/metadataConverters.ts:125 reads field.externalId", - "note": "LIVE via objectui renderer — the 2026-06 audit mis-classified as dead (renderer side not re-verified). Corrected after checking ../objectui." + "evidence": "objectui console `metadataConverters.ts` (:125, commit not pinned) read field.externalId; that module was deleted in objectui dc14bcb for having zero importers, so no reader is cited here — re-verification owed (#25712 follow-up F23)", + "note": "LIVE via objectui renderer — the 2026-06 audit mis-classified as dead (renderer side not re-verified). Corrected after checking ../objectui. 2026-10-26 (#25712, objectui merged into this repo as ui/): the only cited reader, objectui console `metadataConverters.ts`, was deleted in objectui dc14bcb (\"zero importers\"), so the pointer is now prose. Follow-up F23 on #25712 owns finding the current reader or re-classifying." }, "currencyConfig": { "status": "live", "verifiedAt": "2026-09-27", - "evidenceScope": "cross-repo", - "evidence": "objectui: packages/i18n/src/currency.ts:60 reads field.currencyConfig.defaultCurrency ONLY when currencyConfig.currencyMode is 'fixed' — the second step of resolveFieldCurrency's precedence chain; a 'dynamic' config, or one naming no mode, skips it for the tenant default localization.currency; objectui: packages/fields/src/widgets/CurrencyField.tsx:55 calls resolveFieldCurrency and applies the resolved code at :91 through formatAmount, which passes it to Intl.NumberFormat via formatDisplayNumber — measured objectui @25c7d584", + "evidenceScope": "in-repo", + "evidence": "ui/packages/i18n/src/currency.ts:60 reads field.currencyConfig.defaultCurrency ONLY when currencyConfig.currencyMode is 'fixed' — the second step of resolveFieldCurrency's precedence chain; a 'dynamic' config, or one naming no mode, skips it for the tenant default localization.currency; ui/packages/fields/src/widgets/CurrencyField.tsx:55 calls resolveFieldCurrency and applies the resolved code at :91 through formatAmount, which passes it to Intl.NumberFormat via formatDisplayNumber — measured objectui @25c7d584", "note": "RE-CITED 2026-09-27 (#20126): since objectui#10461 the reader takes defaultCurrency only under currencyMode 'fixed', and the chain no longer cites the date/datetime-semantics ADR, which never names currency; line pointers moved with it (currency.ts :32 → :60, CurrencyField.tsx :45/:51 → :55/:91). Verdict unchanged: LIVE. RE-CITED 2026-08-10 (#7133/#7142): the old pointer 'packages/fields/src/index.tsx:469' now lands in formatRelativeDays, a date-phrase helper with no currency involvement — the resolver moved to @object-ui/i18n and packages/fields/src/currency.ts:14 only re-exports it, which is why the old line drifted onto unrelated code rather than disappearing. Verdict unchanged: LIVE, and now cited at both the reader that consumes the key and the widget that applies the resolved currency. The removed `currencyConfig.precision` (#19992; packages/spec/src/data/field.zod.ts; moved out of spec source, #23434): the widget's one width is the currency's ISO 4217 minor unit, objectui @b9814b334 packages/fields/src/widgets/CurrencyField.tsx#CurrencyField through objectui @b9814b334 packages/fields/src/widgets/number-format.ts#currencyDisplayWidth over objectui @b9814b334 packages/fields/src/currency.ts#currencyFractionDigits, and no objectui code reads `currencyConfig.precision`; re-read 2026-10-15, first cited sha-less (the source then named `currencyFractionDigits(currency)` as the widget's own line)." }, "dimensions": { @@ -400,14 +400,14 @@ }, "inlineTitle": { "status": "live", - "evidence": "objectui: packages/app-shell/src/providers/MetadataProvider.tsx + plugin-form/deriveMasterDetail.ts", + "evidence": "ui/packages/app-shell/src/providers/MetadataProvider.tsx + plugin-form/deriveMasterDetail.ts", "note": "LIVE via objectui renderer — the 2026-06 audit mis-classified as dead (renderer side not re-verified). Corrected after checking ../objectui." }, "inlineColumns": { "status": "live", "verifiedAt": "2026-08-17", - "evidenceScope": "cross-repo", - "evidence": "objectui: packages/app-shell/src/providers/MetadataProvider.tsx (attachInlineSubforms passes d.inlineColumns as the subform's columns) → plugin-form/MasterDetailForm.tsx (hydrateColumns) → fields/GridField.tsx (the name-keyed GridColumn renderer, objectui#3951)", + "evidenceScope": "in-repo", + "evidence": "ui/packages/app-shell/src/providers/MetadataProvider.tsx (attachInlineSubforms passes d.inlineColumns as the subform's columns) → plugin-form/MasterDetailForm.tsx (hydrateColumns) → fields/GridField.tsx (the name-keyed GridColumn renderer, objectui#3951)", "note": "LIVE via objectui renderer. #9227 closed the element shape (strict, name-keyed InlineGridColumnSchema): the child keys below are exactly the GridColumn keys the widget has a live read for, measured against objectui main 2026-08-17. NOTE the .objectui-sha pin (665661ab) predates objectui#3951 — the PINNED console still reads the retired field spelling; the drilled evidence below cites objectui main, which is the contract this schema mirrors. Renderer read points moved out of spec source (#23434). Column-type hydration (packages/spec/src/stack.zod.ts HYDRATED_INLINE_COLUMN_TYPE; packages/spec/src/inline-grid-column-carriers.test.ts): objectui @db11afd4967c packages/plugin-form/src/deriveMasterDetail.ts#hydrateColumns leaves a column that declares a `type` alone and otherwise sets `type: fieldTypeToColumnType(childField.type)`; objectui @db11afd4967c packages/plugin-form/src/deriveMasterDetail.ts#fieldTypeToColumnType maps each of the column schema's nine types' namesake field type to that same column type, and its only `currency` arm is the `currency` field type. Line-position field (packages/spec/src/data/inline-grid-sort-fields.ts INLINE_GRID_SORT_FIELDS): objectui @89cad75d5570 packages/plugin-form/src/deriveMasterDetail.ts#SORT_FIELD_NAMES, the same six names in the same order. Per-row expand form (packages/spec/src/data/inline-grid-columns.ts deriveInlineRowFormFields and its test): objectui @31971ff1e28f packages/plugin-form/src/deriveMasterDetail.ts#deriveFormFields is the derivation rule, and the first two test fixtures are its own cases in objectui @31971ff1e28f packages/plugin-form/src/deriveMasterDetail.test.ts (describe \"deriveFormFields (per-row expand form)\"); the offer condition (isInlineRowFormOffered) is the one objectui @31971ff1e28f packages/plugin-form/src/MasterDetailForm.tsx#MasterDetailLines applies — `inlineMode === 'form'`, or more `formFields` than `columns`.", "children": { "name": { @@ -494,35 +494,35 @@ }, "inlineAmountField": { "status": "live", - "evidence": "objectui: packages/app-shell/src/providers/MetadataProvider.tsx", + "evidence": "ui/packages/app-shell/src/providers/MetadataProvider.tsx", "note": "LIVE via objectui renderer — the 2026-06 audit mis-classified as dead (renderer side not re-verified). Corrected after checking ../objectui." }, "relatedList": { "status": "live", "verifiedAt": "2026-08-10", - "evidenceScope": "cross-repo", - "evidence": "objectui: packages/app-shell/src/utils/deriveRelatedLists.ts:131 skips the FK entirely when relatedList is false and :138 marks the derived list isPrimary when it is 'primary'; objectui: packages/app-shell/src/views/RecordDetailView.tsx:1037 calls deriveRelatedLists and renders what it returns, consuming the derived structure rather than the key — measured objectui @11c1e71e", + "evidenceScope": "in-repo", + "evidence": "ui/packages/app-shell/src/utils/deriveRelatedLists.ts:295 skips the FK entirely when relatedList is false and :302 marks the derived list isPrimary when it is 'primary'; ui/packages/app-shell/src/views/RecordDetailView.tsx:1732 calls deriveRelatedLists and renders what it returns, consuming the derived structure rather than the key — measured objectui @11c1e71e", "note": "RE-CITED 2026-08-10 (#7133/#7142): the old split citation's RecordDetailView half went to prose — every `relatedList` occurrence in that file is now a comment (:1013, :1847, :1857, :1864). The working half is deriveRelatedLists, which is what branches on the value. RecordDetailView is kept, explicitly labelled as the consumer of the DERIVED list, so the next re-audit does not read its zero key-hits as rot a second time. Verdict unchanged: LIVE." }, "relatedListTitle": { "status": "live", "verifiedAt": "2026-08-10", - "evidenceScope": "cross-repo", - "evidence": "objectui: packages/app-shell/src/utils/deriveRelatedLists.ts:140-141 uses relatedListTitle as the related-list title when it is a non-empty string, falling back to a label derived from the child object; objectui: packages/app-shell/src/views/RecordDetailView.tsx:1037 renders the derived list — measured objectui @11c1e71e", + "evidenceScope": "in-repo", + "evidence": "ui/packages/app-shell/src/utils/deriveRelatedLists.ts:306-307 uses relatedListTitle as the related-list title when it is a non-empty string, falling back to a label derived from the child object; ui/packages/app-shell/src/views/RecordDetailView.tsx:1732 renders the derived list — measured objectui @11c1e71e", "note": "RE-CITED 2026-08-10 (#7133/#7142): same repair as `relatedList` — the RecordDetailView half of the old split citation is comments only (:1847 describes the rule it no longer implements), while deriveRelatedLists is the reader that applies the value. Verdict unchanged: LIVE." }, "relatedListColumns": { "status": "live", "verifiedAt": "2026-08-17", - "evidenceScope": "cross-repo", - "evidence": "objectui: packages/app-shell/src/utils/deriveRelatedLists.ts (columns override on the derived descriptor) → views/RecordDetailView.tsx → plugin-detail/RelatedList.tsx (normalizeColumn hydrates STRING entries: header from the child field label, type-aware cell via makeCell)", + "evidenceScope": "in-repo", + "evidence": "ui/packages/app-shell/src/utils/deriveRelatedLists.ts (columns override on the derived descriptor) → views/RecordDetailView.tsx → plugin-detail/RelatedList.tsx (normalizeColumn hydrates STRING entries: header from the child field label, type-aware cell via makeCell)", "note": "LIVE via objectui renderer. #9227 narrowed the elements to child FIELD-NAME STRINGS — the only authored form in-repo (three showcase objects) and the only form the renderer hydrates fully; the page-block sibling record:related_list.columns (ui/component.zod.ts) is the same strings-only shape. RE-CITED 2026-09-03 (#14166): the 2026-08-17 reading against objectui main had the data-table accessor resolving `accessorKey || name`; objectui#6963 retired the `name` alias, and at the `.objectui-sha` pin `67dadd602a3a` object entries span two vocabularies (RelatedList resolves identity via columnIdentity, canonical `field`; the data-table accessor resolves `accessorKey` only), so a spec-canonical {field} object renders BLANK cells — refused at parse instead. Verdict unchanged: LIVE." }, "relatedListFilter": { "status": "live", "verifiedAt": "2026-09-20", - "evidenceScope": "cross-repo", - "evidence": "objectui: packages/app-shell/src/utils/deriveRelatedLists.ts#deriveRelatedLists:276 puts the authored value on the derived descriptor as `filter`, gated by the same file's #isDeclaredFilter predicate — the declared-vs-absent discrimination its relatedListTitle / relatedListColumns siblings do, with no alias accepted and no shape coerced; objectui: packages/app-shell/src/views/RecordDetailView.tsx#synthParts:2079 destructures that descriptor key as `declaredFilter` (:2041) and writes it onto the synthesized `record:related_list` node, the same `filter` slot the component-level input already lowers, so RelatedList AND-composes it with { [referenceField]: parentId } and the tab strip's count probe composes the same pair — measured objectui @53ded82bf7, which is this repo's `.objectui-sha` pin, so both pointers were read AT THE PIN this repo builds against rather than on objectui main", + "evidenceScope": "in-repo", + "evidence": "ui/packages/app-shell/src/utils/deriveRelatedLists.ts#deriveRelatedLists:319 puts the authored value on the derived descriptor as `filter`, gated by the same file's #isDeclaredFilter predicate — the declared-vs-absent discrimination its relatedListTitle / relatedListColumns siblings do, with no alias accepted and no shape coerced; ui/packages/app-shell/src/views/RecordDetailView.tsx#synthParts:3160 destructures that descriptor key as `declaredFilter` (:3121) and writes it onto the synthesized `record:related_list` node, the same `filter` slot the component-level input already lowers, so RelatedList AND-composes it with { [referenceField]: parentId } and the tab strip's count probe composes the same pair — measured objectui @53ded82bf7, which is this repo's `.objectui-sha` pin, so both pointers were read AT THE PIN this repo builds against rather than on objectui main", "note": "[#8704] Contract-first spec half (maintainer ruling 2026-08-15, 「接受全部建议。」 item 3): the declarative default filter the auto-derived related list AND-composes with { [referenceField]: parentId }, badge-count parity included — both clauses normative in the key's describe()/JSDoc and pinned in field.test.ts. Reuses FilterConditionSchema, so the #8793 bare-preset schema door judges it at parse (pinned at this position) and the engine FILTER doors (#8296 formula refusal etc.) judge the composed query at run time. Seeded `planned` + authorWarn per enforce-or-mark, the app.navigation runAction (#4848) shape rather than the useGrouping one, and that seeding wrote its own flip condition: 「Flip to `live` (and drop authorWarn) with a deriveRelatedLists/RecordDetailView evidence pointer when objectui#4664 lands.」 **FLIPPED `planned` → `live` 2026-09-20 (#19187), and the condition was re-measured rather than relayed**: objectui#4664 landed as objectui d796c8dde (objectui PR #6946, 2026-08-31) and `git merge-base --is-ancestor d796c8dde 53ded82bf7` exits 0, so the consumer is inside the console this repo pins — the #10068 discipline (read both pointers at the pin, never at objectui main) one row over. What the flip drops is not bookkeeping: the authorHint it carried told an author 「the auto-derived related list does not apply this filter yet」 about a key the pinned console applies, and `packages/lint`'s ledger-driven advisory repeated that sentence at every compile, steering authors off a working key. The sentence is recorded rather than deleted because it was TRUE from #8704 until the pin absorbed d796c8dde — a `planned` row falsifies by its consumer landing, which is the one direction no citation check can see (the pointer never rots; there was no pointer). The form hint's face today (packages/spec/src/data/field.form.ts; moved out of spec source, #23434): objectui @b9814b334 packages/app-shell/src/views/metadata-admin/SchemaForm.tsx#resolveFieldFace lands the `filter-condition` hint on `{ kind: 'raw-json', hint }`, past both structural fallbacks, because objectui @b9814b334 packages/app-shell/src/views/metadata-admin/widgets.tsx#WIDGETS registers no `filter-condition`; re-read 2026-10-15, first cited sha-less." }, "trackHistory": { diff --git a/packages/spec/liveness/flow.json b/packages/spec/liveness/flow.json index dbcf793b3eb..fce69c02afc 100644 --- a/packages/spec/liveness/flow.json +++ b/packages/spec/liveness/flow.json @@ -25,9 +25,9 @@ "description": { "status": "live", "verifiedAt": "2026-09-29", - "evidenceScope": "cross-repo", - "evidence": "objectui @dd3f7e1be: packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx#defaultColumns (the Studio metadata list's default `description` column: `flow` registers no `listColumns`, so `DefaultMetadataList` takes these defaults and draws each row's `description` through `defaultCell`); objectui @dd3f7e1be: packages/app-shell/src/views/metadata-admin/QuickFind.tsx#MetadataQuickFind (the Studio metadata quick-find indexes every item's `description` off the same list read and draws it under the item's name)", - "producer": "objectui @dd3f7e1be: packages/app-shell/src/console/AppContent.tsx#AppContent (mounts `MetadataResourceListPage` on the Studio route `metadata/:type`, which the metadata directory tile and the quick-find both navigate to); objectui @dd3f7e1be: packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx#MetadataResourceListPage (renders a registered custom `ListPage` and otherwise `DefaultMetadataList`); objectui @dd3f7e1be: packages/app-shell/src/views/metadata-admin/anchors.ts#registerBuiltinAnchors (the only `flow` registration, which sets no `ListPage` and no `listColumns`); objectui @dd3f7e1be: packages/data-objectstack/src/metadata-client.ts#MetadataClient (`list('flow')` reads `GET /api/v1/meta/flow` and takes its `items`); framework: packages/rest/src/meta-item-read-gate.ts#createMetaListAnswer (the one list answer both transports serve: after the per-caller gate it runs no projection for `flow` except the translation step, so the authored `description` reaches the body)", + "evidenceScope": "in-repo", + "evidence": "ui/packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx#defaultColumns (the Studio metadata list's default `description` column: `flow` registers no `listColumns`, so `DefaultMetadataList` takes these defaults and draws each row's `description` through `defaultCell`); ui/packages/app-shell/src/views/metadata-admin/QuickFind.tsx#MetadataQuickFind (the Studio metadata quick-find indexes every item's `description` off the same list read and draws it under the item's name)", + "producer": "ui/packages/app-shell/src/console/AppContent.tsx#AppContent (mounts `MetadataResourceListPage` on the Studio route `metadata/:type`, which the metadata directory tile and the quick-find both navigate to); ui/packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx#MetadataResourceListPage (renders a registered custom `ListPage` and otherwise `DefaultMetadataList`); ui/packages/app-shell/src/views/metadata-admin/anchors.ts#registerBuiltinAnchors (the only `flow` registration, which sets no `ListPage` and no `listColumns`); ui/packages/data-objectstack/src/metadata-client.ts#MetadataClient (`list('flow')` reads `GET /api/v1/meta/flow` and takes its `items`); framework: packages/rest/src/meta-item-read-gate.ts#createMetaListAnswer (the one list answer both transports serve: after the per-caller gate it runs no projection for `flow` except the translation step, so the authored `description` reaches the body)", "note": "RE-GRADED dead → live 2026-09-29 (#20299) under the #7131 previews ruling (README, 'Designer previews count as consumers'): for a display key, being shown to a human is the whole of the claimed effect. The lane's call on #20299 (claim 5880838693) counts a Studio list column and the metadata quick-find as that showing. The superseded evidence, 'no reader either layer', was answered by the preview lookup below, which looked at previews only. READER, read at the `.objectui-sha` pin dd3f7e1be: `MetadataResourceListPage` falls through to `DefaultMetadataList` because no registration gives `flow` a `ListPage`, and that page takes `config.listColumns ?? defaultColumns(...)`, whose columns are the primary key, `label` and `description`. Each cited string counts the same at objectui main 5d689c3. PRODUCER, measured booted rather than read: a throwaway `@objectstack/verify` boot of the real examples/app-showcase composition, signed in as the dev admin, answered `GET /api/v1/meta/flow` 200 with a top-level `items` array of 30 flows, and all 30 carried their authored `label` and `description` with `_packageId: com.example.showcase`. `GET /api/v1/meta/package` listed that package with `scope: project`, which is what the list page's package scope admits. UNCHANGED by the re-grade: still docs-shaped annotation, deliberately KEPT (2026-07-30 sweep, ADR-0033) and not authorWarn'd. `live` here does not mean the automation engine acquired a use for it. HISTORY, kept as history — the note this row carried while `dead`: KEPT deliberately (2026-07-30 sweep): docs-shaped, not capability-shaped — an annotation field documents intent for the next reader (per ADR-0033, often a model) even without a runtime consumer. Exempt from enforce-or-remove; do not re-litigate. PREVIEW LOOKUP RECORDED 2026-08-10 (#7427): 'no reader either layer' is a cross-repo absence claim, so the previews ruling (#7131; README, 'Designer previews count as consumers') was applied to it mechanically. At objectui @e9ab52f9 FlowPreview IS registered (previews/index.ts:58) and reachable (ResourceEditPage.tsx:949), and it does NOT read the flow description — the only `description` token in the file is a TypeScript interface member at FlowPreview.tsx:64, not a draft read; the preview keys off `d.name`, the node graph and the edges. The claim survives the look; the verdict is unchanged and this note re-litigates nothing." }, "version": { @@ -112,7 +112,7 @@ }, "boundaryConfig": { "status": "live", - "evidence": "objectui: packages/app-shell/src/views/metadata-admin/inspectors/flow-node-config.ts:480", + "evidence": "ui/packages/app-shell/src/views/metadata-admin/inspectors/flow-node-config.ts (:480 in objectui, commit not pinned)", "note": "LIVE via objectui metadata-admin authoring UI — the 2026-06 audit missed objectui; verified by reading the file." }, "outputSchema": { diff --git a/packages/spec/liveness/hook.json b/packages/spec/liveness/hook.json index e1367e014ca..a1b998cb54b 100644 --- a/packages/spec/liveness/hook.json +++ b/packages/spec/liveness/hook.json @@ -87,17 +87,17 @@ "label": { "status": "live", "verifiedAt": "2026-09-29", - "evidenceScope": "cross-repo", - "evidence": "objectui @dd3f7e1be: packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx#defaultColumns (the Studio metadata list's default `label` column: `hook` registers no `listColumns`, so `DefaultMetadataList` takes these defaults and draws each row's `label` through `defaultCell`); objectui @dd3f7e1be: packages/app-shell/src/views/metadata-admin/QuickFind.tsx#MetadataQuickFind (the Studio metadata quick-find indexes every item's `label` off the same list read and draws it beside the item's name)", - "producer": "objectui @dd3f7e1be: packages/app-shell/src/console/AppContent.tsx#AppContent (mounts `MetadataResourceListPage` on the Studio route `metadata/:type`, which the metadata directory tile and the quick-find both navigate to); objectui @dd3f7e1be: packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx#MetadataResourceListPage (renders a registered custom `ListPage` and otherwise `DefaultMetadataList`); objectui @dd3f7e1be: packages/app-shell/src/views/metadata-admin/anchors.ts#registerBuiltinAnchors (the only `hook` registration, which sets no `ListPage` and no `listColumns`); objectui @dd3f7e1be: packages/data-objectstack/src/metadata-client.ts#MetadataClient (`list('hook')` reads `GET /api/v1/meta/hook` and takes its `items`); framework: packages/rest/src/meta-item-read-gate.ts#createMetaListAnswer (the one list answer both transports serve: after the per-caller gate it runs no projection for `hook` except the translation step, so the authored `label` reaches the body)", + "evidenceScope": "in-repo", + "evidence": "ui/packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx#defaultColumns (the Studio metadata list's default `label` column: `hook` registers no `listColumns`, so `DefaultMetadataList` takes these defaults and draws each row's `label` through `defaultCell`); ui/packages/app-shell/src/views/metadata-admin/QuickFind.tsx#MetadataQuickFind (the Studio metadata quick-find indexes every item's `label` off the same list read and draws it beside the item's name)", + "producer": "ui/packages/app-shell/src/console/AppContent.tsx#AppContent (mounts `MetadataResourceListPage` on the Studio route `metadata/:type`, which the metadata directory tile and the quick-find both navigate to); ui/packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx#MetadataResourceListPage (renders a registered custom `ListPage` and otherwise `DefaultMetadataList`); ui/packages/app-shell/src/views/metadata-admin/anchors.ts#registerBuiltinAnchors (the only `hook` registration, which sets no `ListPage` and no `listColumns`); ui/packages/data-objectstack/src/metadata-client.ts#MetadataClient (`list('hook')` reads `GET /api/v1/meta/hook` and takes its `items`); framework: packages/rest/src/meta-item-read-gate.ts#createMetaListAnswer (the one list answer both transports serve: after the per-caller gate it runs no projection for `hook` except the translation step, so the authored `label` reaches the body)", "note": "RE-GRADED dead → live 2026-09-29 (#20299) under the #7131 previews ruling (README, 'Designer previews count as consumers'): for a display key, being shown to a human is the whole of the claimed effect. The lane's call on #20299 (claim 5880838693) counts a Studio list column and the metadata quick-find as that showing. The superseded evidence, 'pure docs, zero runtime readers (runtime uses only name)', was true of the hook RUNTIME and false as a whole-system claim, and the 2026-08-10 preview lookup (file `_note`) looked at previews only: `hook` still has no registered preview. READER, read at the `.objectui-sha` pin dd3f7e1be: `MetadataResourceListPage` falls through to `DefaultMetadataList` because no registration gives `hook` a `ListPage`, and that page takes `config.listColumns ?? defaultColumns(...)`, whose columns are the primary key, `label` and `description`. Each cited string counts the same at objectui main 5d689c3. PRODUCER, measured booted rather than read: a throwaway `@objectstack/verify` boot of the real examples/app-showcase composition, signed in as the dev admin, answered `GET /api/v1/meta/hook` 200 with a top-level `items` array of 4 hooks, and all 4 carried their authored `label` and `description` with `_packageId: com.example.showcase`. `GET /api/v1/meta/package` listed that package with `scope: project`, which is what the list page's package scope admits. UNCHANGED by the re-grade: still docs-shaped annotation, deliberately KEPT (2026-07-30 sweep, ADR-0033) and not authorWarn'd. `live` here does not mean the hook runtime acquired a use for it. HISTORY, kept as history — the note this row carried while `dead`: KEPT deliberately (2026-07-30 sweep): docs-shaped, not capability-shaped — an annotation field documents intent for the next reader even without a runtime consumer. Exempt from enforce-or-remove; do not re-litigate." }, "description": { "status": "live", "verifiedAt": "2026-09-29", - "evidenceScope": "cross-repo", - "evidence": "objectui @dd3f7e1be: packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx#defaultColumns (the Studio metadata list's default `description` column: `hook` registers no `listColumns`, so `DefaultMetadataList` takes these defaults and draws each row's `description` through `defaultCell`); objectui @dd3f7e1be: packages/app-shell/src/views/metadata-admin/QuickFind.tsx#MetadataQuickFind (the Studio metadata quick-find indexes every item's `description` off the same list read and draws it under the item's name)", - "producer": "objectui @dd3f7e1be: packages/app-shell/src/console/AppContent.tsx#AppContent (mounts `MetadataResourceListPage` on the Studio route `metadata/:type`); objectui @dd3f7e1be: packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx#MetadataResourceListPage (renders a registered custom `ListPage` and otherwise `DefaultMetadataList`); objectui @dd3f7e1be: packages/app-shell/src/views/metadata-admin/anchors.ts#registerBuiltinAnchors (the only `hook` registration, which sets no `ListPage` and no `listColumns`); objectui @dd3f7e1be: packages/data-objectstack/src/metadata-client.ts#MetadataClient (`list('hook')` reads `GET /api/v1/meta/hook` and takes its `items`); framework: packages/rest/src/meta-item-read-gate.ts#createMetaListAnswer (the same list answer as `label`, with no projection that drops `description`)", + "evidenceScope": "in-repo", + "evidence": "ui/packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx#defaultColumns (the Studio metadata list's default `description` column: `hook` registers no `listColumns`, so `DefaultMetadataList` takes these defaults and draws each row's `description` through `defaultCell`); ui/packages/app-shell/src/views/metadata-admin/QuickFind.tsx#MetadataQuickFind (the Studio metadata quick-find indexes every item's `description` off the same list read and draws it under the item's name)", + "producer": "ui/packages/app-shell/src/console/AppContent.tsx#AppContent (mounts `MetadataResourceListPage` on the Studio route `metadata/:type`); ui/packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx#MetadataResourceListPage (renders a registered custom `ListPage` and otherwise `DefaultMetadataList`); ui/packages/app-shell/src/views/metadata-admin/anchors.ts#registerBuiltinAnchors (the only `hook` registration, which sets no `ListPage` and no `listColumns`); ui/packages/data-objectstack/src/metadata-client.ts#MetadataClient (`list('hook')` reads `GET /api/v1/meta/hook` and takes its `items`); framework: packages/rest/src/meta-item-read-gate.ts#createMetaListAnswer (the same list answer as `label`, with no projection that drops `description`)", "note": "RE-GRADED dead → live 2026-09-29 (#20299) with `label`, same ruling, same lane call, same pin and the same booted measurement: all 4 showcase hooks served by `GET /api/v1/meta/hook` carried an authored `description`, and the Studio list's default `description` column draws it. The superseded evidence, 'pure docs, zero runtime readers; redundant with label', was true of the hook runtime. The two keys are not redundant on screen: the list draws them as two columns and the quick-find draws `label` beside the name and `description` under it. UNCHANGED by the re-grade: still docs-shaped annotation, deliberately KEPT (2026-07-30 sweep, ADR-0033) and not authorWarn'd. HISTORY, kept as history — the note this row carried while `dead`: KEPT deliberately (2026-07-30 sweep): docs-shaped, not capability-shaped — an annotation field documents intent for the next reader even without a runtime consumer. Exempt from enforce-or-remove; do not re-litigate." } } diff --git a/packages/spec/liveness/job.json b/packages/spec/liveness/job.json index 37d5356a7de..bd2f06cd55f 100644 --- a/packages/spec/liveness/job.json +++ b/packages/spec/liveness/job.json @@ -11,17 +11,17 @@ "label": { "status": "live", "verifiedAt": "2026-08-10", - "evidenceScope": "cross-repo", - "evidence": "objectui @aeb8424b: packages/app-shell/src/views/metadata-admin/previews/JobPreview.tsx:257 takes `label` off the job draft with the job's own `name` as fallback, and packages/app-shell/src/views/metadata-admin/previews/JobPreview.tsx:313 renders it as the preview card's title", - "producer": "objectui @aeb8424b: packages/app-shell/src/views/metadata-admin/previews/index.ts:62 registers JobPreview against the `job` metadata type, and packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx:949 resolves that registration and hands the component the draft being edited — the read point is reachable, not merely present", + "evidenceScope": "in-repo", + "evidence": "ui/packages/app-shell/src/views/metadata-admin/previews/JobPreview.tsx:262 takes `label` off the job draft with the job's own `name` as fallback, and ui/packages/app-shell/src/views/metadata-admin/previews/JobPreview.tsx:318 renders it as the preview card's title", + "producer": "ui/packages/app-shell/src/views/metadata-admin/previews/index.ts:76 registers JobPreview against the `job` metadata type, and ui/packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx (:949 at objectui @aeb8424b) resolves that registration and hands the component the draft being edited — the read point is reachable, not merely present", "note": "display metadata, and it IS displayed: the metadata-admin job preview shows it as the card title, falling back to the job `name` when unset. RE-GRADED dead → live 2026-08-10 (#7131) under the maintainer ruling that a designer preview rendering a key to a human is a runtime consumer — see README, 'Designer previews count as consumers'. The superseded wording, 'no runtime consumer (sys_job stores name/schedule only)', was true about the SCHEDULER and false as a whole-system claim: for a docs-shaped key the display IS the runtime effect, so sys_job was never the surface that could falsify it. UNCHANGED by the re-grade: still docs-shaped annotation, deliberately KEPT and not authorWarn'd — the hook.label/description precedent, exempt from enforce-or-remove (ADR-0033). `live` here does not mean the scheduler acquired a use for it." }, "description": { "status": "live", "verifiedAt": "2026-08-10", - "evidenceScope": "cross-repo", - "evidence": "objectui @aeb8424b: packages/app-shell/src/views/metadata-admin/previews/JobPreview.tsx:258 takes `description` off the job draft, and packages/app-shell/src/views/metadata-admin/previews/JobPreview.tsx:316 renders it beneath the title whenever it is non-empty", - "producer": "objectui @aeb8424b: packages/app-shell/src/views/metadata-admin/previews/index.ts:62 registers JobPreview against the `job` metadata type, and packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx:949 resolves that registration and hands the component the draft being edited", + "evidenceScope": "in-repo", + "evidence": "ui/packages/app-shell/src/views/metadata-admin/previews/JobPreview.tsx:263 takes `description` off the job draft, and ui/packages/app-shell/src/views/metadata-admin/previews/JobPreview.tsx:321 renders it beneath the title whenever it is non-empty", + "producer": "ui/packages/app-shell/src/views/metadata-admin/previews/index.ts:76 registers JobPreview against the `job` metadata type, and ui/packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx (:949 at objectui @aeb8424b) resolves that registration and hands the component the draft being edited", "note": "same as `label`: docs-shaped, deliberately kept, no warning — and displayed under the title in the same preview card. Re-graded dead → live with `label` on 2026-08-10 (#7131), same ruling, same ADR-0033 KEPT status." }, "schedule": { diff --git a/packages/spec/liveness/manifest.json b/packages/spec/liveness/manifest.json index f4461d2e39d..82299a528ff 100644 --- a/packages/spec/liveness/manifest.json +++ b/packages/spec/liveness/manifest.json @@ -67,38 +67,38 @@ }, "permissions": { "status": "live", - "evidence": "objectui: packages/app-shell/src/console/marketplace/PluginDisclosure.tsx#PluginDisclosure @a58626c88dc8 (the install-consent disclosure reads the marketplace version's `permissions` and renders its four lists — `services`, `hooks`, `network`, `fs` — under \"This package requests:\", one group per list)", + "evidence": "ui/packages/app-shell/src/console/marketplace/PluginDisclosure.tsx#PluginDisclosure @a58626c88dc8 (the install-consent disclosure reads the marketplace version's `permissions` and renders its four lists — `services`, `hooks`, `network`, `fs` — under \"This package requests:\", one group per list)", "producer": "packages/cli/src/commands/plugin/publish.ts#PluginPublish (`os plugin publish` uploads the compiled manifest whole as `plugin_manifest`; the cloud control plane's projection of it onto the version row the disclosure reads was NOT measured from this seat — see the note)", "verifiedAt": "2026-10-07", - "evidenceScope": "cross-repo", + "evidenceScope": "in-repo", "note": "RE-VERDICTED 2026-10-07 by the retirement of the legacy flat-list arm (ADR-0049 enforce-or-remove, ruled option A; ADR-0087 conversion `manifest-permissions-string-list-removed`): `ManifestPermissionsSchema` is now the structured `PluginPermissionsSchema` block itself (services / hooks / network / fs, ADR-0025 §3.2), and a list is refused at parse with its prescription. The verdict stays `live`, on CORRECTED evidence. This row used to rest on `collectDeclaredSuggestions` (plugin-security) reading the legacy arm — a misattribution: that reader wants the ASSEMBLED stage's `PermissionSet[]` collection, which shares this key (the two-readings collision `AssembledPackageBodySchema`'s stage table records), and it, like `AppPlugin`'s security registrar, only REPORTS an authoring-stage value as skipped. LOCAL HALF, measured at e67ba80049: every `manifest.permissions` / `manifest?.permissions` / `manifest[...]` spelling and every `ManifestPermissions*` / `PluginPermissions*` importer — zero runtime consumers of the manifest-stage value. The permission enforcer is not one: it registers `grantedPermissions` from the environment artifact (the CONSENTED set, whose values are this same schema), never the manifest's request. Control on the same instrument: it finds those two drop reports and the assembled-stage read in the CLI's permission-set-name-collisions test, so the zero is about this key's readers, not the pattern. FOREIGN HALF: objectui at the `.objectui-sha` pin a58626c88dc8 reads the structured block in the marketplace install dialog (`PluginDisclosure`) and shows the installer each requested service, hook, host and path — 0 reads of `manifest.permissions` there, control 91 `manifest.(id|name|version)` reads at the same pin. A flat list carries none of the four lists, so the retired arm disclosed nothing there either. PRODUCER: `os plugin publish` uploads the manifest; the cloud control plane's projection onto the version row is the unmeasured hop — `add_repo` on cloud was denied from this seat on 2026-10-07, so a cloud-capable seat re-reads that projection and pins its commit. NOT ENFORCED AT LOAD: the block records a consent request and REFUSES NOTHING (the `PluginPermissionsSchema` docblock), so `live` here means authoring it changes what an installer is shown, not what the plugin may touch.", "children": { "services": { "status": "live", - "evidence": "objectui: packages/app-shell/src/console/marketplace/PluginDisclosure.tsx#PluginDisclosure @a58626c88dc8 (`perms.services` — counted into the disclosure's has-any test and rendered as its \"Platform services\" group)", + "evidence": "ui/packages/app-shell/src/console/marketplace/PluginDisclosure.tsx#PluginDisclosure @a58626c88dc8 (`perms.services` — counted into the disclosure's has-any test and rendered as its \"Platform services\" group)", "verifiedAt": "2026-10-07", - "evidenceScope": "cross-repo", + "evidenceScope": "in-repo", "note": "Drilled 2026-10-07 when the retirement of the flat-list arm left this block the whole of `permissions`, so the walk began to see it as a container. Shown to the installer, NOT enforced at load — the container's note carries the producer hop and the not-enforced half." }, "hooks": { "status": "live", - "evidence": "objectui: packages/app-shell/src/console/marketplace/PluginDisclosure.tsx#PluginDisclosure @a58626c88dc8 (`perms.hooks` — counted into the disclosure's has-any test and rendered as its \"Lifecycle hooks\" group)", + "evidence": "ui/packages/app-shell/src/console/marketplace/PluginDisclosure.tsx#PluginDisclosure @a58626c88dc8 (`perms.hooks` — counted into the disclosure's has-any test and rendered as its \"Lifecycle hooks\" group)", "verifiedAt": "2026-10-07", - "evidenceScope": "cross-repo", + "evidenceScope": "in-repo", "note": "Drilled 2026-10-07 when the retirement of the flat-list arm left this block the whole of `permissions`, so the walk began to see it as a container. Shown to the installer, NOT enforced at load — the container's note carries the producer hop and the not-enforced half." }, "network": { "status": "live", - "evidence": "objectui: packages/app-shell/src/console/marketplace/PluginDisclosure.tsx#PluginDisclosure @a58626c88dc8 (`perms.network` — counted into the disclosure's has-any test and rendered as its \"Network access\" group)", + "evidence": "ui/packages/app-shell/src/console/marketplace/PluginDisclosure.tsx#PluginDisclosure @a58626c88dc8 (`perms.network` — counted into the disclosure's has-any test and rendered as its \"Network access\" group)", "verifiedAt": "2026-10-07", - "evidenceScope": "cross-repo", + "evidenceScope": "in-repo", "note": "Drilled 2026-10-07 when the retirement of the flat-list arm left this block the whole of `permissions`, so the walk began to see it as a container. Shown to the installer, NOT enforced at load — the container's note carries the producer hop and the not-enforced half." }, "fs": { "status": "live", - "evidence": "objectui: packages/app-shell/src/console/marketplace/PluginDisclosure.tsx#PluginDisclosure @a58626c88dc8 (`perms.fs` — counted into the disclosure's has-any test and rendered as its \"Filesystem access\" group)", + "evidence": "ui/packages/app-shell/src/console/marketplace/PluginDisclosure.tsx#PluginDisclosure @a58626c88dc8 (`perms.fs` — counted into the disclosure's has-any test and rendered as its \"Filesystem access\" group)", "verifiedAt": "2026-10-07", - "evidenceScope": "cross-repo", + "evidenceScope": "in-repo", "note": "Drilled 2026-10-07 when the retirement of the flat-list arm left this block the whole of `permissions`, so the walk began to see it as a container. Shown to the installer, NOT enforced at load — the container's note carries the producer hop and the not-enforced half." } } diff --git a/packages/spec/liveness/mapping.json b/packages/spec/liveness/mapping.json index ee152cfef4a..bd4c342d96b 100644 --- a/packages/spec/liveness/mapping.json +++ b/packages/spec/liveness/mapping.json @@ -11,7 +11,7 @@ "label": { "status": "live", "verifiedAt": "2026-08-01", - "evidence": "objectui @940ba24: packages/plugin-grid/src/ImportWizard.tsx:979", + "evidence": "ui/packages/plugin-grid/src/ImportWizard.tsx (:979 at objectui @940ba24)", "note": "saved-mapping picker option text (`label || name`) and the applied-mapping hint. Display metadata with a real selection surface — the datasource.label treatment, not authorWarn'd." }, "sourceFormat": { diff --git a/packages/spec/liveness/object.json b/packages/spec/liveness/object.json index 7ae22bae634..421c2dcc32a 100644 --- a/packages/spec/liveness/object.json +++ b/packages/spec/liveness/object.json @@ -34,8 +34,8 @@ "imageField": { "status": "live", "verifiedAt": "2026-10-05", - "evidenceScope": "cross-repo", - "evidence": "packages/spec/src/data/object.zod.ts#refuseNonPictureImageField (the AUTHORING judgement: a name the object does not declare, or a field of any type but `image` / `avatar`, is refused at parse, so at the defineStack, `os validate` and metadata-save doors alike); objectui @9dfaca654311: packages/components/src/renderers/layout/containers.tsx#PageHeaderRenderer (the record chrome reads `objectSchema.imageField` off the record context at :2392 and hands the picture to the record chip's `icon` slot at :2449); objectui @9dfaca654311: packages/components/src/renderers/layout/containers.tsx#recordPictureUrl (:1458, resolves the served row's value of that field to the URL drawn)", + "evidenceScope": "in-repo", + "evidence": "packages/spec/src/data/object.zod.ts#refuseNonPictureImageField (the AUTHORING judgement: a name the object does not declare, or a field of any type but `image` / `avatar`, is refused at parse, so at the defineStack, `os validate` and metadata-save doors alike); ui/packages/components/src/renderers/layout/containers.tsx#PageHeaderRenderer (the record chrome reads `objectSchema.imageField` off the record context at :2786 and hands the picture to the record chip's `icon` slot at :2844); ui/packages/components/src/renderers/layout/containers.tsx#recordPictureUrl (:1598, resolves the served row's value of that field to the URL drawn)", "note": "The record's picture, ruling A on objectstack-ai/hotcrm#1199: one object-level declaration every record detail page reads, not a `page:header` prop. Two halves, both live. The spec judges the pointer at authoring time (`refuseNonPictureImageField`): only a declared `image` / `avatar` field of the same object is accepted. objectui's record chrome draws it at the `.objectui-sha` pin 9dfaca654311, which carries objectui PR 11619 (merge c096f03279): `PageHeaderRenderer` reads the object's `imageField`, `recordPictureUrl` resolves the row's value (an expanded `{ url }`, a bare `sys_file` id served from the storage download path, a legacy URL string, or the first resolvable entry of a `multiple` list), and the picture goes in the record chip's `icon` slot beside the title, an `avatar` drawn round and cropped, an `image` drawn whole. An empty or unresolvable value draws nothing, never initials or a placeholder, and a field the reader may not see arrives masked, so nothing is drawn for it either. The server masks the pointer itself for such a reader (packages/metadata-core/src/object-schema-fls-references.ts). Declared in the reference app by examples/app-showcase/src/data/objects/field-zoo.object.ts (`imageField: 'f_image'`)." }, "titleFormat": { diff --git a/packages/spec/liveness/page.json b/packages/spec/liveness/page.json index 1e080d3f88c..226f5c48b12 100644 --- a/packages/spec/liveness/page.json +++ b/packages/spec/liveness/page.json @@ -111,15 +111,15 @@ "template": { "status": "live", "verifiedAt": "2026-10-15", - "evidenceScope": "cross-repo", - "evidence": "objectui @0656b0e3c0 packages/components/src/renderers/layout/page.tsx#resolveTemplate resolves schema.template through #TEMPLATE_REGISTRY (default, full-width, header-sidebar-main, three-column, dashboard; default and full-width are the same #FullWidthTemplate) and the page renderer selects that layout ahead of the page-type layout — re-read 2026-10-15", + "evidenceScope": "in-repo", + "evidence": "ui/packages/components/src/renderers/layout/page.tsx#resolveTemplate resolves schema.template through #TEMPLATE_REGISTRY (default, full-width, header-sidebar-main, three-column, dashboard; default and full-width are the same #FullWidthTemplate) and the page renderer selects that layout ahead of the page-type layout — re-read 2026-10-15", "note": "layout template selects the region arrangement. CLOSED 2026-10-15 (#23610, ruling 6190536588 Q2 A): PageTemplateSchema is the registered set less full-width (retired by name, a second name for default), and each template declares the regions it places in PAGE_TEMPLATE_SLOTS (page.zod.ts), which checkPageRegionPlacement enforces at the region name. RE-CITED 2026-08-10 (#7133/#7142): the old citation was split across 'page.tsx + containers.tsx' and the containers.tsx half is dead for this key — its only `template` occurrences (:240-248) are a `{field.path}` string-interpolation helper, an unrelated sense of the word. Verdict unchanged: LIVE. LOAD PATH (P0 after #23610): a stored or installed page still spelling full-width replays the retired D2 entry page-template-full-width-to-default on read (both names drew FullWidthTemplate); an unregistered name has no lossless rewrite and stays with the parse." }, "regions": { "status": "live", "verifiedAt": "2026-10-15", - "evidenceScope": "cross-repo", - "evidence": "objectui @0656b0e3c0 packages/components/src/renderers/layout/page.tsx#RegionLayout resolves the named slots (header/sidebar/main/aside/footer, by #findRegion, case-insensitive) and appends the remainder below main (#getRemainingRegions); #HeaderSidebarMainTemplate places header/sidebar/main, #ThreeColumnTemplate the five, #DashboardTemplate header/footer and grids the rest — re-read 2026-10-15. The spec declares the same slots per template (PAGE_TEMPLATE_SLOTS) and refuses a region its template does not place (checkPageRegionPlacement, #23610)", + "evidenceScope": "in-repo", + "evidence": "ui/packages/components/src/renderers/layout/page.tsx#RegionLayout resolves the named slots (header/sidebar/main/aside/footer, by #findRegion, case-insensitive) and appends the remainder below main (#getRemainingRegions); #HeaderSidebarMainTemplate places header/sidebar/main, #ThreeColumnTemplate the five, #DashboardTemplate header/footer and grids the rest — re-read 2026-10-15. The spec declares the same slots per template (PAGE_TEMPLATE_SLOTS) and refuses a region its template does not place (checkPageRegionPlacement, #23610)", "note": "region → component tree rendering (header/main/sidebar/footer). RE-CITED 2026-08-10 (#7133/#7142): same split-citation pruning as `template` — at objectui @11c1e71e the containers.tsx half names `regions` only in a comment (:798, 'page:section — thin wrapper used inside regions'). Verdict unchanged: LIVE. Component-level liveness stays unclassified (undrilled container, #4956); one component key carries a verdict OUTSIDE this file: `components[].responsive` is a retiredKey tombstone (#11027, ADR-0049 D2 — dead on a two-repo measurement; see the file _note). COMPONENT-MEMBER READ POINTS (moved out of packages/spec/src, #23434; ruling 6165388570 letter C, Q1 A as the seat read it): an EVIDENCE LIST only — no verdict is asserted, so component-level liveness stays unclassified as above. Each entry names the ComponentPropsMap row and member it reads, then its read points in the spelling `objectui @SHA FILE#SYMBOL` (the SHA is the objectui tree the point was last read in; a pin hop moves nothing here, and a renamed or deleted symbol is the event that re-measures it). Inside one entry, a letter defined at its head abbreviates a repeated file: with `P = objectui @SHA FILE`, `P#a P#b` reads `objectui @SHA FILE#a`, `objectui @SHA FILE#b`. Entries, in source order: [1] page:header (H = objectui @b9814b334 packages/components/src/renderers/layout/containers.tsx#PageHeaderRenderer): title (`schema?.title ?? schema?.properties?.title`), the refused `icon` (resolved only per header action) and `breadcrumb` (an empty slot), `actions` (resolvedHeaderActions then headerActionPlan), recordChrome (`=== false` on either carrier, branching the header), the refused `showStar` (the record chip draws its star only where the host wires `onToggleFavorite`, and no reference host does; retired #23650) and showCopyId (the record chip), maxVisible and mobileMaxVisible (`?? 3` / `?? 1`), all in H; re-read 2026-10-15, first cited sha-less; [2] page:tabs (T = objectui @b9814b334 packages/components/src/renderers/layout/containers.tsx#PageTabsRenderer): tabStyle (`schema?.properties?.type || schema?.tabStyle || 'line'`), alwaysShowStrip (`schema?.alwaysShowStrip === true || schema?.properties?.alwaysShowStrip === true`), items[].value (`it.value`, falling back to `tab-${idx}`; the refused `key` is the designer's `page:tabs.items.itemFields` input in objectui @b9814b334 packages/app-shell/src/views/metadata-admin/previews/block-config.ts#BLOCK_CONFIG), items[].label (`translateLabel(pickLocalized(it.label, language), language, ttLabel)`: the inline locale map first, then the bundle, the order packages/spec/src/ui/i18n-label-resolver.ts mirrors, whose module docblock cited it by line, sha-less, until #23434; objectui @b9814b334 packages/components/src/renderers/layout/containers.tsx#PageAccordionRenderer composes an accordion item's label the same way), all in T; the retired props key `type` (the reason for the rename): left unhoisted by objectui @b9814b334 packages/react/src/SchemaRenderer.tsx#HOIST_PROTECTED_KEYS, and skipped as a base prop by objectui @b9814b334 packages/sdui-parser/src/validate.ts#BASE_PROPS; re-read 2026-10-15, first cited sha-less; [3] page:tabs items[].icon (C = objectui @b9814b334 packages/components/src/renderers/layout/containers.tsx): C#triggerClass (`LazyIcon` inside the TabsTrigger) in C#PageTabsRenderer; published by its registration's `items` input; vocabulary objectui @b9814b334 packages/components/src/lib/lazy-icon.tsx#getLazyIcon; [4] page:tabs items[].visibleWhen (the tab-item evaluator: `data` is the row, bare fields spread flat, ambient roots after it): objectui @1fb1ced36243 packages/components/src/renderers/layout/containers.tsx#isItemVisible (its ExpressionEvaluator) in PageTabsRenderer; [5] record:details / record:highlights / record:related_list / record:quick_actions .requiredPermissions (RECORD_BLOCK_REQUIRED_PERMISSIONS_DESCRIPTION; R = objectui @b9814b334 packages/plugin-detail/src/renderers/, P = objectui @b9814b334 packages/permissions/src/): the gate `perms.hasCapabilities(required)` and the notice in R record-details.tsx#RecordDetailsBody, R record-highlights.tsx#highlightsAllowed (RecordHighlightsBody), R record-related-list.tsx#RecordRelatedListBody, R record-quick-actions.tsx#RecordQuickActionsBody; earlier withholding: no record R record-details.tsx#RecordDetailsBody, no object and the related read gate R record-related-list.tsx#RecordRelatedListBody; a gated highlights registers no fields R record-highlights.tsx#RecordHighlightsBody (useRegisterHighlightFields); ALL OF THEM and fail-open on unreported P MePermissionsProvider.tsx#hasCapabilities; fail-open P PermissionProvider.tsx#ALL_CAPABILITIES and P usePermissions.ts#NO_PROVIDER_PERMISSIONS; the binding wrapper (objectui#12294) objectui @b9814b334 packages/plugin-detail/src/renderers/recordBinding.tsx#recordBindingBlock; [6] record:details sections[].headerColor (closed enum, #12126): the six class literals of objectui @b9814b334 packages/plugin-detail/src/headerColor.ts#headerColorClass (objectui#6294, merged 2026-08-25); re-read 2026-10-15, first cited sha-less; [7] record:details sections[] hideEmpty, collapsible, showBorder and the #11661 trio (renderer defaults measured; the trio at objectui @190fbd01): objectui @190fbd01 packages/plugin-detail/src/renderers/record-details.tsx#RecordDetailsRenderer and objectui @190fbd01 packages/plugin-detail/src/DetailSection.tsx#DetailSection (`hideEmpty ?? true`; the undeclared `s.title ?? s.label` limb); [8] record:related_list.parentDataSource (the name): the sibling record blocks' binding read objectui @f0532d8a packages/plugin-detail/src/renderers/recordBinding.tsx#recordBindingBlock (`isElementDataSourceConfig(schema.dataSource)`), measured on objectui main; [9] record:details sections[].count (read since objectui c08c9332a, objectui#12192): objectui @b9814b334 packages/plugin-detail/src/renderers/record-details.tsx#countField (inside filteredSections; fills every `{{ count }}` hole); [10] record:related_list.parentDataSource (the read, since objectui d81e46043, objectui#12407; R = objectui @4016370af3c9 packages/plugin-detail/src/renderers/): R record-related-list.tsx#RecordRelatedListRenderer (`schema.parentDataSource`, handed to the parent read) and its no-row answer R record-related-list.tsx#NoParentRecord; R recordBinding.tsx#ParentRecordBinding (unbound: the children as they are) and R recordBinding.tsx#BoundRecordProvider (`$top: 1` under the binding's `$orderby`); the LIVE record is the file _note's; [11] record:activity guidance `items` / `loading` (host feed slot; R = objectui @89cad75d5570 packages/plugin-detail/src/renderers/): R record-activity.tsx#hostItems and R record-activity.tsx#loading in RecordActivityRenderer; record:chatter / record:discussion `feed` reads neither, R record-chatter.tsx#RecordChatterRenderer; [12] record:chatter.position (vocabulary bottom / right / left, #8762): objectui @665661ab0932 packages/plugin-detail/src/RecordChatterPanel.tsx#position (`right`/`left` dock vs `bottom` in-flow); the designer's CHATTER_INPUTS and the merge fallback in objectui @665661ab0932 packages/plugin-detail/src/renderers/record-chatter.tsx#RecordChatterRenderer; [13] record:reference_rail (RecordReferenceRailProps): objectui @b9814b334 packages/plugin-detail/src/renderers/record-reference-rail.tsx#RecordReferenceRailRenderer ( an entry `icon` is unread, `title` a raw React child); the synthesizer emitting `icon` objectui @b9814b334 packages/plugin-detail/src/synth/buildDefaultPageSchema.ts#buildDefaultPageSchema; re-read 2026-10-15, first cited sha-less; [14] record:alert, record:quick_actions, record:history: objectui @b9814b334 packages/plugin-detail/src/renderers/record-alert.tsx#RecordAlertRenderer, objectui @b9814b334 packages/plugin-detail/src/renderers/record-quick-actions.tsx#RecordQuickActionsRenderer, objectui @b9814b334 packages/plugin-detail/src/renderers/record-history.tsx#RecordHistoryRenderer and objectui @b9814b334 packages/plugin-detail/src/HistoryTimeline.tsx#HistoryTimeline (each renderer's ACTUAL reads, against its registration's declared inputs); re-read 2026-10-15, first cited sha-less; [15] record:line_items (RecordLineItemsProps; L = objectui @31971ff1e28f packages/plugin-form/src/LineItemsPanel.tsx): registered as `line_items` (namespace `record`) in objectui @31971ff1e28f packages/plugin-form/src/index.tsx with its mapping objectui @31971ff1e28f packages/plugin-form/src/index.tsx#RECORD_LINE_ITEMS_DATA_SOURCE; the fifteen `schema.KEY` reads in L#LineItemsPanel (childObject L#heldReloadRef; parentObject L#parentObject; parentId, recordId L#parentId; amountField, totalField L#parentPatch; filter L#scopeFilter; limit L#message; columns L#gridField (applyColumnPermissions)); the gate objectui @31971ff1e28f packages/react/src/element-data-source/ElementDataSourceGate.tsx#useElementDataSourceSchema; read set unchanged at objectui main d59f11c0d3dc; [16] record:line_items guidance (addLabel, sortField, onRowExpand not handed to the grid): objectui @9dfaca654311 packages/plugin-form/src/LineItemsPanel.tsx#gridField; [17] record:approvals (RecordApprovalsProps, EMPTY key set; A = objectui @47b1f0bb71 packages/app-shell/src/views/): registered as `approvals` (namespace `record`) in A record-approvals-renderer.tsx#RecordApprovalsRenderer; approvals A record-approvals-renderer.tsx#hostApprovals, threaded by A RecordDetailView.tsx#RecordDetailView and emitted by objectui @47b1f0bb71 packages/plugin-detail/src/synth/buildDefaultPageSchema.ts#buildDefaultPageSchema; currentUserId falls back in A RecordApprovalsPanel.tsx#isSubmitter; Studio page create seeds without options in A metadata-admin/anchors.ts#synth; [18] page:accordion items[].value (refused): objectui @b9814b334 packages/components/src/renderers/layout/containers.tsx#PageAccordionRenderer overwrites it with `panel-${idx}`; the designer input `page:accordion.items.itemFields` in objectui @b9814b334 packages/app-shell/src/views/metadata-admin/previews/block-config.ts#BLOCK_CONFIG (objectui#7973); re-read 2026-10-15, first cited sha-less; [19] page:accordion items[].icon (C = objectui @b9814b334 packages/components/src/renderers/layout/containers.tsx): C#commonChildren (`LazyIcon` in the AccordionTrigger) inside C#PageAccordionRenderer; published by its `accordion` registration's `items` input; vocabulary objectui @b9814b334 packages/components/src/lib/lazy-icon.tsx#getLazyIcon; [20] page:accordion.variant: objectui @b9814b334 packages/components/src/renderers/layout/containers.tsx#PageAccordionRenderer (`schema?.variant ?? schema?.properties?.variant ?? 'flush'`); re-read 2026-10-15, first cited sha-less; [21] element:text.variant retired `heading` / `subheading`: objectui @89cad75d55 packages/components/src/renderers/basic/elements.tsx#VARIANT_TAG; [22] element:button.icon (B = objectui @b9814b334 packages/components/src/renderers/form/button.tsx, R = objectui @b9814b334 packages/components/src/renderers/action/resolve-icon.ts): B#Icon (resolveIcon) and its render on either side of the label in B#ButtonRenderer; R#resolveIcon R#describeIconLookup R#toPascalCase R#iconNameMap R#recordIconName (the vocabulary: the generated objectui @b9814b334 packages/components/src/lib/lucide-record-icon-names.ts#LUCIDE_RECORD_ICON_NAME_TABLE); contrast objectui @b9814b334 packages/components/src/lib/lazy-icon.tsx#getLazyIcon; no `icon` in the registration's inputs inside B#ButtonRenderer (objectui#9910 added the `children` slot input); history: the read point moved files on the hop onto objectui 9602dc820, and the registration ranges were first wrong-since-written at objectui a472b0716; [23] action:* rows, the effective executor and the runner's reads (R = objectui @b9814b334 packages/core/src/actions/ActionRunner.ts, first read at objectui @1fb1ced36243 with every anchor at the same symbols; A = objectui @b9814b334 packages/components/src/renderers/action/): executor resolution R#execute (`action.type || action.actionType || action.name`); forwarded executor A action-button.tsx#forwarded, A action-icon.tsx#forwarded (`type: schema.actionType`), A action-group.tsx#paramsPayload, A action-menu.tsx#paramsPayload (`type: action.type`); the name leg documented in A action-button.tsx#forwarded; built-in executors and the `navigation` alias R#ActionRunner (`builtinExecutors`), url navigation executeUrl / executeNavigation / navigateTo in R#ActionRunner; no console host handler under `url` / `navigation`: objectui @b9814b334 packages/app-shell/src/console/ConsoleShell.tsx, packages/app-shell/src/hooks/useConsoleActionRuntime.tsx, packages/app-shell/src/console/AppContent.tsx, packages/app-shell/src/hooks/useObjectActions.ts; onSuccess R#handlePostExecution R#readOnSuccessNavigation; operation read before any executor R#execute (isUpdateOperationAction), forwarded A action-button.tsx#forwarded; the row `patch` is never posted and is read for the Undo snapshot only (executeUpdateOperation in R#ActionRunner; the route writes the named declaration's `patch`), so it is a retiredKey() tombstone since #24394, re-read at objectui 0707b4ae37 and main 4b7ce47; unchanged at objectui main 866763bd and 72594f29; [24] section 4b, the action:* quartet and element:* lists (shared reads; A = objectui @b9814b334 packages/components/src/renderers/action/): publication objectui @b9814b334 packages/core/src/registry/public-blocks.ts#PUBLIC_BLOCKS; `autoTrigger` \"NOT persisted metadata\" in the header of A auto-trigger.ts#useAutoTriggerOnce; the Button primitive vocabulary objectui @b9814b334 packages/components/src/ui/button.tsx#buttonVariants; `objectName` forwarded by A action-button.tsx#forwarded and A action-icon.tsx#forwarded, and per member by A action-group.tsx#paramsPayload and A action-menu.tsx#paramsPayload; history: objectui#11168 slices 1-2, objectui#11212 (fail-closed `visible`), objectui#11182 (host `disabled` verdict), objectui#11344 (`outcomeMessages`), objectui#11638 (member `properties.params` read retired), objectui#11839 (disabled reason), objectui#11880 (repeater binding), objectui#12085 (repeater publishes only the binding); [25] action:button (ActionButtonPropsSchema; B = objectui @b9814b334 packages/components/src/renderers/action/action-button.tsx; the retired gate's quoted first line `type: schema.actionType,` holds at B#forwarded): name B#isVisible (diagnostic label) B#forwarded; label B#button (React child) B#isVisible B#forwarded; icon B#Icon (resolveIcon); actionType B#forwarded (`type`); variant, size B#variant and its `size` twin; visible B#isVisible B#ActionButtonRenderer (hasDeclaredVisibilityGate); disabled B#isDisabled B#button; params B#staticValues (withUndoBaseline) and objectui @b9814b334 packages/components/src/renderers/action/static-params.ts#readStaticParamValues; the forwarded set B#forwarded (outcomeMessages forwarded since objectui#11344, left unpublished in the registration's inputs inside B#ActionButtonRenderer); undo baseline B#withUndoBaseline; [26] action:icon (ActionIconPropsSchema; I = objectui @b9814b334 packages/components/src/renderers/action/action-icon.tsx; the retired gate's quoted first line `type: schema.actionType,` holds at I#forwarded): size not read I#size (fixed `'icon'`); the forward I#forwarded (no undoable / recordIdField; outcomeMessages since objectui#11344); label I#isVisible I#forwarded I#button (aria-label, initial letter, DisabledReasonTrigger heading) I#ActionIconRenderer (tooltip); visible I#isVisible I#ActionIconRenderer; disabled I#isDisabledPred I#button; variant I#variant; params I#staticValues (readStaticParamValues); [27] action:group / action:menu members (actionContainerMember; G = objectui @2e818d0b51ec packages/components/src/renderers/action/action-group.tsx, M = objectui @2e818d0b51ec packages/components/src/renderers/action/action-menu.tsx, P = objectui @2e818d0b51ec packages/components/src/renderers/action/static-params.ts): drawn G#InlineActionButton G#DropdownActionItem M#ActionMenuItem; visible G#useMemberVisible M#useMenuActionVisible; size (group only) G#btnSize; tags G#showSeparator; locations G#actions (actionRendersAt); forwarded G#staticValues M#staticValues (the forward block); object params forwarded on `api` and dropped, with a development-build warning only, for every other `type` P#readActionEntryParamValues (first cited at objectui @0abd4f9f8, same symbol); refused: properties.params P#readMemberStaticParamValues, className G#handleClick, autoTrigger M#ActionAutoTrigger (useAutoTriggerOnce), onClick G#handleExecute M#handleExecute; the array forward (the retired gate's quoted first line `const paramsPayload: ActionDef = Array.isArray(action.params)`) objectui @b9814b334 packages/components/src/renderers/action/action-group.tsx#paramsPayload and objectui @b9814b334 packages/components/src/renderers/action/action-menu.tsx#paramsPayload, other values objectui @b9814b334 packages/components/src/renderers/action/static-params.ts#readActionEntryParamValues; the fork measured at objectui @ab1879721595, files byte-identical at objectui main 2abec3a96; [28] action:group (ActionGroupPropsSchema; G = objectui @b9814b334 packages/components/src/renderers/action/action-group.tsx): actions G#declaredActions G#actions (actionRendersAt) G#useMemberVisible G#paramsPayload; display G#display; label, icon, variant, size G#TriggerIcon (the trigger; size raw to inline members) G#btnSize; visible G#isVisible; the host `disabled` verdict (objectui#11182) G#ActionGroupRenderer; group `name` unread (the comment above G#ActionGroupRenderer, objectui#11168); the registration's inputs inside G#ActionGroupRenderer; [29] action:menu (ActionMenuPropsSchema; M = objectui @b9814b334 packages/components/src/renderers/action/action-menu.tsx): actions M#ActionMenuRenderer M#ActionMenuItem M#ActionAutoTrigger M#paramsPayload; label, icon, variant, size, visible and the host `disabled` verdict (objectui#11182) M#ActionMenuRenderer; the registration's inputs inside M#ActionMenuRenderer (objectui#11168 slice 1); [30] element:definition-list (ElementDefinitionListPropsSchema; L = objectui @b9814b334 packages/components/src/renderers/basic/data-list.tsx; the retired gate's quoted first line `const props = readProps<{` holds at L#props): items, columns (`=== 2`), inline, term, description L#DefinitionListRenderer (an item's `description` shown through L#toText: a string or number as text, an object as JSON; first cited at objectui @89cad75d55); the registration's number enum and optional `items` (objectui#11168 slice 2) at its `definition-list` registration in L; the designer's number control objectui @b9814b334 packages/app-shell/src/views/metadata-admin/previews/block-config.ts#BLOCK_CONFIG; [31] element:repeater (ElementRepeaterPropsSchema; L = objectui @b9814b334 packages/components/src/renderers/basic/data-list.tsx; the quoted first line `const props = readProps<{` holds at L#props in L#RepeaterRenderer): object, filter, sort, limit, emptyText, divided, titleField, fields L#RepeaterRenderer; the binding L#dataBinding (useElementDataSource, objectui#11880, aaba8655a); resolved filter tokens L#filterScope; delivery objectui @b9814b334 packages/data-objectstack/src/index.ts#ObjectStackAdapter (translateFilterArray arm) and objectui @b9814b334 packages/data-objectstack/src/index.ts#serializeOrderBy; the registration wrapped by elementDataSourceBlock publishes no `object` input since objectui#12085 (ce991bd70); the registration's `object` required flag read at objectui @20c6d351a; [32] object-grid.aggregations[] (GridAggregationSchema): objectui @89cad75d55 packages/plugin-grid/src/ObjectGrid.tsx#groupHeaders (useGroupedData) and objectui @89cad75d55 packages/plugin-grid/src/ObjectGrid.tsx#serverGroupSource (useServerGroupHeaders); the grid's own function vocabulary objectui @89cad75d55 packages/plugin-grid/src/useGroupedData.ts#AggregationType (unchanged at objectui @b9814b334); [33] object-grid (ObjectGridPropsSchema, per key; E = objectui @eb7f586b packages/plugin-grid/src/ObjectGrid.tsx): columns, fields E#schemaFields; filter E#schemaFilterSource (toFilterNode); defaultFilters E#cols (legacy fallback); sort E#schemaSort; defaultSort E#cols; pagination, pageSize, showPagination E#lastFindParamsRef E#schemaPageSize E#paginationEnabled; searchableFields, showSearch E#trimmedSearch E#searchEnabled; rowHeight E#ObjectGrid (resolveRowHeightMode); grouping, aggregations E#grouping E#groupValueFormatter; rowColor E#getRowClassName; conditionalFormatting E#predicateFields E#rules; selection, selectable E#ObjectGrid; rowActions E#rowActionsList; batchActions, bulkActions E#declaredBulkActions; bulkActionDefs E#legacyBulkNames; navigation E#navigation; editable, singleClickEdit, resizable, resizableColumns, reorderableColumns E#dataTableSchema; frozenColumns E#effectiveFrozenColumns; showColumnTypeIcons E#fieldMeta; exportOptions, operations E#declared; label, title E#fileNameFor E#dataTableSchema; data, staticData E#getDataConfig; [#20694] description objectui @db11afd4967c packages/plugin-grid/src/ObjectGrid.tsx#resolvedDescription (drawn as `gridDescription`); emptyState objectui @db11afd4967c packages/plugin-grid/src/ObjectGrid.tsx#authoredEmptyState (DataEmptyState in renderedGridContent); keyboardNavigation 0 read points at objectui @db11afd4967c, 15 hit lines at objectui @ab1879721595 and at objectui @b9814b334 against 3 then 4 for the control `schema.editable` (the fourth objectui @b9814b334 packages/plugin-grid/src/ObjectGrid.tsx#carriesConcurrencyToken); [34] object-grid.description, object-grid.emptyState (B = objectui @b9814b334 packages/plugin-grid/src/ObjectGrid.tsx): description objectui @db11afd4967c packages/plugin-grid/src/ObjectGrid.tsx#resolvedDescription; emptyState title/message B#authoredEmptyTitle (resolveInlineI18nLabel, objectui#11227 carried by 6158e4c93; raw at objectui @89cad75d5570) drawn in B#renderedGridContent, link (objectui#12270, b434f5f47) B#renderedGridContent (EmptyStateLink); [35] object-grid.columns (ListViewSchema.shape.columns; A = objectui @ab1879721595 packages/plugin-grid/src/ObjectGrid.tsx): A#normalizeColumns (first-entry dispatch) A#cols A#fieldName (resolvesToDataColumn); the retired `colOverride?.options` read: A#groupValueFormatter A#rawOptions (objectui b0bf413, objectui#11544); the footer summary's off-contract column keys objectui @ab1879721595 packages/plugin-grid/src/useColumnSummary.ts#columnHints; [36] object-grid.fields (O = objectui @89cad75d55 packages/plugin-grid/src/ObjectGrid.tsx): O#schemaFields O#fieldsToShow O#generateColumns; the host hand-off projection O#getSelectFields; [37] object-grid.filter: objectui @53ded82b packages/plugin-grid/src/ObjectGrid.tsx#ObjectGrid (toFilterNode before `$filter`); [38] object-grid.sort (S = objectui @53ded82b packages/plugin-grid/src/ObjectGrid.tsx): S#schemaSort S#cols (the fetch path's string arm and array arm) S#declaredSort (parseSchemaSort, `TableSortItem[]`); the registration's array input objectui @53ded82b packages/plugin-grid/src/index.tsx (`{ name: 'sort', type: 'array' }`); [39] object-grid.defaultSort (REMOVED, tombstone; D = objectui @190fbd01d packages/plugin-grid/src/ObjectGrid.tsx): D#cols (`$orderby` fallback) and D#declaredSort (`[schema.defaultSort]`); [40] object-grid.pagination (D = objectui @d18322415 packages/plugin-grid/src/ObjectGrid.tsx): pageSize D#lastFindParamsRef D#schemaPageSize D#pageSize; pageSizeOptions D#dataTableSchema; presence D#paginationEnabled; the registry input objectui @d18322415 packages/plugin-grid/src/index.tsx (`pagination`, type object); [41] object-grid members read at objectui @89cad75d55 (O = objectui @89cad75d55 packages/plugin-grid/src/ObjectGrid.tsx): rowHeight O#resolveRowHeightMode; aggregations O#groupHeaders (useGroupedData) O#serverGroupSource (useServerGroupHeaders); conditionalFormatting O#getRowStyle (resolveConditionalFormatting); rowColor O#getRowClassName (useRowColor); selection and selectable O#ObjectGrid (the selection-mode branch) O#dataTableSchema (`selectable`), read by objectui @89cad75d55 packages/components/src/renderers/complex/data-table.tsx#resolveSelectionMode; rowActions O#rowActionsDeclared; bulkActions, batchActions O#declaredBulkActions (`batchActions ?? bulkActions`); bulkActionDefs O#legacyBulkNames (resolveBulkActions); [42] object-grid.navigation: objectui @89cad75d55 packages/plugin-grid/src/ObjectGrid.tsx#navigation (useNavigationOverlay); [43] object-grid.keyboardNavigation (built by objectui 154075ab1, objectui#11068; declared ahead of its reader, which read nothing at objectui @db11afd4967c): objectui @b9814b334 packages/plugin-grid/src/ObjectGrid.tsx#dataTableSchema (`schema.keyboardNavigation ?? inlineEditable`) with objectui @b9814b334 packages/plugin-grid/src/ObjectGrid.tsx#inlineEditable; objectui @b9814b334 packages/components/src/renderers/complex/data-table.tsx#DataTableRenderer (default false, `role=\"grid\"`, the roving tabIndex), objectui @b9814b334 packages/components/src/renderers/complex/data-table.tsx#navigationTarget and objectui @b9814b334 packages/components/src/renderers/complex/data-table.tsx#handleCellKeyDown; [44] object-grid.resizableColumns (REMOVED, tombstone): objectui @89cad75d55 packages/plugin-grid/src/ObjectGrid.tsx#dataTableSchema (`schema.resizable ?? schema.resizableColumns ?? true`); [45] object-grid.operations (GridOperationsSchema; O = objectui @89cad75d55 packages/plugin-grid/src/ObjectGrid.tsx): create O#dataTableSchema (showAddRow); update, delete O#wantDeleteAction; export O#handleExport O#dataTableSchema (exportable) O#exportEnabled; a declared block replaces the default O#explicitOperations; [46] object-grid.data: the registry input objectui @b9814b334 packages/plugin-grid/src/index.tsx#GRID_QUERY_INPUTS (`{ name: 'data', type: 'object' }`, held by gridDataInputContract.test.ts); re-read 2026-10-15, first cited sha-less; [47] object-metric.aggregate (ObjectMetricAggregateSchema; M = objectui @89cad75d55 packages/plugin-dashboard/src/ObjectMetricWidget.tsx): field, function M#computeOne; function also M#fieldUnitApplies (answersInFieldUnit) M#inferredFormat (min/max width); groupBy M#computeOne (objectAggregateSpecQuery; floored at `'_all'`) typed on M#ObjectMetricWidgetProps (`ChartAggregate['groupBy']`); objectui's unit-rule pin ObjectMetricWidget.countNotCurrency-10356.test.tsx mounts one tile per AggregationFunction member; [48] object-metric.trend (ObjectMetricTrendSchema; W = objectui @89cad75d55 packages/plugin-dashboard/src/): drawn in W MetricWidget.tsx#MetricWidget (`{value}%`, direction arrow), caption W MetricWidget.tsx#localizedTrendLabel (pickLocalized); declared on W MetricWidget.tsx#MetricWidgetProps, mirrored on W ObjectMetricWidget.tsx#ObjectMetricWidgetProps; replaced by a derived trend in W ObjectMetricWidget.tsx#effectiveTrend; [49] object-metric.drillDown (ObjectMetricDrillDownSchema; A = objectui @ab1879721595): enabled A packages/plugin-dashboard/src/ObjectMetricWidget.tsx#drillEnabled (isDrillEnabled); title A packages/plugin-dashboard/src/ObjectMetricWidget.tsx#drawerTitle (resolveDrillTitle against an empty event); target, columns, maxRows A packages/plugin-dashboard/src/ObjectMetricWidget.tsx#drillDrawer (DrillDownDrawer); `filter` / `mode` refused as objectui's own A packages/types/src/data-display.ts#ObjectMetricDrillDownConfig refuses them; measured writers, objectui's drill-down member pins A packages/plugin-dashboard/src/__tests__/objectMetricDrillDownMembers-8071.test.tsx and A packages/plugin-dashboard/src/__tests__/ObjectMetricWidget.drillRoutedToSharedDrawer-8970.test.tsx; [50] object-metric.drillDown.report (R = objectui @2e818d0b51ec packages/plugin-dashboard/src/): handed verbatim in R ObjectMetricWidget.tsx#drillDrawer; drawn when R DrillDownDrawer.tsx#isDatasetBoundReport holds (R DrillDownDrawer.tsx#isReportDrill), joining the metric filter into `runtimeFilter` (R DrillDownDrawer.tsx#body); typed `SpecReportInput` in objectui @2e818d0b51ec packages/types/src/data-display.ts; the fork measured at objectui @ab1879721595, both files byte-identical at objectui main 2abec3a96; [51] object-metric.compareTo (ObjectMetricCompareToSchema; A = objectui @ab1879721595): A packages/plugin-dashboard/src/ObjectMetricWidget.tsx#comparisonFilter (second aggregate over shiftFilterByCompareTo) and A packages/plugin-dashboard/src/ObjectMetricWidget.tsx#derivedTrend (compareToTrendLabelKey); both dispatch on `kind === 'previousYear'` in A packages/core/src/utils/compare-to.ts#shiftFilterByCompareTo and A packages/core/src/utils/compare-to.ts#compareToTrendLabelKey; `dimension` \"deliberately never read on this path\" in that module's header; [52] object-metric (ObjectMetricPropsSchema): objectui @eb7f586b packages/plugin-dashboard/src/ObjectMetricWidget.tsx#ObjectMetricWidgetProps (the complete destructured read set); [53] object-metric.icon (B = objectui @b9814b334 packages/plugin-dashboard/src/): published by B index.tsx#ObjectMetricBlock (`{ name: 'icon', type: 'string' }`); destructured and forwarded by B ObjectMetricWidget.tsx#ObjectMetricWidget; resolved and drawn by B MetricWidget.tsx#MetricWidget (getLazyIcon, guarded on a string); vocabulary objectui @b9814b334 packages/components/src/lib/lazy-icon.tsx#getLazyIcon; not B MetricCard.tsx (off this key's path); [54] object-metric.filter: the analytics wire lowering objectui @53ded82b packages/data-objectstack/src/index.ts#lowerAnalyticsFilterForWire (objectui#7754); before it the adapter at objectui @a472b07 posted an array verbatim (a 400, #15828); [55] object-kanban.columns[] lane (ObjectKanbanLaneSchema; K = objectui @89cad75d55 packages/plugin-kanban/src/): id K index.tsx#bucketCardsIntoColumns; title K ObjectKanban.tsx#localizeColumn; cards K index.tsx#bucketCardsIntoColumns (kept ahead of the bucketed records); limit K KanbanImpl.tsx#isLimitExceeded; className K KanbanImpl.tsx#KanbanColumnView; collapsed K KanbanImpl.tsx#isColumnCollapsed; [56] object-kanban (ObjectKanbanPropsSchema; E = objectui @eb7f586b packages/plugin-kanban/src/ObjectKanban.tsx): columns E#effectiveColumns; filter E#fetchData (`$filter`); data E#boundData; cardTitle, titleField E#explicitTitleField; cardFields E#explicitCardFields; swimlaneField, grouping E#effectiveSwimlaneField; coverImageField, conditionalFormatting via objectui @eb7f586b packages/plugin-kanban/src/index.tsx#KanbanRenderer (the spread of the authored bag); limit objectui @b9814b334 packages/plugin-kanban/src/ObjectKanban.tsx#query (`$top: resolveRowLimit(schema.limit, DEFAULT_KANBAN_FETCH_BATCH_SIZE)`), first re-read at objectui @87af769e9 where `$top: schema.limit ?? DEFAULT_KANBAN_LIMIT` had become resolveRowLimit; [57] object-kanban.columns (the first-entry dispatch): objectui @89cad75d55 packages/plugin-kanban/src/ObjectKanban.tsx#effectiveColumns (string arm only without `groupBy`); [58] object-kanban.filter: objectui @53ded82b packages/plugin-kanban/src/ObjectKanban.tsx#ObjectKanban (`$filter: schema.filter`, verbatim, lowered by ObjectStackAdapter.convertQueryParams through translateFilterArray); [59] object-kanban.limit (B = objectui @b9814b334): query B packages/plugin-kanban/src/ObjectKanban.tsx#query (`$top: resolveRowLimit(schema.limit, DEFAULT_KANBAN_FETCH_BATCH_SIZE)`) with B packages/plugin-kanban/src/ObjectKanban.tsx#DEFAULT_KANBAN_FETCH_BATCH_SIZE and the resolved filter B packages/plugin-kanban/src/ObjectKanban.tsx#filterScope; mapping B packages/plugin-kanban/src/index.tsx#OBJECT_KANBAN_DATA_SOURCE (`limit: 'limit'`); type B packages/types/src/objectql.ts#ObjectKanbanSchema (`limit?: number`); docs B content/docs/plugins/plugin-kanban.mdx (`limit: 250` snippet and Properties row); precedence B packages/react/src/element-data-source/ElementDataSourceGate.tsx#useElementDataSourceSchema (binding cap over this key; a view's pagination.pageSize only when unset) and B packages/react/src/element-data-source/ElementDataSourceGate.tsx#isUsableRowLimit; the saved-view fallback B packages/core/src/data-scope/element-data-source.ts#savedViewRawLimit; first re-read at objectui @87af769e9 2026-09-20; [60] object-master-detail-form (ObjectMasterDetailFormPropsSchema): objectui @eb7f586b packages/plugin-form/src/MasterDetailForm.tsx#MasterDetailForm; auto-derivation objectui @eb7f586b packages/plugin-form/src/deriveMasterDetail.ts; the `formType` pair objectui's MasterDetailFormSchema declares; [61] object-kanban.quickAdd (RETIRED, tombstone; K = objectui @b9814b334 packages/plugin-kanban/src/): K index.tsx#KanbanRenderer passes `quickAdd` / `onQuickAdd` (the host-mounted React component); gated on both in K KanbanImpl.tsx#KanbanColumnView; K KanbanBoardCore.tsx#KanbanBoardCoreProps reads neither off `schema` (objectui#11234); K ObjectKanban.tsx#ObjectKanban names `quickAdd` and `onQuickAdd` 2 times each (both in comments) against 11 for `onCardClick` (method: occurrences in that file, re-counted 2026-10-15); no `kanban-ui` registration at that sha (objectui#8257; the name survives only in tests and the changelog); through objectui @db11afd49 the board's spread forwarded the key; [62] object-kanban.conditionalFormatting (A = objectui @ab1879721595): A packages/plugin-kanban/src/KanbanBoardCore.tsx#processedColumns hands it to the board; A packages/plugin-kanban/src/KanbanImpl.tsx#getCardStyles evaluates it through A packages/core/src/evaluator/listConditional.ts#resolveConditionalFormatting, the evaluator of A packages/plugin-grid/src/ObjectGrid.tsx#getRowStyle; objectui's declaration A packages/types/src/zod/objectql.zod.ts#KanbanConditionalFormattingRuleSchema and A packages/types/src/objectql.ts#KanbanConditionalFormattingRule (objectui#11522, landed by objectui#11532 c73cdb5695); the stored-dialect arms in A packages/plugin-kanban/src/KanbanImpl.tsx#getCardStyles; blank-predicate fallback A packages/core/src/evaluator/listConditional.ts#evalRowPredicate; the four reader files byte-identical at objectui main 2e818d0b51; the writer census also read objectui at main; [63] object-kanban.navigation (K = objectui @b9814b334 packages/plugin-kanban/src/ObjectKanban.tsx): K#navConfig (`schema.navigation ?? { mode: 'drawer' }`) K#navigation (useNavigationOverlay) K#boardView (card click) K#renderRecordOverlay (NavigationOverlay); first re-derived at objectui @87af769e9, where the `(schema as any)` cast was still spelled; [64] object-calendar flat field keys (OBJECT_CALENDAR_FLAT_FIELD_GUIDANCE): objectui @b9814b334 packages/plugin-calendar/src/ObjectCalendar.tsx#getCalendarConfig (the backward-compat flat fallback); re-read 2026-10-15, first cited sha-less; [65] object-calendar (ObjectCalendarPropsSchema; C = objectui @eb7f586b packages/plugin-calendar/src/ObjectCalendar.tsx): calendar C#getCalendarConfig; defaultView C#schemaDefaultView; locale, loading via the registry shell objectui @eb7f586b packages/plugin-calendar/src/index.tsx; [66] object-calendar.calendar (CalendarConfigSchema by reference; C = objectui @89cad75d55 packages/plugin-calendar/src/ObjectCalendar.tsx): C#getCalendarConfig returns the block; its five bindings read in C#queryFilter, C#handleEventDropDefault and C#submitQuickCreate; [67] object-calendar.filter: objectui @53ded82b packages/plugin-calendar/src/ObjectCalendar.tsx#fetchData (`$filter: schema.filter`, verbatim); [68] object-tree.navigation: objectui @89cad75d55 packages/plugin-tree/src/ObjectTree.tsx#navigation (useNavigationOverlay); [69] object-calendar.sort: objectui @53ded82b packages/plugin-calendar/src/ObjectCalendar.tsx#fetchData (`$orderby: convertSortToQueryParams(schema.sort)`), whose sink objectui @53ded82b packages/core/src/utils/sort-query.ts#convertSortToQueryParams still lowered the string clause there; no `sort` input on objectui @53ded82b packages/plugin-calendar/src/index.tsx; [70] object-timeline flat config keys (OBJECT_TIMELINE_FLAT_CONFIG_GUIDANCE): the backward-compat flat rung (with the `dateField` alias) in objectui @b9814b334 packages/plugin-timeline/src/ObjectTimeline.tsx#startDateField and its siblings; emitted by objectui @b9814b334 packages/plugin-list/src/ListView.tsx#viewComponentSchema (`case 'timeline'`); re-read 2026-10-15, first cited sha-less; [71] object-calendar.navigation (C = objectui @b9814b334 packages/plugin-calendar/src/ObjectCalendar.tsx): C#navConfig (`schema.navigation ?? { mode: 'drawer' }`) C#navigation (useNavigationOverlay) C#calendarView (event click) C#renderRecordOverlay (NavigationOverlay); [72] object-timeline.mapping (ObjectTimelineMappingSchema; A = objectui @ab1879721595 packages/plugin-timeline/src/ObjectTimeline.tsx; unchanged at objectui main 94985a92ba): title A#titleField; date A#startDateField; description A#descField; variant A#variantField; declared on A#ObjectTimelineProps (`mapping`) and A#TimelineMappingSchema; [73] object-timeline.items[] arms (R = objectui @2e818d0b51ec packages/plugin-timeline/src/renderer.tsx; unchanged at objectui main 2abec3a96): gantt bar startDate / endDate R#dimensions R#calculateDateRange, variant and title in R#TimelineRenderer (the bar and its tooltip); feed entry time R#dateLabel and title, description, variant, icon, content (renderChildren), className in R#TimelineRenderer; gantt row label R#TimelineRenderer (TimelineGanttLabel), items R#classifyGanttRows; vocabulary objectui @2e818d0b51ec packages/types/src/data-display.ts#TimelineItemVariant; element shape and refinement objectui @2e818d0b51ec packages/types/src/zod/data-display.zod.ts#TimelineItemSchema and objectui @2e818d0b51ec packages/types/src/zod/data-display.zod.ts#timelineItemsFitVariant; [74] object-form.layout retired values `inline` / `grid` (OBJECT_FORM_LAYOUT_RETIRED; P = objectui @f8a9d0fb0 packages/plugin-form/src/): folded to 'vertical' in P ObjectForm.tsx#SimpleObjectForm; only 'vertical' / 'horizontal' passed through by P ObjectForm.tsx#drawerLayout, ObjectForm.tsx#modalLayout, DrawerForm.tsx#formLayout, ModalForm.tsx#formLayout; 'vertical' hard-coded in P TabbedForm.tsx#TabbedForm, SplitForm.tsx#panes, WizardForm.tsx#WizardForm; [75] object-form.mobile (ObjectFormMobileSchema; F = objectui @89cad75d55 packages/plugin-form/src/ObjectForm.tsx): F#mobileOpts (the flat arm); stickyActions F#formSchema; stepper, stepperMinFields, stepperFieldsPerStep F#stepperMode; fullscreenLongText F#fieldsWithMobile; the `data-mobile-form` marker in F#SimpleObjectForm; [76] object-form.fields and object-master-detail-form.fields (formFieldNameList): objectui @ab1879721595 packages/plugin-form/src/MasterDetailForm.tsx#parentSchema (hands `fields` over verbatim), read by objectui @ab1879721595 packages/plugin-form/src/ObjectForm.tsx#fieldsToShow and objectui @ab1879721595 packages/plugin-form/src/flatFields.ts#buildFlatFields (a stored `{ name }` entry drawn by name; any other object entry warned by warnUnresolvedTopLevelField); unchanged at objectui main 94985a92ba; the retired inline field entry an author was taught by objectui @ab1879721595 skills/objectui/guides/page-builder.md (its object-form example; first cited sha-less); [77] object-form.customFields[] `grid` widget keys (minRows, maxRows, allowAdd, allowDelete, allowReorder, totalField, addLabel, sortField; W = objectui @9dfaca654311 packages/fields/src/widgets/GridField.tsx): declared on objectui @9dfaca654311 packages/types/src/field-types.ts#GridFieldMetadata, carried by objectui @9dfaca654311 packages/types/src/form.ts#FormField, mirrored in objectui @9dfaca654311 packages/types/src/zod/form.zod.ts#FormFieldSchema; read W#cfg W#allowAdd W#removeRow (minRows) W#totalField W#showTotal W#emit (sortField) W#GridFieldBody (maxRows, allowReorder, addLabel); objectui#11610 (merge 2abec3a96c) camelCased them; the parent-field `totalField` hand-off objectui @9dfaca654311 packages/plugin-form/src/LineItemsPanel.tsx#gridField and objectui @9dfaca654311 packages/plugin-form/src/MasterDetailForm.tsx#MasterDetailLines; the writer shape (one `grid` field carrying all eight camelCase keys) objectui @9dfaca654311 examples/schema-catalog/src/schemas/fields-grid/line-items-grid.json; [78] object-form.customFields[] retired snake_case `grid` keys (OBJECT_FORM_GRID_WIDGET_SNAKE_KEYS): objectui @9dfaca654311 packages/types/src/field-types.ts#GRID_FIELD_RETIRED_KEYS; refused by objectui @9dfaca654311 packages/fields/src/widgets/GridField.tsx#GridField, tombstoned on objectui @9dfaca654311 packages/types/src/field-types.ts#GridFieldMetadata and objectui @9dfaca654311 packages/types/src/form.ts#FormField, named in objectui @9dfaca654311 packages/types/src/zod/form.zod.ts#FormFieldSchema (retiredGridFieldKey); [79] object-form.customFields[].validation: objectui @2e818d0b51ec packages/components/src/renderers/form/form.tsx#rules (inside renderFormField: minLength/maxLength/min/max spread as `{ value, message }`, `required` read only as a message string); objectui's declaration objectui @2e818d0b51ec packages/types/src/form.ts#FieldValidationRules; [80] object-timeline.navigation: objectui @b9814b334 packages/plugin-timeline/src/ObjectTimeline.tsx#navigation (useNavigationOverlay) and objectui @b9814b334 packages/plugin-timeline/src/ObjectTimeline.tsx#effectiveSchema (entry click, overlay); re-read 2026-10-15, first cited sha-less, when the read still spelled `(schema as any).navigation`; [81] object-form.customFields[].options[] (buildObjectFormRuntimeOption; F = objectui @2e818d0b51ec packages/components/src/renderers/form/form.tsx): label, value F#BuiltinSelectControl and objectui @2e818d0b51ec packages/core/src/evaluator/optionRules.ts#optionDisplayLabel (the four option widgets); visibleWhen F#cascade (resolveCascadingOptions; each widget's useCascadingOptions); description objectui @2e818d0b51ec packages/fields/src/widgets/LookupField.tsx#filteredOptions; `value` a runtime value: F#BuiltinSelectControl (matchOptionValue), objectui @2e818d0b51ec packages/types/src/zod/form.zod.ts#SelectOptionSchema (deliberate divergence comment), objectui @2e818d0b51ec packages/plugin-designer/src/ObjectManager.tsx#formSchema (writes `{ label, value }` pairs); unchanged at objectui main fd060f076; [82] object-form.customFields[] and the inline section entry (buildObjectFormRuntimeField; F = objectui @2e818d0b51ec packages/components/src/renderers/form/form.tsx, W = objectui @2e818d0b51ec packages/fields/src/widgets/): merge objectui @2e818d0b51ec packages/plugin-form/src/customFieldsMerge.ts#mergeCustomFields from objectui @2e818d0b51ec packages/plugin-form/src/ObjectForm.tsx#generateField; carrier F#builtinBranchHasNoControl (`field.field || field`); field row F#renderFormField (name, label, description, type, required, disabled, readonly, hidden, colSpan via F#colSpanClass), widget F#resolvedType, validation F#rules, the three `*When` rules F#ruleState, placeholder and inputType F#builtinBranchHasNoControl, options and dependsOn F#cascade, multiple F#resolvedType; layout objectui @2e818d0b51ec packages/plugin-form/src/autoLayout.ts#resolveColSpan (span, colSpan) and objectui @2e818d0b51ec packages/plugin-form/src/fieldGroups.ts#deriveFieldGroupSections (group); widgets W TextAreaField.tsx#rows, FileField.tsx#multiple (accept, multiple), VectorField.tsx#dimensions, LookupField.tsx#referenceTo, NumberField.tsx#NumberField (min, max), F#renderFieldComponent (minLength, maxLength), FormulaField.tsx#returnType, SummaryField.tsx#summaryType, GridField.tsx#allColumns (columns); read and refused: visibleOn F#ruleState, condition F#legacyConditionCel, id F#fieldKey; a measured writer, the object-manager create / edit dialog (a `formType: 'modal'` block): objectui @b9814b334 packages/plugin-designer/src/ObjectManager.tsx#formSchema (first cited sha-less); [83] object-form / object-master-detail-form sections[].fields[] `{ field }` entry (buildObjectFormSectionFieldEntry): objectui @2e818d0b51ec packages/plugin-form/src/sectionFields.ts#resolveSectionEntry (names an object field by `field`, every other key overrides its generated definition; label/placeholder/helpText copied as they stand); [84] object-form / object-master-detail-form sections[] (buildObjectFormSection; F = objectui @2e818d0b51ec packages/plugin-form/src/ObjectForm.tsx): handed over verbatim by objectui @2e818d0b51ec packages/plugin-form/src/MasterDetailForm.tsx#parentSchema; name, label F#sectionCtx (heading) F#inertPredicateSections (per-formType maps); collapsible, collapsed F#collapse (resolveSectionCollapse); visibleWhen F#sectionCtx (divider-row predicate); columns F#secCols F#clampCol (a NUMBER only); pane objectui @2e818d0b51ec packages/plugin-form/src/SplitForm.tsx#paneOf; the inline runtime field drawn as it stands in objectui @2e818d0b51ec packages/plugin-form/src/sectionFields.ts#resolveSectionEntry; objectui's own declaration objectui @2e818d0b51ec packages/types/src/objectql.ts#ObjectFormSection; [85] object-form (ObjectFormPropsSchema: the UNION of `schema.*` reads of the simple form and the sub-forms it forwards the bag into): objectui @eb7f586b packages/plugin-form/src/ObjectForm.tsx#ObjectForm, with TabbedForm, WizardForm, SplitForm, DrawerForm, ModalForm under objectui @eb7f586b packages/plugin-form/src/; `layout` `inline` / `grid` folded to `vertical` there while the registry inputs and the designer palette offered them; [86] object-form.fields (the `{ name }` entry retired from the authoring faces by objectui#11550): a census of objectui @ab1879721595 found it written only by fixtures probing the stored read and the refusal; [87] object-form.customFields: objectui @2e818d0b51ec packages/plugin-form/src/customFieldsMerge.ts#mergeCustomFields (merged over the generated fields); at objectui @2e818d0b51ec the open runtime field spelled the eight `grid` widget keys in snake_case, camelCased by objectui#11610 (carried at objectui @9dfaca654311); [88] object-form.contentLayout: objectui @89cad75d55 packages/plugin-form/src/ModalForm.tsx#renderContent (`schema.contentLayout === 'tabbed' && groups.length > 1`), declared on objectui @89cad75d55 packages/plugin-form/src/ModalForm.tsx#ModalFormSchema; [89] object-form.submitBehavior: objectui @89cad75d55 packages/plugin-form/src/ObjectForm.tsx#handleSubmit and objectui @89cad75d55 packages/plugin-form/src/WizardForm.tsx#handleStepSubmit (switch on `kind`), redirect `url` judged by objectui @89cad75d55 packages/plugin-form/src/submitRedirect.ts#resolveSubmitRedirect (FormViewSchema.safeParse); [90] object-form.navigateOnSuccess: objectui @89cad75d55 packages/plugin-form/src/successBehavior.ts#resolveSuccessNavigate, called from objectui @89cad75d55 packages/plugin-form/src/ObjectForm.tsx#handleSubmit and objectui @89cad75d55 packages/plugin-form/src/WizardForm.tsx#handleStepSubmit; a refused value reported on the success toast in objectui @89cad75d55 packages/plugin-form/src/ObjectForm.tsx#handleSubmit; [91] object-master-detail-form.details[] (the eleven entry keys): objectui @31971ff1e28f packages/plugin-form/src/MasterDetailForm.tsx#MasterDetailDetailConfig and every `d.KEY` read in objectui @31971ff1e28f packages/plugin-form/src/MasterDetailForm.tsx#MasterDetailLines; re-read at objectui @89cad75d5570 for #21589; [92] object-master-detail-form.details[].sortField (REMOVED, tombstone): objectui @89cad75d5570 packages/plugin-form/src/MasterDetailForm.tsx#MasterDetailDetailConfig (no `sortField` member), derived by objectui @89cad75d5570 packages/plugin-form/src/deriveMasterDetail.ts#deriveDetail (SORT_FIELD_NAMES) and handed to the grid as `sort_field` in objectui @89cad75d5570 packages/plugin-form/src/MasterDetailForm.tsx#MasterDetailLines; at objectui @9dfaca654311 the same hand-off spells `sortField` (objectui#11610) from objectui @9dfaca654311 packages/plugin-form/src/deriveMasterDetail.ts#deriveDetail; an entry the renderer does not resolve loads no child schema (objectui @9dfaca654311 packages/plugin-form/src/MasterDetailForm.tsx#entries); [93] object-master-detail-form.sections, .fields: objectui @2e818d0b51ec packages/plugin-form/src/MasterDetailForm.tsx#parentSchema (handed to the parent object-form verbatim); [94] object-map flat config keys (OBJECT_MAP_FLAT_CONFIG_GUIDANCE): objectui @b9814b334 packages/plugin-map/src/ObjectMap.tsx#getMapConfig (branch 2) and objectui @b9814b334 packages/plugin-map/src/ObjectMap.tsx#warnOnShadowedFlatMapKeys and objectui @b9814b334 packages/plugin-map/src/ObjectMap.tsx#FLAT_MAP_CONFIG_KEYS; emitted by objectui @b9814b334 packages/plugin-view/src/ObjectView.tsx#ObjectView and objectui @b9814b334 packages/plugin-list/src/ListView.tsx#viewComponentSchema (`case 'map'`); re-read 2026-10-15, first cited sha-less at the then pin; [95] object-map (ObjectMapPropsSchema, per key; P = objectui @b9814b334 packages/plugin-map/src/ObjectMap.tsx, C = objectui @b9814b334 packages/core/src/utils/record-source.ts, I = objectui @b9814b334 packages/plugin-map/src/index.tsx): data P#getDataConfig C#resolveRecordSourceConfig (rung 1; the deleted array head and its carriers are recorded in the docblock above P#FLAT_MAP_CONFIG_KEYS); staticData C#resolveRecordSourceConfig (rung 2); objectName C#resolveRecordSourceConfig (rung 3) P#navigation P#warnOnLegacyFilterMapConfig P#warnOnTopLevelStyleUrl P#warnOnShadowedFlatMapKeys (cache keys) P#recordSourceObjectName (useSettledSchema); filter P#queryFilter P#fetchData (`$filter` on both fetches); sort P#fetchData (convertSortToQueryParams); map P#getMapConfig (branch 1; validates against ObjectMapConfigSchema) I#ObjectMapRenderer (its registration's `map` input); mapStyle P#getMapConfig (`schema.mapStyle || schema.map?.style`) I#ObjectMapRenderer; navigation P#navigation I#ObjectMapRenderer; enableClustering P#clusteredData I#ObjectMapRenderer; measured and not declared: style P#warnOnTopLevelStyleUrl (read only to say it is not a map style); [96] object-map.filter: objectui @b9814b334 packages/plugin-map/src/ObjectMap.tsx#queryFilter (useResolvedFilter) and objectui @b9814b334 packages/plugin-map/src/ObjectMap.tsx#fetchData (`$filter` on the inline and the object fetch); the registration's `filter` input objectui @b9814b334 packages/plugin-map/src/index.tsx#ObjectMapRenderer; [97] object-map.sort: objectui @b9814b334 packages/plugin-map/src/ObjectMap.tsx#fetchData (convertSortToQueryParams to `$orderby`, both fetches); the registration's array-form `sort` input (objectui#8220) objectui @b9814b334 packages/plugin-map/src/index.tsx#ObjectMapRenderer; [98] object-map.navigation: objectui @89cad75d55 packages/plugin-map/src/ObjectMap.tsx#navigation (hands `schema.navigation` to useNavigationOverlay) and objectui @89cad75d55 packages/react/src/hooks/useNavigationOverlay.ts#useNavigationOverlay (`navigation?.mode ?? 'page'`); [99] object-gantt flat config keys (OBJECT_GANTT_FLAT_CONFIG_GUIDANCE): objectui @b9814b334 packages/plugin-gantt/src/ObjectGantt.tsx#FLAT_GANTT_CONFIG_KEYS and objectui @b9814b334 packages/plugin-gantt/src/ObjectGantt.tsx#getGanttConfig (branch 2 reads them; a present `gantt` block is taken whole and the flat keys beside it named as ignored); re-read 2026-10-15, first cited sha-less at the then pin; [100] object-gantt.markers (objectGanttMarker): G = objectui @ab1879721595 packages/plugin-gantt/src/GanttView.tsx: objectui @ab1879721595 packages/plugin-gantt/src/ObjectGantt.tsx#ganttView (hands `schema.markers` to GanttView verbatim); date G#markers (time-zone re-base) G#resolvedMarkers (axis mapping, drops unparsable or out-of-range dates); color G#resolvedMarkers (`m.color || 'hsl(var(--primary))'`); drawn in G#GanttView and in G#buildExportSvg; the declaration taken: objectui @ab1879721595 packages/types/src/objectql.ts#ObjectGanttSchema and objectui @ab1879721595 packages/types/src/zod/objectql.zod.ts#ObjectGanttSchema (`markers`); unchanged at objectui main 94985a92ba; [101] object-gantt (ObjectGanttPropsSchema, per key; P = objectui @b9814b334 packages/plugin-gantt/src/ObjectGantt.tsx, C = objectui @b9814b334 packages/core/src/utils/record-source.ts): data P#rawDataConfig C#resolveRecordSourceConfig (rung 1); staticData C#resolveRecordSourceConfig (rung 2); objectName (rung 3) P#persistLayoutKey P#navigation; filter P#queryFilter P#reload (`$filter`); sort P#reload (convertSortToQueryParams to `$orderby`); gantt P#getGanttConfig (branch 1, validated against the spec's GanttConfigSchema) and objectui @b9814b334 packages/plugin-gantt/src/index.tsx#ObjectGanttRenderer (its registration's `gantt` object input); navigation P#navConfig P#navigation; label P#ganttView (resolveInlineI18nLabel, export file-name chain); skipWeekends, holidays P#workingCalendar; persistLayout, viewName P#persistLayoutKey; markers, criticalPath, showBaselines, readOnly, mobileReadOnly P#ganttView, readOnly also P#renderRecordOverlay; measured and not declared: title (absent from the P#ganttView export-name chain), row cap (P#reload spreads nonGridRowCeilingQuery, marked not authorable), search and searchableFields P#searchTerm P#reload (host-generated by objectui @b9814b334 packages/plugin-list/src/ListView.tsx#viewComponentSchema, its `case 'gantt'` arm); [102] object-gantt.navigation: objectui @89cad75d55 packages/plugin-gantt/src/ObjectGantt.tsx#navConfig (`schema.navigation ?? { mode: 'drawer' }`) and objectui @89cad75d55 packages/plugin-gantt/src/ObjectGantt.tsx#navigation (useNavigationOverlay); [103] object-tree flat config keys (OBJECT_TREE_FLAT_CONFIG_GUIDANCE: parentField, labelField, titleField, fields, defaultExpandedDepth): objectui @b9814b334 packages/plugin-tree/src/ObjectTree.tsx#getTreeConfig (reads the flat spellings ahead of the `tree` block; the `schema.titleField` rung's retirement is recorded in its docblock above `fieldKey`, objectui#8841), objectui @b9814b334 packages/plugin-list/src/ListView.tsx#viewComponentSchema (its `case 'tree'` arm emits parentField, `labelField: treeCfg.labelField || 'name'`, fields, defaultExpandedDepth and no titleField since objectui 3fd862510), objectui @b9814b334 packages/plugin-tree/src/index.tsx#treeInputs (`{ name: 'tree', type: 'object' }`, no flat input); [104] object-tree (ObjectTreePropsSchema, per key; P = objectui @b9814b334 packages/plugin-tree/src/ObjectTree.tsx, C = objectui @b9814b334 packages/core/src/utils/record-source.ts): data P#dataConfig (resolveRecordSourceConfig with the `view-data` arm) and C#resolveRecordSourceConfig (rung 1, authoredDataIsOnTheDeclaredArm); staticData C#resolveRecordSourceConfig (rung 2); objectName (rung 3) P#headerObjectName P#navigation P#renderRecordOverlay (each behind resolveRecordSourceObjectName) P#parentField (detectParentField); filter P#queryFilter (useResolvedFilter) P#fetchData (`$filter` on the object fetch and on the inline ValueDataSource query); tree P#getTreeConfig and objectui @b9814b334 packages/plugin-tree/src/index.tsx#treeInputs; navigation P#navigation (useNavigationOverlay); host-prop `data` (not the authored key) P#passed; measured and not declared: sort (no `$orderby` in P#fetchData), row cap (P#fetchData spreads nonGridRowCeilingQuery); [105] object-tree.objectName (optional because no ElementDataSourceGate wiring): objectui @b9814b334 packages/plugin-tree/src/index.tsx#ObjectTreeRenderer has 0 occurrences of `ElementDataSourceGate` (re-counted 2026-10-15), against controls objectui @b9814b334 packages/plugin-gantt/src/index.tsx#ObjectGanttRenderer, packages/plugin-grid/src/index.tsx, packages/plugin-calendar/src/index.tsx (3 each) and packages/plugin-map/src/index.tsx#ObjectMapRenderer (4); method: occurrences of the identifier in each `src/index.tsx`; [106] object-timeline (ObjectTimelinePropsSchema, per key; P = objectui @b9814b334 packages/plugin-timeline/src/ObjectTimeline.tsx, R = objectui @b9814b334 packages/plugin-timeline/src/renderer.tsx, I = objectui @b9814b334 packages/plugin-timeline/src/index.tsx): objectName P#ObjectTimeline (fetch gate) P#useSettledSchema P#fetchesForItself P#invalidationNonce P#fetchData P#handleRefresh P#effectiveItems P#navigation; timeline P#timelineConfig; filter P#queryFilter P#filterKey P#fetchData; sort P#sortKey P#fetchData (convertSortToQueryParams); limit P#fetchData (resolveRowLimit) P#DEFAULT_TIMELINE_LIMIT P#describeRefusedRowLimit; items P#hasInlineItems P#fetchesForItself P#effectiveItems P#hasAuthoredItems; data P#hasInlineData P#fetchesForItself P#rawData; descriptionField P#descField; mapping P#titleField P#startDateField P#descField P#variantField; variant P#hasAuthoredItems (the `timeline-unsupported-variant` refusal, objectui#6655) and I#OBJECT_TIMELINE_INPUTS; navigation P#navigation P#effectiveSchema; dateFormat, rowLabel, minDate, maxDate R#TimelineRenderer (rowLabel and minDate/maxDate in its gantt branch, R#findUnusableGanttDate) off P#effectiveSchema; measured and not declared: bind P#boundData (useDataScope), className P#effectiveSchema; [107] object-timeline.data (read off React props, not `schema`; the prop channel is SchemaRenderer's `properties.*` hoist, objectui @b9814b334 packages/react/src/SchemaRenderer.tsx#SchemaRenderer, with `object-timeline` absent from objectui @b9814b334 packages/core/src/utils/record-source.ts#RECORD_SOURCE_DATA_ARM_BY_TYPE): objectui @b9814b334 packages/plugin-timeline/src/ObjectTimeline.tsx#hasInlineData (loading seed), objectui @b9814b334 packages/plugin-timeline/src/ObjectTimeline.tsx#fetchesForItself (the query skip the fetch effect reads), objectui @b9814b334 packages/plugin-timeline/src/ObjectTimeline.tsx#rawData (row source `(props as any).data || boundData || fetchedData`), objectui @b9814b334 packages/plugin-timeline/src/ObjectTimeline.tsx#effectiveItems (the `timeline` field bindings a fetched row takes); [108] object-timeline.items: objectui @2e818d0b51ec packages/types/src/data-display.ts#TimelineFeedItem and objectui @2e818d0b51ec packages/types/src/data-display.ts#TimelineGanttItem (the declared authored entry, ruled on objectui#6356), handed to the rail verbatim at objectui @2e818d0b51ec packages/plugin-timeline/src/ObjectTimeline.tsx#effectiveItems (`if (schema.items) return schema.items`); [109] record:reference_rail, record:alert, record:quick_actions, record:history, record:line_items (rows): key sets measured from the renderers' read points; the per-key read points are recorded under each props schema below; [110] record:approval_decision (row, EMPTY key set): objectui @d03b022 packages/app-shell/src/views/approval-decision/ApprovalDecisionPanel.tsx#ApprovalDecisionRenderer (landed in objectui a368ccb, module-internal at d03b022; discards `schema`, draws from the record context); [111] record:attachments (row, EMPTY key set): objectui @47b1f0bb71 packages/app-shell/src/views/record-attachments-renderer.tsx#RecordAttachmentsRenderer (registered as `attachments` in the `record` namespace; discards `schema`); [112] cloud-connection:panel, marketplace:installed-list (rows, EMPTY key sets): objectui @b9814b334 packages/app-shell/src/console/cloud-connection/CloudConnectionPanel.tsx#CloudConnectionPanel and objectui @b9814b334 packages/app-shell/src/console/marketplace/InstalledListWidget.tsx#InstalledList (each registration is `() => `; re-read 2026-10-15, first measured sha-less at the then pin); [113] mcp:connect-agent (row, EMPTY key set): objectui @b9814b334 packages/app-shell/src/console/connect/ConnectAgentWidget.tsx#ConnectAgent (registration `() => `; re-read 2026-10-15, first measured sha-less at the then pin); [114] element:definition-list, element:repeater (rows: the three-part string-arm evidence): objectui @b9814b334 packages/components/src/renderers/basic/data-list.tsx#DefinitionListRenderer and objectui @b9814b334 packages/components/src/renderers/basic/data-list.tsx#RepeaterRenderer (both registered in the `element` namespace), objectui @b9814b334 packages/core/src/registry/public-blocks.ts#PUBLIC_BLOCKS (publication), objectui @b9814b334 packages/app-shell/src/views/metadata-admin/previews/block-types.ts#BLOCK_TYPE_META (palette), objectui @b9814b334 packages/app-shell/src/views/metadata-admin/previews/block-config.ts#BLOCK_CONFIG (each block's inspector); [115] action:button, action:group, action:menu, action:icon (rows): objectui @b9814b334 packages/core/src/registry/public-blocks.ts#PUBLIC_BLOCKS (the four `action:*` members), registered under objectui packages/components/src/renderers/action/ in the `action` namespace; [116] object-map, object-gantt, object-tree (row key sets; per-key read points under each props schema): objectui @b9814b334 packages/plugin-map/src/index.tsx#ObjectMapRenderer, objectui @b9814b334 packages/plugin-map/src/ObjectMap.tsx#ObjectMap, objectui @b9814b334 packages/plugin-gantt/src/index.tsx#ObjectGanttRenderer, objectui @b9814b334 packages/plugin-gantt/src/ObjectGantt.tsx#ObjectGantt, objectui @b9814b334 packages/plugin-tree/src/index.tsx#ObjectTreeRenderer, objectui @b9814b334 packages/plugin-tree/src/ObjectTree.tsx#ObjectTree; [117] object-timeline (row key set; per-key read points under ObjectTimelinePropsSchema): objectui @b9814b334 packages/plugin-timeline/src/index.tsx#ObjectTimelineRenderer (the `object-timeline` and `timeline` registrations), objectui @b9814b334 packages/plugin-timeline/src/ObjectTimeline.tsx#ObjectTimeline, objectui @b9814b334 packages/plugin-timeline/src/renderer.tsx#TimelineRenderer; [118] EXPRESSION_BINDABLE_TEXT_KEYS_BY_COMPONENT rows statistic, card, button (the top-level text keys each renderer reads back; R = objectui @82a9417 packages/components/src/renderers/, re-verified identical at objectui 6c68b13 on 2026-08-18): statistic `label`, `value`, `description` R data-display/statistic.tsx#StatisticRenderer; card `title`, `description` R layout/card.tsx#CardRenderer; button `label` R form/button.tsx#ButtonRenderer; the deliberate `text` omission, `schema.content || schema.value`, at its `text` registration in R basic/text.tsx; [119] PageComponent.dataSource, the PLANNED API binding (ElementApiDataSourceSchema; its record is this file's `_note`; R = objectui @7578323a544e packages/core/src/): `read.url`'s refusal of a `./` path, measured against the console's `apiBase` promotion of a root-relative `/api/…` path only, R actions/ActionRunner.ts#navigateTo; `read.method` GET alone because every read (find, findOne, aggregate) sends an explicit `method: 'GET'`, R adapters/ApiDataSource.ts#ApiDataSource; no renderer reads the binding yet, the hook admitting only a string `object`, R data-scope/element-data-source.ts#isElementDataSourceConfig; [120] PageComponent.visibleWhen (the component-level evaluator's roots): the ambient scope (`current_user`, `user`, `ctx.user`, `os.user`, `features`, nothing else) objectui @1fb1ced36243 packages/app-shell/src/providers/ExpressionProvider.tsx#buildExpressionScope; composed with `record` (only when the page has a row) and `page` by objectui @1fb1ced36243 packages/react/src/SchemaRenderer.tsx#configEvaluationScope; no `data` root (objectui#9308) in objectui @1fb1ced36243 packages/react/src/SchemaRenderer.tsx#evaluatedSchema; the files identical at objectui main f228d710; the `page:tabs` item-level evaluator is the `page:tabs items[].visibleWhen` entry;" }, "assignedProfiles": { @@ -142,57 +142,57 @@ "print": { "status": "live", "verifiedAt": "2026-10-16", - "evidenceScope": "cross-repo", - "evidence": "objectui @daeb77fc84dd: packages/components/src/renderers/layout/page-print.ts#buildPagePrintStylesheet (builds the `@media print` sheet from every key below, plus the two rules every print page needs: everything that is not the print page, an ancestor of it or inside it is hidden on paper, and the ancestors' viewport-height clipping is released); objectui @daeb77fc84dd: packages/components/src/renderers/layout/page.tsx#PageRenderer (mounts the sheet only for a page whose `print` is an object); framework packages/spec/src/ui/page.zod.ts#checkPagePrintComposition (the parse refuses `print` on a page that does not print its own authored blocks); packages/lint/src/validate-print-page-blocks.ts#validatePrintPageBlocks (refuses a block outside PRINTABLE_PAGE_COMPONENT_TYPES inside a print page at os validate / os build / os lint and the page save door)", + "evidenceScope": "in-repo", + "evidence": "ui/packages/components/src/renderers/layout/page-print.ts#buildPagePrintStylesheet (builds the `@media print` sheet from every key below, plus the two rules every print page needs: everything that is not the print page, an ancestor of it or inside it is hidden on paper, and the ancestors' viewport-height clipping is released); ui/packages/components/src/renderers/layout/page.tsx#PageRenderer (mounts the sheet only for a page whose `print` is an object); framework packages/spec/src/ui/page.zod.ts#checkPagePrintComposition (the parse refuses `print` on a page that does not print its own authored blocks); packages/lint/src/validate-print-page-blocks.ts#validatePrintPageBlocks (refuses a block outside PRINTABLE_PAGE_COMPONENT_TYPES inside a print page at os validate / os build / os lint and the page save door)", "note": "FLIPPED planned → live 2026-10-16 (#23710). Card ② of the ruling of record 6051470224 on #8346 (letter B′: \"A document is a page with a print declaration; no new template type\") has landed: the console's `PageRenderer` mounts, for a page whose `print` is an object, the print stylesheet `buildPagePrintStylesheet` builds from the declaration, every rule inside `@media print`, so the page on screen is drawn as before and the browser's print dialog (which also saves as PDF) prints the declared paper, margins, running header and footer, page numbers and page-break hints. All eight keys and the four `margins` sides are read; see each child. The reader landed in objectui 2312bd9a6 (objectui#11958), and the `.objectui-sha` pin daeb77fc84dd carries it (`git merge-base --is-ancestor 2312bd9a6 daeb77fc84dd` answers yes), so every objectui pointer here was read at the pin this repo builds against. Re-read when the pin moved f96f771140f1 → daeb77fc84dd (2026-10-16): the reader files are byte-identical at both. It was `planned` (verified 2026-10-08) with `authorWarn` per enforce-or-mark: the declaration landed spec-first with its REFUSALS live (the parse's `checkPagePrintComposition`, lint's `validatePrintPageBlocks`) while what the keys configure had no reader. `authorWarn` and `authorHint` are dropped in this edit: a `live` row refuses them, and an author who writes `print` is no longer told the layout is not applied. Still to come, and not this row's: the server-side PDF renderer (card ③) and the record-page \"generate PDF\" action (card ④).", "children": { "paperSize": { "status": "live", "verifiedAt": "2026-10-16", - "evidenceScope": "cross-repo", - "evidence": "objectui @daeb77fc84dd: packages/components/src/renderers/layout/page-print.ts#buildPagePrintStylesheet (the size keyword of `@page { size }`, written by `pageRule` only for an enum member (A4 when omitted or off-vocabulary)); objectui @daeb77fc84dd: packages/components/src/renderers/layout/page.tsx#PageRenderer (mounts the sheet only for a page whose `print` is an object)", + "evidenceScope": "in-repo", + "evidence": "ui/packages/components/src/renderers/layout/page-print.ts#buildPagePrintStylesheet (the size keyword of `@page { size }`, written by `pageRule` only for an enum member (A4 when omitted or off-vocabulary)); ui/packages/components/src/renderers/layout/page.tsx#PageRenderer (mounts the sheet only for a page whose `print` is an object)", "note": "FLIPPED planned → live 2026-10-16 (#23710). Applied by the console's browser print rendering: the size keyword of `@page { size }`, written by `pageRule` only for an enum member (A4 when omitted or off-vocabulary). It was `planned` (verified 2026-10-08) because the key was declared spec-first (#22158) with no reader in either repo. The reader landed in objectui 2312bd9a6 (objectui#11958), and the `.objectui-sha` pin daeb77fc84dd carries it (`git merge-base --is-ancestor 2312bd9a6 daeb77fc84dd` answers yes), so every objectui pointer here was read at the pin this repo builds against. Re-read when the pin moved f96f771140f1 → daeb77fc84dd (2026-10-16): the reader files are byte-identical at both." }, "orientation": { "status": "live", "verifiedAt": "2026-10-16", - "evidenceScope": "cross-repo", - "evidence": "objectui @daeb77fc84dd: packages/components/src/renderers/layout/page-print.ts#buildPagePrintStylesheet (the orientation keyword of `@page { size }`, written by `pageRule` only for `portrait` / `landscape` (portrait when omitted)); objectui @daeb77fc84dd: packages/components/src/renderers/layout/page.tsx#PageRenderer (mounts the sheet only for a page whose `print` is an object)", + "evidenceScope": "in-repo", + "evidence": "ui/packages/components/src/renderers/layout/page-print.ts#buildPagePrintStylesheet (the orientation keyword of `@page { size }`, written by `pageRule` only for `portrait` / `landscape` (portrait when omitted)); ui/packages/components/src/renderers/layout/page.tsx#PageRenderer (mounts the sheet only for a page whose `print` is an object)", "note": "FLIPPED planned → live 2026-10-16 (#23710). Applied by the console's browser print rendering: the orientation keyword of `@page { size }`, written by `pageRule` only for `portrait` / `landscape` (portrait when omitted). It was `planned` (verified 2026-10-08) because the key was declared spec-first (#22158) with no reader in either repo. The reader landed in objectui 2312bd9a6 (objectui#11958), and the `.objectui-sha` pin daeb77fc84dd carries it (`git merge-base --is-ancestor 2312bd9a6 daeb77fc84dd` answers yes), so every objectui pointer here was read at the pin this repo builds against. Re-read when the pin moved f96f771140f1 → daeb77fc84dd (2026-10-16): the reader files are byte-identical at both." }, "margins": { "status": "live", "verifiedAt": "2026-10-16", - "evidenceScope": "cross-repo", - "evidence": "objectui @daeb77fc84dd: packages/components/src/renderers/layout/page-print.ts#buildPagePrintStylesheet (one `@page` `margin-` descriptor per declared side, in millimetres, written by `pageRule` only for a finite non-negative number; a side left out keeps the console's default sheet margin); objectui @daeb77fc84dd: packages/components/src/renderers/layout/page.tsx#PageRenderer (mounts the sheet only for a page whose `print` is an object)", + "evidenceScope": "in-repo", + "evidence": "ui/packages/components/src/renderers/layout/page-print.ts#buildPagePrintStylesheet (one `@page` `margin-` descriptor per declared side, in millimetres, written by `pageRule` only for a finite non-negative number; a side left out keeps the console's default sheet margin); ui/packages/components/src/renderers/layout/page.tsx#PageRenderer (mounts the sheet only for a page whose `print` is an object)", "note": "FLIPPED planned → live 2026-10-16 (#23710). Applied by the console's browser print rendering: one `@page` `margin-` descriptor per declared side, in millimetres, written by `pageRule` only for a finite non-negative number; a side left out keeps the console's default sheet margin. It was `planned` (verified 2026-10-08) because the key was declared spec-first (#22158) with no reader in either repo. The reader landed in objectui 2312bd9a6 (objectui#11958), and the `.objectui-sha` pin daeb77fc84dd carries it (`git merge-base --is-ancestor 2312bd9a6 daeb77fc84dd` answers yes), so every objectui pointer here was read at the pin this repo builds against. Re-read when the pin moved f96f771140f1 → daeb77fc84dd (2026-10-16): the reader files are byte-identical at both.", "children": { "top": { "status": "live", "verifiedAt": "2026-10-16", - "evidenceScope": "cross-repo", - "evidence": "objectui @daeb77fc84dd: packages/components/src/renderers/layout/page-print.ts#buildPagePrintStylesheet (`pageRule` writes `margin-top: mm` when `margins.top` is a finite non-negative number); objectui @daeb77fc84dd: packages/components/src/renderers/layout/page.tsx#PageRenderer (mounts the sheet only for a page whose `print` is an object)", + "evidenceScope": "in-repo", + "evidence": "ui/packages/components/src/renderers/layout/page-print.ts#buildPagePrintStylesheet (`pageRule` writes `margin-top: mm` when `margins.top` is a finite non-negative number); ui/packages/components/src/renderers/layout/page.tsx#PageRenderer (mounts the sheet only for a page whose `print` is an object)", "note": "FLIPPED planned → live 2026-10-16 (#23710): the top sheet margin, in millimetres, written into `@page`. It was `planned` (verified 2026-10-08) with no reader in either repo. The reader landed in objectui 2312bd9a6 (objectui#11958), and the `.objectui-sha` pin daeb77fc84dd carries it (`git merge-base --is-ancestor 2312bd9a6 daeb77fc84dd` answers yes), so every objectui pointer here was read at the pin this repo builds against. Re-read when the pin moved f96f771140f1 → daeb77fc84dd (2026-10-16): the reader files are byte-identical at both." }, "right": { "status": "live", "verifiedAt": "2026-10-16", - "evidenceScope": "cross-repo", - "evidence": "objectui @daeb77fc84dd: packages/components/src/renderers/layout/page-print.ts#buildPagePrintStylesheet (`pageRule` writes `margin-right: mm` when `margins.right` is a finite non-negative number); objectui @daeb77fc84dd: packages/components/src/renderers/layout/page.tsx#PageRenderer (mounts the sheet only for a page whose `print` is an object)", + "evidenceScope": "in-repo", + "evidence": "ui/packages/components/src/renderers/layout/page-print.ts#buildPagePrintStylesheet (`pageRule` writes `margin-right: mm` when `margins.right` is a finite non-negative number); ui/packages/components/src/renderers/layout/page.tsx#PageRenderer (mounts the sheet only for a page whose `print` is an object)", "note": "FLIPPED planned → live 2026-10-16 (#23710): the right sheet margin, in millimetres, written into `@page`. It was `planned` (verified 2026-10-08) with no reader in either repo. The reader landed in objectui 2312bd9a6 (objectui#11958), and the `.objectui-sha` pin daeb77fc84dd carries it (`git merge-base --is-ancestor 2312bd9a6 daeb77fc84dd` answers yes), so every objectui pointer here was read at the pin this repo builds against. Re-read when the pin moved f96f771140f1 → daeb77fc84dd (2026-10-16): the reader files are byte-identical at both." }, "bottom": { "status": "live", "verifiedAt": "2026-10-16", - "evidenceScope": "cross-repo", - "evidence": "objectui @daeb77fc84dd: packages/components/src/renderers/layout/page-print.ts#buildPagePrintStylesheet (`pageRule` writes `margin-bottom: mm` when `margins.bottom` is a finite non-negative number); objectui @daeb77fc84dd: packages/components/src/renderers/layout/page.tsx#PageRenderer (mounts the sheet only for a page whose `print` is an object)", + "evidenceScope": "in-repo", + "evidence": "ui/packages/components/src/renderers/layout/page-print.ts#buildPagePrintStylesheet (`pageRule` writes `margin-bottom: mm` when `margins.bottom` is a finite non-negative number); ui/packages/components/src/renderers/layout/page.tsx#PageRenderer (mounts the sheet only for a page whose `print` is an object)", "note": "FLIPPED planned → live 2026-10-16 (#23710): the bottom sheet margin, in millimetres, written into `@page`. It was `planned` (verified 2026-10-08) with no reader in either repo. The reader landed in objectui 2312bd9a6 (objectui#11958), and the `.objectui-sha` pin daeb77fc84dd carries it (`git merge-base --is-ancestor 2312bd9a6 daeb77fc84dd` answers yes), so every objectui pointer here was read at the pin this repo builds against. Re-read when the pin moved f96f771140f1 → daeb77fc84dd (2026-10-16): the reader files are byte-identical at both." }, "left": { "status": "live", "verifiedAt": "2026-10-16", - "evidenceScope": "cross-repo", - "evidence": "objectui @daeb77fc84dd: packages/components/src/renderers/layout/page-print.ts#buildPagePrintStylesheet (`pageRule` writes `margin-left: mm` when `margins.left` is a finite non-negative number); objectui @daeb77fc84dd: packages/components/src/renderers/layout/page.tsx#PageRenderer (mounts the sheet only for a page whose `print` is an object)", + "evidenceScope": "in-repo", + "evidence": "ui/packages/components/src/renderers/layout/page-print.ts#buildPagePrintStylesheet (`pageRule` writes `margin-left: mm` when `margins.left` is a finite non-negative number); ui/packages/components/src/renderers/layout/page.tsx#PageRenderer (mounts the sheet only for a page whose `print` is an object)", "note": "FLIPPED planned → live 2026-10-16 (#23710): the left sheet margin, in millimetres, written into `@page`. It was `planned` (verified 2026-10-08) with no reader in either repo. The reader landed in objectui 2312bd9a6 (objectui#11958), and the `.objectui-sha` pin daeb77fc84dd carries it (`git merge-base --is-ancestor 2312bd9a6 daeb77fc84dd` answers yes), so every objectui pointer here was read at the pin this repo builds against. Re-read when the pin moved f96f771140f1 → daeb77fc84dd (2026-10-16): the reader files are byte-identical at both." } } @@ -200,36 +200,36 @@ "repeatHeader": { "status": "live", "verifiedAt": "2026-10-16", - "evidenceScope": "cross-repo", - "evidence": "objectui @daeb77fc84dd: packages/components/src/renderers/layout/page-print.ts#buildPagePrintStylesheet (the page's own `header` region placed in the `thead` of the running-region table, which the sheet's `runningRegionRules` turn back into a table on paper so the browser repeats it on every sheet); objectui @daeb77fc84dd: packages/components/src/renderers/layout/page.tsx#PageRenderer (mounts the sheet only for a page whose `print` is an object); objectui @daeb77fc84dd: packages/components/src/renderers/layout/page.tsx#RunningRegions (draws the running-region table, `display: contents` on screen, only when the flag is `true` and the region exists)", + "evidenceScope": "in-repo", + "evidence": "ui/packages/components/src/renderers/layout/page-print.ts#buildPagePrintStylesheet (the page's own `header` region placed in the `thead` of the running-region table, which the sheet's `runningRegionRules` turn back into a table on paper so the browser repeats it on every sheet); ui/packages/components/src/renderers/layout/page.tsx#PageRenderer (mounts the sheet only for a page whose `print` is an object); ui/packages/components/src/renderers/layout/page.tsx#RunningRegions (draws the running-region table, `display: contents` on screen, only when the flag is `true` and the region exists)", "note": "FLIPPED planned → live 2026-10-16 (#23710). Applied by the console's browser print rendering: the page's own `header` region placed in the `thead` of the running-region table, which the sheet's `runningRegionRules` turn back into a table on paper so the browser repeats it on every sheet. It was `planned` (verified 2026-10-08) because the key was declared spec-first (#22158) with no reader in either repo. The reader landed in objectui 2312bd9a6 (objectui#11958), and the `.objectui-sha` pin daeb77fc84dd carries it (`git merge-base --is-ancestor 2312bd9a6 daeb77fc84dd` answers yes), so every objectui pointer here was read at the pin this repo builds against. Re-read when the pin moved f96f771140f1 → daeb77fc84dd (2026-10-16): the reader files are byte-identical at both." }, "repeatFooter": { "status": "live", "verifiedAt": "2026-10-16", - "evidenceScope": "cross-repo", - "evidence": "objectui @daeb77fc84dd: packages/components/src/renderers/layout/page-print.ts#buildPagePrintStylesheet (the page's own `footer` region placed in the `tfoot` of the running-region table, which the sheet's `runningRegionRules` turn back into a table on paper so the browser repeats it on every sheet); objectui @daeb77fc84dd: packages/components/src/renderers/layout/page.tsx#PageRenderer (mounts the sheet only for a page whose `print` is an object); objectui @daeb77fc84dd: packages/components/src/renderers/layout/page.tsx#RunningRegions (draws the running-region table, `display: contents` on screen, only when the flag is `true` and the region exists)", + "evidenceScope": "in-repo", + "evidence": "ui/packages/components/src/renderers/layout/page-print.ts#buildPagePrintStylesheet (the page's own `footer` region placed in the `tfoot` of the running-region table, which the sheet's `runningRegionRules` turn back into a table on paper so the browser repeats it on every sheet); ui/packages/components/src/renderers/layout/page.tsx#PageRenderer (mounts the sheet only for a page whose `print` is an object); ui/packages/components/src/renderers/layout/page.tsx#RunningRegions (draws the running-region table, `display: contents` on screen, only when the flag is `true` and the region exists)", "note": "FLIPPED planned → live 2026-10-16 (#23710). Applied by the console's browser print rendering: the page's own `footer` region placed in the `tfoot` of the running-region table, which the sheet's `runningRegionRules` turn back into a table on paper so the browser repeats it on every sheet. It was `planned` (verified 2026-10-08) because the key was declared spec-first (#22158) with no reader in either repo. The reader landed in objectui 2312bd9a6 (objectui#11958), and the `.objectui-sha` pin daeb77fc84dd carries it (`git merge-base --is-ancestor 2312bd9a6 daeb77fc84dd` answers yes), so every objectui pointer here was read at the pin this repo builds against. Re-read when the pin moved f96f771140f1 → daeb77fc84dd (2026-10-16): the reader files are byte-identical at both." }, "pageNumbers": { "status": "live", "verifiedAt": "2026-10-16", - "evidenceScope": "cross-repo", - "evidence": "objectui @daeb77fc84dd: packages/components/src/renderers/layout/page-print.ts#buildPagePrintStylesheet (the `@page` bottom-right margin box with `counter(page)` / `counter(pages)`, written by `pageRule` when the key is `true`); objectui @daeb77fc84dd: packages/components/src/renderers/layout/page.tsx#PageRenderer (mounts the sheet only for a page whose `print` is an object)", + "evidenceScope": "in-repo", + "evidence": "ui/packages/components/src/renderers/layout/page-print.ts#buildPagePrintStylesheet (the `@page` bottom-right margin box with `counter(page)` / `counter(pages)`, written by `pageRule` when the key is `true`); ui/packages/components/src/renderers/layout/page.tsx#PageRenderer (mounts the sheet only for a page whose `print` is an object)", "note": "FLIPPED planned → live 2026-10-16 (#23710). Applied by the console's browser print rendering: the `@page` bottom-right margin box with `counter(page)` / `counter(pages)`, written by `pageRule` when the key is `true`. It was `planned` (verified 2026-10-08) because the key was declared spec-first (#22158) with no reader in either repo. The reader landed in objectui 2312bd9a6 (objectui#11958), and the `.objectui-sha` pin daeb77fc84dd carries it (`git merge-base --is-ancestor 2312bd9a6 daeb77fc84dd` answers yes), so every objectui pointer here was read at the pin this repo builds against. Re-read when the pin moved f96f771140f1 → daeb77fc84dd (2026-10-16): the reader files are byte-identical at both." }, "repeatTableHeaders": { "status": "live", "verifiedAt": "2026-10-16", - "evidenceScope": "cross-repo", - "evidence": "objectui @daeb77fc84dd: packages/components/src/renderers/layout/page-print.ts#buildPagePrintStylesheet (`thead { display: table-header-group }` for every table on the page (on when omitted); `false` writes `table-row-group`, which does not repeat); objectui @daeb77fc84dd: packages/components/src/renderers/layout/page.tsx#PageRenderer (mounts the sheet only for a page whose `print` is an object)", + "evidenceScope": "in-repo", + "evidence": "ui/packages/components/src/renderers/layout/page-print.ts#buildPagePrintStylesheet (`thead { display: table-header-group }` for every table on the page (on when omitted); `false` writes `table-row-group`, which does not repeat); ui/packages/components/src/renderers/layout/page.tsx#PageRenderer (mounts the sheet only for a page whose `print` is an object)", "note": "FLIPPED planned → live 2026-10-16 (#23710). Applied by the console's browser print rendering: `thead { display: table-header-group }` for every table on the page (on when omitted); `false` writes `table-row-group`, which does not repeat. It was `planned` (verified 2026-10-08) because the key was declared spec-first (#22158) with no reader in either repo. The reader landed in objectui 2312bd9a6 (objectui#11958), and the `.objectui-sha` pin daeb77fc84dd carries it (`git merge-base --is-ancestor 2312bd9a6 daeb77fc84dd` answers yes), so every objectui pointer here was read at the pin this repo builds against. Re-read when the pin moved f96f771140f1 → daeb77fc84dd (2026-10-16): the reader files are byte-identical at both." }, "avoidBreakInside": { "status": "live", "verifiedAt": "2026-10-16", - "evidenceScope": "cross-repo", - "evidence": "objectui @daeb77fc84dd: packages/components/src/renderers/layout/page-print.ts#buildPagePrintStylesheet (`break-inside: avoid` on every block of a region, written when the key is `true`); objectui @daeb77fc84dd: packages/components/src/renderers/layout/page.tsx#PageRenderer (mounts the sheet only for a page whose `print` is an object)", + "evidenceScope": "in-repo", + "evidence": "ui/packages/components/src/renderers/layout/page-print.ts#buildPagePrintStylesheet (`break-inside: avoid` on every block of a region, written when the key is `true`); ui/packages/components/src/renderers/layout/page.tsx#PageRenderer (mounts the sheet only for a page whose `print` is an object)", "note": "FLIPPED planned → live 2026-10-16 (#23710). Applied by the console's browser print rendering: `break-inside: avoid` on every block of a region, written when the key is `true`. It was `planned` (verified 2026-10-08) because the key was declared spec-first (#22158) with no reader in either repo. The reader landed in objectui 2312bd9a6 (objectui#11958), and the `.objectui-sha` pin daeb77fc84dd carries it (`git merge-base --is-ancestor 2312bd9a6 daeb77fc84dd` answers yes), so every objectui pointer here was read at the pin this repo builds against. Re-read when the pin moved f96f771140f1 → daeb77fc84dd (2026-10-16): the reader files are byte-identical at both." } } diff --git a/packages/spec/liveness/permission.json b/packages/spec/liveness/permission.json index dfcca2299e3..90c94968c79 100644 --- a/packages/spec/liveness/permission.json +++ b/packages/spec/liveness/permission.json @@ -11,15 +11,15 @@ "label": { "status": "live", "verifiedAt": "2026-10-14", - "evidenceScope": "cross-repo", - "evidence": "objectui @b9814b334d8a, the .objectui-sha pin and unchanged at objectui main @524136ea7: packages/app-shell/src/services/builtinComponents.tsx:206 registers `{ key: 'label', label: 'Label' }` as a list column of the `permission` metadata resource, and packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx:698 renders it as `row.item[c.key]` for every set Studio's permission-set list reads through `client.list('permission')`, which is GET /api/v1/meta/permission; framework: packages/metadata-protocol/src/protocol.ts#getMetaItems serves the authored body unprojected, so the authored label reaches that column.", + "evidenceScope": "in-repo", + "evidence": "objectui @b9814b334d8a, the .objectui-sha pin and unchanged at ui/packages/app-shell/src/services/builtinComponents.tsx (:206 at objectui @524136ea7) registers `{ key: 'label', label: 'Label' }` as a list column of the `permission` metadata resource, and ui/packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx:902 renders it as `row.item[c.key]` for every set Studio's permission-set list reads through `client.list('permission')`, which is GET /api/v1/meta/permission; framework: packages/metadata-protocol/src/protocol.ts#getMetaItems serves the authored body unprojected, so the authored label reaches that column.", "note": "display metadata. RE-VERIFIED 2026-10-14 (#23404): the previous evidence named the record title of the sys_permission_set row object in Setup's generic object views, and that object was retired at C3 stage 8; the Studio list column is the reader that remains." }, "description": { "status": "live", "verifiedAt": "2026-10-14", - "evidenceScope": "cross-repo", - "evidence": "objectui @b9814b334d8a, the .objectui-sha pin and unchanged at objectui main @524136ea7: packages/app-shell/src/services/builtinComponents.tsx:207 registers `{ key: 'description', label: 'Description' }` as a list column of the `permission` metadata resource, and packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx:698 renders it as `row.item[c.key]` for every set Studio's permission-set list reads through `client.list('permission')`, which is GET /api/v1/meta/permission; framework: packages/metadata-protocol/src/protocol.ts#getMetaItems serves the authored body unprojected, so the authored description reaches that column.", + "evidenceScope": "in-repo", + "evidence": "objectui @b9814b334d8a, the .objectui-sha pin and unchanged at ui/packages/app-shell/src/services/builtinComponents.tsx (:207 at objectui @524136ea7) registers `{ key: 'description', label: 'Description' }` as a list column of the `permission` metadata resource, and ui/packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx:902 renders it as `row.item[c.key]` for every set Studio's permission-set list reads through `client.list('permission')`, which is GET /api/v1/meta/permission; framework: packages/metadata-protocol/src/protocol.ts#getMetaItems serves the authored body unprojected, so the authored description reaches that column.", "note": "Declared in #4001: the built-in default sets always authored it, but the schema could not represent it, so it was silently stripped at parse (ADR-0078 §3 inverse drift) — the .strict() gate surfaced the gap. RE-VERIFIED 2026-10-14 (#23404): both readers the previous evidence named are deleted — the sys_permission_set row object (C3 stage 8) and the permission-set projection (C3 stage 7a). No framework-side reader of the authored key remains: packages/core/src/security/permission-set-row-definition.ts maps a legacy row INTO the key for the upgrade ceremony (a producer, not a reader), and the security catalog never reads it. Display-only, so the Studio list column is the whole of its claimed effect (README, the display/behaviour table)." }, "packageId": { @@ -177,17 +177,17 @@ "label": { "status": "live", "verifiedAt": "2026-10-03", - "evidenceScope": "cross-repo", - "evidence": "objectui @89cad75d557: packages/app-shell/src/views/metadata-admin/PermissionAdvancedFacets.tsx#PermissionAdvancedFacets (the permission editor's Row-Level Security section heads each policy card with `pol.label`, verbatim: a plain spec string with no locale lookup, and a policy whose label is absent or empty draws nothing; the section opens collapsed, showing the policy count, until the author expands it)", - "producer": "objectui @89cad75d557: packages/app-shell/src/services/builtinComponents.tsx (registers `PermissionMatrixEditPage` as the `permission` EditPage; the later `anchors.ts` registration sets only `anchors`, and `registerMetadataResource` merges defined keys only, so that EditPage stands); objectui @89cad75d557: packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx#MetadataResourceEditPage (the element of both `metadata/:type/:name` routes in `console/AppContent.tsx`; it hands every non-create `permission` item to that EditPage); objectui @89cad75d557: packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx#PermissionMatrixEditPage (reads the set through `client.layered`, the `GET /meta/permission/:name/layers` route, and mounts `PermissionAdvancedFacets` on the whole served draft, `rowLevelSecurity` included; the Studio Access pillar in `studio-design/StudioDesignSurface.tsx` mounts the same page embedded); framework: packages/rest/src/meta-item-read-gate.ts#createMetaLayeredAnswer (serves every layer of a `permission` whole: `createMetaItemReadGate` has no per-type arm for it, and the ADR-0106 object-schema mask does not apply to a non-object type); framework: examples/app-showcase/src/security/permission-sets.ts#ContributorPermissionSet (an authored producer: each of its three policies declares a `label` and a `description`)", + "evidenceScope": "in-repo", + "evidence": "ui/packages/app-shell/src/views/metadata-admin/PermissionAdvancedFacets.tsx#PermissionAdvancedFacets (the permission editor's Row-Level Security section heads each policy card with `pol.label`, verbatim: a plain spec string with no locale lookup, and a policy whose label is absent or empty draws nothing; the section opens collapsed, showing the policy count, until the author expands it)", + "producer": "ui/packages/app-shell/src/services/builtinComponents.tsx (registers `PermissionMatrixEditPage` as the `permission` EditPage; the later `anchors.ts` registration sets only `anchors`, and `registerMetadataResource` merges defined keys only, so that EditPage stands); ui/packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx#MetadataResourceEditPage (the element of both `metadata/:type/:name` routes in `console/AppContent.tsx`; it hands every non-create `permission` item to that EditPage); ui/packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx#PermissionMatrixEditPage (reads the set through `client.layered`, the `GET /meta/permission/:name/layers` route, and mounts `PermissionAdvancedFacets` on the whole served draft, `rowLevelSecurity` included; the Studio Access pillar in `studio-design/StudioDesignSurface.tsx` mounts the same page embedded); framework: packages/rest/src/meta-item-read-gate.ts#createMetaLayeredAnswer (serves every layer of a `permission` whole: `createMetaItemReadGate` has no per-type arm for it, and the ADR-0106 object-schema mask does not apply to a non-object type); framework: examples/app-showcase/src/security/permission-sets.ts#ContributorPermissionSet (an authored producer: each of its three policies declares a `label` and a `description`)", "note": "RE-GRADED dead → live 2026-10-03 (#20299) under the #7131 previews ruling (README, 'Designer previews count as consumers'): for a display key, being shown to a human is the whole claimed effect, so the editor surface that draws it settles the row. No preview is cited, because `PermissionPreview`, which also draws it, is still mounted by no route for `permission`. objectui#11199, fixed by PR objectui#11215 (merge f8334f8777, an ancestor of the `.objectui-sha` pin 89cad75d557), added the caption. Read at that pin with `git show`. Control: at the superseded pin db11afd4967 the same file reads `pol.label` 0 times and `pol.description` 0 times, against `pol.name` once in both trees, which is the reachability gap the superseded note recorded (a preview that draws both, mounted nowhere). objectui#11215's booted probe on the showcase set `showcase_contributor` found both values on `/apps/studio/metadata/permission/:name`, `/apps/setup/metadata/permission/:name` and the Studio Access pillar; this re-grade is a static read and did not re-boot. UNCHANGED: benign display metadata, deliberately KEPT (ADR-0033) and not authorWarn'd. The flip ends the `liveness-dead-property` warning (#16094) an authored policy label drew." }, "description": { "status": "live", "verifiedAt": "2026-10-03", - "evidenceScope": "cross-repo", - "evidence": "objectui @89cad75d557: packages/app-shell/src/views/metadata-admin/PermissionAdvancedFacets.tsx#PermissionAdvancedFacets (draws `pol.description` beneath the policy's label in each Row-Level Security policy card, verbatim; an absent or empty description draws nothing)", - "producer": "objectui @89cad75d557: packages/app-shell/src/services/builtinComponents.tsx (registers `PermissionMatrixEditPage` as the `permission` EditPage; the later `anchors.ts` registration sets only `anchors`, and `registerMetadataResource` merges defined keys only, so that EditPage stands); objectui @89cad75d557: packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx#MetadataResourceEditPage (the element of both `metadata/:type/:name` routes in `console/AppContent.tsx`; it hands every non-create `permission` item to that EditPage); objectui @89cad75d557: packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx#PermissionMatrixEditPage (reads the set through `client.layered`, the `GET /meta/permission/:name/layers` route, and mounts `PermissionAdvancedFacets` on the whole served draft, `rowLevelSecurity` included; the Studio Access pillar in `studio-design/StudioDesignSurface.tsx` mounts the same page embedded); framework: packages/rest/src/meta-item-read-gate.ts#createMetaLayeredAnswer (serves every layer of a `permission` whole: `createMetaItemReadGate` has no per-type arm for it, and the ADR-0106 object-schema mask does not apply to a non-object type); framework: examples/app-showcase/src/security/permission-sets.ts#ContributorPermissionSet (an authored producer: each of its three policies declares a `label` and a `description`)", + "evidenceScope": "in-repo", + "evidence": "ui/packages/app-shell/src/views/metadata-admin/PermissionAdvancedFacets.tsx#PermissionAdvancedFacets (draws `pol.description` beneath the policy's label in each Row-Level Security policy card, verbatim; an absent or empty description draws nothing)", + "producer": "ui/packages/app-shell/src/services/builtinComponents.tsx (registers `PermissionMatrixEditPage` as the `permission` EditPage; the later `anchors.ts` registration sets only `anchors`, and `registerMetadataResource` merges defined keys only, so that EditPage stands); ui/packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx#MetadataResourceEditPage (the element of both `metadata/:type/:name` routes in `console/AppContent.tsx`; it hands every non-create `permission` item to that EditPage); ui/packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx#PermissionMatrixEditPage (reads the set through `client.layered`, the `GET /meta/permission/:name/layers` route, and mounts `PermissionAdvancedFacets` on the whole served draft, `rowLevelSecurity` included; the Studio Access pillar in `studio-design/StudioDesignSurface.tsx` mounts the same page embedded); framework: packages/rest/src/meta-item-read-gate.ts#createMetaLayeredAnswer (serves every layer of a `permission` whole: `createMetaItemReadGate` has no per-type arm for it, and the ADR-0106 object-schema mask does not apply to a non-object type); framework: examples/app-showcase/src/security/permission-sets.ts#ContributorPermissionSet (an authored producer: each of its three policies declares a `label` and a `description`)", "note": "RE-GRADED dead → live 2026-10-03 (#20299) with `label`, on the same measurement at the `.objectui-sha` pin 89cad75d557 (objectui#11199 via PR objectui#11215). Control at the superseded pin db11afd4967: `pol.description` 0 reads in the same file. The superseded note recorded that `PermissionPreview` drew it but was mounted nowhere, and that is still true; the facet is the reader. UNCHANGED: benign display metadata, KEPT (ADR-0033), not authorWarn'd." }, "object": { diff --git a/packages/spec/liveness/sharing_rule.json b/packages/spec/liveness/sharing_rule.json index 373815465b0..54cd68d0037 100644 --- a/packages/spec/liveness/sharing_rule.json +++ b/packages/spec/liveness/sharing_rule.json @@ -12,8 +12,8 @@ "label": { "status": "live", "verifiedAt": "2026-09-17", - "evidenceScope": "cross-repo", - "evidence": "packages/plugins/plugin-sharing/src/objects/sys-sharing-rule.object.ts#SysSharingRule — `titleFormat: '{label}'` makes the authored label the record title of every `sys_sharing_rule` row, and `label` is a column of all four declared listViews (`active`, `inactive`, `by_object`, `all_rules`); objectui @dda8f381 packages/components/src/renderers/layout/containers.tsx interpolates `objectSchema.titleFormat` for the rendered title, so the authored string is what an admin reads in Setup.", + "evidenceScope": "in-repo", + "evidence": "packages/plugins/plugin-sharing/src/objects/sys-sharing-rule.object.ts#SysSharingRule — `titleFormat: '{label}'` makes the authored label the record title of every `sys_sharing_rule` row, and `label` is a column of all four declared listViews (`active`, `inactive`, `by_object`, `all_rules`); ui/packages/components/src/renderers/layout/containers.tsx interpolates `objectSchema.titleFormat` for the rendered title, so the authored string is what an admin reads in Setup.", "producer": "packages/plugins/plugin-sharing/src/declared-sharing-rules.ts#translateDeclaredSharingRule — `label: String(r.label ?? r.name)` threads the authored label into the served rule, which the sharing-rule service lists as the rule's `label`; without that thread the rule NAME would stand for every declared rule, which is exactly what an unauthored `label` still produces.", "note": "Display-shaped, so the #7131 split settles it: being shown to a human IS the whole of the claimed effect, and there is no second layer where a \"real\" consumer would live. The read point is the platform object's own record surface, NOT a metadata-admin preview — this type has none (enumerated in the file note above)." }, diff --git a/packages/spec/liveness/tool.json b/packages/spec/liveness/tool.json index e986b26346a..5d6579c1ee3 100644 --- a/packages/spec/liveness/tool.json +++ b/packages/spec/liveness/tool.json @@ -14,7 +14,7 @@ "status": "live", "verifiedAt": "2026-08-29", "evidenceScope": "cross-repo", - "evidence": "cloud @15f55df: packages/service-ai-studio/src/plugin.ts#toToolLabel — the boot-time Studio ingest inside AIStudioPlugin.init reads the label off each AIToolDefinition, falls back to a name-derived one when unset, and persists the result as the tool metadata record; that call is the ONLY site in the whole cloud repo that registers tool metadata. objectui @26896c6: packages/app-shell/src/views/metadata-admin/previews/ToolPreview.tsx#ToolPreview then reads that persisted label back and renders it as the preview header, with the tool's machine name as fallback", + "evidence": "cloud @15f55df: packages/service-ai-studio/src/plugin.ts#toToolLabel — the boot-time Studio ingest inside AIStudioPlugin.init reads the label off each AIToolDefinition, falls back to a name-derived one when unset, and persists the result as the tool metadata record; that call is the ONLY site in the whole cloud repo that registers tool metadata. ui/packages/app-shell/src/views/metadata-admin/previews/ToolPreview.tsx#ToolPreview then reads that persisted label back and renders it as the preview header, with the tool's machine name as fallback", "producer": "cloud @15f55df: packages/service-ai/src/tools/data-tools.ts and packages/service-ai-studio/src/tools/apply-edit.tool.ts are representative of the shipped definition literals that set it. Cited for REACHABILITY rather than for a missing supplier — the read point runs unconditionally at plugin boot, and its toToolLabel fallback means even an unset value still changes what is persisted", "note": "display, and it IS displayed — the #7131 display-key rule, met at the projection rather than at a preview. RE-CLOSED 2026-08-29 (cloud @15f55df) and given its FIRST evidence pointer: this entry had carried a bare `live` with no `evidence` field at all since seeding, which is why it was the one row the census that prompted this re-close (commit c19035e97) could not even call stale — a claim with nothing to rot. Both sides of the round trip are now cited: cloud reads the label off the AIToolDefinition to compose the persisted record, and objectui's registered metadata-admin preview reads that record back and renders it. (An earlier pass of this re-closure recorded the read-back as un-walked for want of an objectui checkout; the walk has since been taken, and this row no longer carries that hedge.)" }, @@ -36,9 +36,9 @@ "objectName": { "status": "live", "verifiedAt": "2026-08-29", - "evidenceScope": "cross-repo", - "evidence": "objectui @26896c6: packages/app-shell/src/views/metadata-admin/previews/ToolPreview.tsx#ToolPreview takes objectName off the persisted tool record and renders it as the header's object pill, and drops that pill row entirely when the key is absent", - "producer": "objectui @26896c6: packages/app-shell/src/views/metadata-admin/previews/index.ts#registerMetadataPreview binds ToolPreview to the `tool` type and packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx#getMetadataPreview resolves that registration and hands the component the draft being edited — the read point is reachable, not merely present. The author IS the supplier of the value itself", + "evidenceScope": "in-repo", + "evidence": "ui/packages/app-shell/src/views/metadata-admin/previews/ToolPreview.tsx#ToolPreview takes objectName off the persisted tool record and renders it as the header's object pill, and drops that pill row entirely when the key is absent", + "producer": "ui/packages/app-shell/src/views/metadata-admin/previews/index.ts#registerMetadataPreview binds ToolPreview to the `tool` type and ui/packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx#getMetadataPreview resolves that registration and hands the component the draft being edited — the read point is reachable, not merely present. The author IS the supplier of the value itself", "note": "⚠ RE-CLOSED 2026-08-29, and the verdict SURVIVES for a reason that is NOT the one this row used to give — read both halves, because each is load-bearing. (1) The old basis is FALSIFIED. The row claimed 'action-tool dispatch' citing cloud action-tools.ts. Both sites there read `action.objectName`, the ActionSchema key, whose own row in action.json carries that very citation word for word; the same-named `AIToolDefinition.objectName` is WRITTEN by actionToToolDefinition and read by nothing — zero readers in cloud @15f55df, zero in this repo — and it never reaches persisted metadata either, since cloud's single `metadataService.register` tool site ingests only the shipped Studio definitions, none action-backed. So this key gates, binds and routes NOTHING; anyone reading the old note as a dispatch guarantee was misled. (2) A real consumer exists anyway, on the OTHER basis: objectui's registered metadata-admin preview reads it off the persisted record and renders it. That is the #7131 display-key rule — the render is not standing in for a behaviour, because for authored tool metadata there is no execution layer for it to stand in for: ToolSchema's own describe says a metadata-authored tool is a read-only projection that no executor loads. objectui pins this deliberately — ToolPreview.test.tsx asserts the pill renders and disappears with the key, over a comment reading 'objectName is NOT residue — ToolSchema still accepts it'. ⇒ `live` as a DISPLAY key, never as a binding. ⛔ Do NOT read this row as licence to retire the key under ADR-0049: a `dead` verdict here would retire a key the renderer deliberately renders and tests, the `requiresService` / `colorVariant` mistake from the other end. The prior pass left this undated rather than grading `dead` precisely because it could not see objectui; that caution was correct — the renderer is where the answer was." }, "outputSchema": { diff --git a/packages/spec/liveness/translation.json b/packages/spec/liveness/translation.json index 60d2ac78699..0c2ea7b8c3f 100644 --- a/packages/spec/liveness/translation.json +++ b/packages/spec/liveness/translation.json @@ -5,17 +5,17 @@ "name": { "status": "live", "verifiedAt": "2026-08-10", - "evidenceScope": "cross-repo", - "evidence": "objectui @aeb8424b: packages/app-shell/src/views/metadata-admin/previews/TranslationPreview.tsx:67 falls back to the draft's BODY `name` when `label` is unset, and packages/app-shell/src/views/metadata-admin/previews/TranslationPreview.tsx:100 renders the result as the preview card's title", - "producer": "objectui @aeb8424b: packages/app-shell/src/views/metadata-admin/previews/index.ts:50 registers TranslationPreview against the `translation` metadata type, and packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx:949 resolves that registration and hands the component the draft being edited", + "evidenceScope": "in-repo", + "evidence": "ui/packages/app-shell/src/views/metadata-admin/previews/TranslationPreview.tsx:117 falls back to the draft's BODY `name` when `label` is unset, and ui/packages/app-shell/src/views/metadata-admin/previews/TranslationPreview.tsx:150 renders the result as the preview card's title", + "producer": "ui/packages/app-shell/src/views/metadata-admin/previews/index.ts:61 registers TranslationPreview against the `translation` metadata type, and ui/packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx (:949 at objectui @aeb8424b) resolves that registration and hands the component the draft being edited", "note": "Item identity, declared in #4001. RE-GRADED dead → live 2026-08-10 (#7131), and the re-grade supersedes exactly ONE clause of the old note: 'dead as a BODY key — the honest reading of a copy nobody reads'. The body copy has a measured reader — objectui's metadata-admin translation preview uses it as the display fallback when `label` is unset — so under the maintainer ruling that a designer preview rendering a key to a human is a runtime consumer (see README, 'Designer previews count as consumers') the copy is read. THE REST OF THIS ROW STANDS UNCHANGED AND IS THE SUBSTANCE, including its door/row-column analysis: the row's `name` column is the live one on the SYNC path, and the body copy is still not what the sync reads — the preview reads it, the sync does not. The metadata door takes it from the URL (`saveMetaItem({ name })`, rest-server.ts:3903), and `authored-translation-sync` reads `row.name` as the locale fallback for pre-#3778 rows (:140-148) while explicitly destructuring the body copy back out before merging (:154-159). Declared anyway, and deliberately not authorWarn'd, for two reasons: the platform's own create seed sends it (`metadata-create-seeds.ts`, whose stated contract is that EVERY seed carries placeholder identity), so a warning would fire on our own shape; and `translation` being the one registered type of 25 without a `name` is exactly the irregularity an AI author trips on. Undeclared it was silently stripped, which is how the seed gate — the canonical 'designer create shape ≠ spec' guard — passed for years while two thirds of the seed it validated was being thrown away." }, "label": { "status": "live", "verifiedAt": "2026-08-10", - "evidenceScope": "cross-repo", - "evidence": "objectui @aeb8424b: packages/app-shell/src/views/metadata-admin/previews/TranslationPreview.tsx:67 takes `label` off the translation draft as the first choice of the display chain, and packages/app-shell/src/views/metadata-admin/previews/TranslationPreview.tsx:100 renders the result as the preview card's title", - "producer": "objectui @aeb8424b: packages/app-shell/src/views/metadata-admin/previews/index.ts:50 registers TranslationPreview against the `translation` metadata type, and packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx:949 resolves that registration and hands the component the draft being edited", + "evidenceScope": "in-repo", + "evidence": "ui/packages/app-shell/src/views/metadata-admin/previews/TranslationPreview.tsx:117 takes `label` off the translation draft as the first choice of the display chain, and ui/packages/app-shell/src/views/metadata-admin/previews/TranslationPreview.tsx:150 renders the result as the preview card's title", + "producer": "ui/packages/app-shell/src/views/metadata-admin/previews/index.ts:61 registers TranslationPreview against the `translation` metadata type, and ui/packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx (:949 at objectui @aeb8424b) resolves that registration and hands the component the draft being edited", "note": "Display metadata — and the display exists, one repo over: the metadata-admin translation preview renders it as the item's title. RE-GRADED dead → live 2026-08-10 (#7131) under the maintainer ruling that a designer preview rendering a key to a human is a runtime consumer — see README, 'Designer previews count as consumers'. Read the superseded wording carefully: 'no runtime consumer IN THIS REPO' was literally accurate and remains so — what changed is that the cross-repo look was finally taken, which is exactly the blind spot `evidenceScope` exists to make visible (#4895). UNCHANGED: not authorWarn'd — the README's rule is that pure display annotation (`description`, `tags`, `icon`) must not warn, and the `job.label` precedent is exactly this — docs-shaped, deliberately kept, ADR-0033 exempt from enforce-or-remove. Shipped with `name` in the create seed for the same reason." }, "locale": { @@ -27,7 +27,7 @@ "objects": { "status": "live", "verifiedAt": "2026-08-28", - "evidence": "packages/spec/src/system/i18n-resolver.ts#lookupObjectField (`objects..label` / `pluralLabel` / `description`); packages/spec/src/system/i18n-resolver.ts#lookupObjectFieldAttr (`objects..fields..`); packages/spec/src/system/i18n-resolver.ts#resolveViewLabel (`objects.._views..label`); packages/spec/src/system/i18n-resolver.ts#actionTranslationNode (`objects.._actions`, the one address of a bound action — never followed by globalActions); packages/spec/src/system/i18n-resolver.ts#lookupTabLabel (`objects.._tabs`); objectui @940ba24: packages/i18n/src/useObjectLabel.ts:397-400", + "evidence": "packages/spec/src/system/i18n-resolver.ts#lookupObjectField (`objects..label` / `pluralLabel` / `description`); packages/spec/src/system/i18n-resolver.ts#lookupObjectFieldAttr (`objects..fields..`); packages/spec/src/system/i18n-resolver.ts#resolveViewLabel (`objects.._views..label`); packages/spec/src/system/i18n-resolver.ts#actionTranslationNode (`objects.._actions`, the one address of a bound action — never followed by globalActions); packages/spec/src/system/i18n-resolver.ts#lookupTabLabel (`objects.._tabs`); ui/packages/i18n/src/useObjectLabel.ts (:397-400 at objectui @940ba24)", "note": "the largest group, fully live: label/pluralLabel/description (translateObject), fields.{label,help,placeholder,options}, _views (resolveViewLabel + empty-state copy), _actions (label/description/confirmText/successMessage/outcomeMessages/params/resultDialog — a bound action's only address; it never falls back to globalActions), _sections (objectui record:details section labels). Served through REST translateMetaItem(s) and the /api/v1/i18n endpoints; objectui re-resolves client-side via the spec-translations transform. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — this entry named SIX positions in i18n-resolver.ts and only the first, `:735`, was parsable as a citation at all; it had rotted onto `widgets?: WidgetLike[]` in the `DashboardLike` INTERFACE. The other five (`:751`, `:159`, `:197`, `:873`, `:900`) were bare line suffixes with no path in front of them, so they were never resolved, bounded or key-checked by anything — the largest instance in this batch of the class `flow.status` and `dashboard.widgets.suppressWarnings` also carried. The five anchors above are the five distinct group readers, named. Re-closed by hand against 8cb96ec41." }, "picklists": { @@ -135,39 +135,39 @@ "flows": { "status": "live", "verifiedAt": "2026-10-05", - "evidenceScope": "cross-repo", - "evidence": "packages/spec/src/system/i18n-resolver.ts#translateFlow (`flows..label`); packages/spec/src/system/i18n-resolver.ts#resolveFlowScreenTitle (`flows..screens..title`); objectui @0abd4f9f8: packages/app-shell/src/views/FlowRunner.tsx#displayFlowLabel (the flow's name in the runner header and the completion toast); objectui @0abd4f9f8: packages/app-shell/src/views/FlowRunner.tsx#localizeScreen (each screen's heading and per-field copy)", + "evidenceScope": "in-repo", + "evidence": "packages/spec/src/system/i18n-resolver.ts#translateFlow (`flows..label`); packages/spec/src/system/i18n-resolver.ts#resolveFlowScreenTitle (`flows..screens..title`); ui/packages/app-shell/src/views/FlowRunner.tsx#displayFlowLabel (the flow's name in the runner header and the completion toast); ui/packages/app-shell/src/views/FlowRunner.tsx#localizeScreen (each screen's heading and per-field copy)", "children": { "label": { "status": "live", "verifiedAt": "2026-10-05", - "evidenceScope": "cross-repo", - "evidence": "packages/spec/src/system/i18n-resolver.ts#translateFlow (reads `flows..label` through `lookupFlowLabel` and keeps the label it is handed when the bundle has none); objectui @0abd4f9f8: packages/app-shell/src/views/FlowRunner.tsx#displayFlowLabel (`translateFlow` handed the served `flowLabel` as the authored label, then `flowName` when that is empty: the active language's `flows..label`, then the served label, then the API name); objectui @0abd4f9f8: packages/app-shell/src/views/FlowRunner.tsx#FlowRunner (draws that name in the dialog header above the step's own heading, and hands it as `flow` to the `flowRunner.completed` completion toast, which shows when the flow authored no `successMessage`); objectui @0abd4f9f8: packages/app-shell/src/views/FlowRunner.tsx#activeFlowsBundle (the active language's `flows` group out of the i18next `translation` resource tree, the same read the `screens` overlay uses)", - "producer": "packages/services/service-automation/src/engine.ts#AutomationEngine (copies the flow definition's authored `label` onto every run result as `flowLabel`, the string the translation overlays; declared as `AutomationResult.flowLabel` in packages/spec/src/contracts/automation-service.ts); objectui @0abd4f9f8: packages/app-shell/src/utils/flowResponse.ts#interpretFlowResponse (lifts the served `flowLabel` off the trigger and resume 200 bodies); objectui @0abd4f9f8: packages/app-shell/src/hooks/useConsoleActionRuntime.tsx#useConsoleActionRuntime, packages/app-shell/src/views/RecordDetailView.tsx#RecordDetailView and apps/console/src/pages/developer/FlowRunsPage.tsx#FlowRunsPage (the three hosts open the runner with that `flowLabel`); packages/runtime/src/app-plugin.ts#loadTranslations (hands each bundle's locale data to the i18n service whole, `flows` included); packages/runtime/src/domains/i18n.ts#handleI18nRequest (the translations route answers with no group filter); objectui @0abd4f9f8: apps/console/src/loadLanguage.ts#loadLanguage, packages/i18n/src/utils/spec-translations.ts#transformSpecTranslations and packages/i18n/src/provider.tsx#I18nProvider (the delivery chain the `screens` row cites, into the resource bundle `activeFlowsBundle` reads)", + "evidenceScope": "in-repo", + "evidence": "packages/spec/src/system/i18n-resolver.ts#translateFlow (reads `flows..label` through `lookupFlowLabel` and keeps the label it is handed when the bundle has none); ui/packages/app-shell/src/views/FlowRunner.tsx#displayFlowLabel (`translateFlow` handed the served `flowLabel` as the authored label, then `flowName` when that is empty: the active language's `flows..label`, then the served label, then the API name); ui/packages/app-shell/src/views/FlowRunner.tsx#FlowRunner (draws that name in the dialog header above the step's own heading, and hands it as `flow` to the `flowRunner.completed` completion toast, which shows when the flow authored no `successMessage`); ui/packages/app-shell/src/views/FlowRunner.tsx#activeFlowsBundle (the active language's `flows` group out of the i18next `translation` resource tree, the same read the `screens` overlay uses)", + "producer": "packages/services/service-automation/src/engine.ts#AutomationEngine (copies the flow definition's authored `label` onto every run result as `flowLabel`, the string the translation overlays; declared as `AutomationResult.flowLabel` in packages/spec/src/contracts/automation-service.ts); ui/packages/app-shell/src/utils/flowResponse.ts#interpretFlowResponse (lifts the served `flowLabel` off the trigger and resume 200 bodies); ui/packages/app-shell/src/hooks/useConsoleActionRuntime.tsx#useConsoleActionRuntime, ui/packages/app-shell/src/views/RecordDetailView.tsx#RecordDetailView and ui/apps/console/src/pages/developer/FlowRunsPage.tsx#FlowRunsPage (the three hosts open the runner with that `flowLabel`); packages/runtime/src/app-plugin.ts#loadTranslations (hands each bundle's locale data to the i18n service whole, `flows` included); packages/runtime/src/domains/i18n.ts#handleI18nRequest (the translations route answers with no group filter); ui/apps/console/src/loadLanguage.ts#loadLanguage, ui/packages/i18n/src/utils/spec-translations.ts#transformSpecTranslations and ui/packages/i18n/src/provider.tsx#I18nProvider (the delivery chain the `screens` row cites, into the resource bundle `activeFlowsBundle` reads)", "note": "Overlays `Flow.label`. FLIPPED planned → live 2026-10-05. The reader landed client-side in objectui 39a3e91fa, and the `.objectui-sha` pin 0abd4f9f8 carries it (`git merge-base --is-ancestor 39a3e91fad 0abd4f9f8769` answers yes), so every objectui pointer above was read at the pin this repo builds against. The name a user reads for the flow resolves as the active language's `flows..label`, then the authored label the engine serves as `AutomationResult.flowLabel` (landed in commit 5363e2dc64), then the API name. It is drawn in the runner's dialog header, above the step heading, and in the completion toast in place of the API name; an authored `successMessage` still wins the toast, and has carried its own key since 2026-10-09 (#22507; the `successMessage` row). The read goes through the spec's own `translateFlow`, from the same bundle the screen copy is read from, so the address is the spec's and there is no second loader. It was `planned` (verified 2026-08-11) and declared per key rather than inherited, because the two halves resolve through different consumers and could go `live` on different days. They did: `screens` on 2026-09-27, `label` here. `FlowRunner` reading `label` through `translateFlow` for its header and completion toast (falling back to `AutomationResult.flowLabel`, then the API name) and each field's copy over `FLOW_SCREEN_FIELD_COPY_KEYS` was measured at objectui @0abd4f9f8 (citations moved out of packages/spec/src/system/i18n-resolver.ts and translation.zod.ts, #23434)." }, "successMessage": { "status": "live", "verifiedAt": "2026-10-16", - "evidenceScope": "cross-repo", - "evidence": "packages/spec/src/system/i18n-resolver.ts#translateFlow (overlays `flows..successMessage` down the locale chain, only where the flow authors a `successMessage`); packages/spec/src/system/i18n-resolver.ts#FLOW_TERMINAL_MESSAGE_KEYS (the key list the CLI skeleton writer and `translation.test.ts` share); packages/lint/src/validate-translation-references.ts#validateTranslationReferences (refuses a translated `successMessage` over a flow that declares none); objectui @daeb77fc84dd: packages/app-shell/src/utils/flowResponse.ts#translatedFlowMessage (asks the spec's `translateFlow` for `flows..successMessage` under the run's flow name and locale, and keeps the served string when the bundle has none); objectui @daeb77fc84dd: packages/app-shell/src/utils/flowResponse.ts#interpretFlowResponse (the terminal-success arm hands the served `successMessage` to `translatedFlowMessage`); objectui @daeb77fc84dd: packages/app-shell/src/views/FlowRunner.tsx#FlowRunner (hands `interpretFlowResponse` this run's flow name and bundle, so the resume and both launch hosts through `judgeFlowLaunch` answer the same words)", + "evidenceScope": "in-repo", + "evidence": "packages/spec/src/system/i18n-resolver.ts#translateFlow (overlays `flows..successMessage` down the locale chain, only where the flow authors a `successMessage`); packages/spec/src/system/i18n-resolver.ts#FLOW_TERMINAL_MESSAGE_KEYS (the key list the CLI skeleton writer and `translation.test.ts` share); packages/lint/src/validate-translation-references.ts#validateTranslationReferences (refuses a translated `successMessage` over a flow that declares none); ui/packages/app-shell/src/utils/flowResponse.ts#translatedFlowMessage (asks the spec's `translateFlow` for `flows..successMessage` under the run's flow name and locale, and keeps the served string when the bundle has none); ui/packages/app-shell/src/utils/flowResponse.ts#interpretFlowResponse (the terminal-success arm hands the served `successMessage` to `translatedFlowMessage`); ui/packages/app-shell/src/views/FlowRunner.tsx#FlowRunner (hands `interpretFlowResponse` this run's flow name and bundle, so the resume and both launch hosts through `judgeFlowLaunch` answer the same words)", "producer": "packages/spec/src/automation/flow.zod.ts#FlowSchema (`successMessage`, the flow's own completion text); packages/services/service-automation/src/engine.ts#AutomationEngine (copies `flow.successMessage` verbatim onto the terminal result, declared as `AutomationResult.successMessage` in packages/spec/src/contracts/automation-service.ts)", "note": "FLIPPED planned → live 2026-10-16 (#23710). The console reads the translated `successMessage`: objectui's `translatedFlowMessage` asks the spec's `translateFlow` for `flows..successMessage` over the string the engine serves verbatim, and falls back to that string when the bundle carries none. The reader landed in objectui 42d8e8db6 (objectui#12075), and the `.objectui-sha` pin daeb77fc84dd carries it (`git merge-base --is-ancestor 42d8e8db6 daeb77fc84dd` answers yes), so every objectui pointer here was read at the pin this repo builds against. Re-read when the pin moved f96f771140f1 → daeb77fc84dd (2026-10-16): the reader files are byte-identical at both. It was `planned` (#22507, verified 2026-10-09) as the spec half of a contract-first split, the shape `screens` and `label` took: the face declared the key and `translateFlow` applied it while the console still drew the served string untranslated. No `authorWarn`, then or now: a child row's warn reaches no file-authored bundle (the group is a record keyed by flow name; see the CLI's `authorWarnedTranslationGroups` note)." }, "errorMessage": { "status": "live", "verifiedAt": "2026-10-16", - "evidenceScope": "cross-repo", - "evidence": "packages/spec/src/system/i18n-resolver.ts#translateFlow (overlays `flows..errorMessage` down the locale chain, only where the flow authors a `errorMessage`); packages/spec/src/system/i18n-resolver.ts#FLOW_TERMINAL_MESSAGE_KEYS (the key list the CLI skeleton writer and `translation.test.ts` share); packages/lint/src/validate-translation-references.ts#validateTranslationReferences (refuses a translated `errorMessage` over a flow that declares none); objectui @daeb77fc84dd: packages/app-shell/src/utils/flowResponse.ts#translatedFlowMessage (asks the spec's `translateFlow` for `flows..errorMessage` under the run's flow name and locale, and keeps the served string when the bundle has none); objectui @daeb77fc84dd: packages/app-shell/src/utils/flowResponse.ts#flowFailureMessage (prefers the served `errorMessage`, handed to `translatedFlowMessage`, over the raw `error`); objectui @daeb77fc84dd: packages/app-shell/src/views/FlowRunner.tsx#FlowRunner (hands `interpretFlowResponse` this run's flow name and bundle, so the resume and both launch hosts through `judgeFlowLaunch` answer the same words)", + "evidenceScope": "in-repo", + "evidence": "packages/spec/src/system/i18n-resolver.ts#translateFlow (overlays `flows..errorMessage` down the locale chain, only where the flow authors a `errorMessage`); packages/spec/src/system/i18n-resolver.ts#FLOW_TERMINAL_MESSAGE_KEYS (the key list the CLI skeleton writer and `translation.test.ts` share); packages/lint/src/validate-translation-references.ts#validateTranslationReferences (refuses a translated `errorMessage` over a flow that declares none); ui/packages/app-shell/src/utils/flowResponse.ts#translatedFlowMessage (asks the spec's `translateFlow` for `flows..errorMessage` under the run's flow name and locale, and keeps the served string when the bundle has none); ui/packages/app-shell/src/utils/flowResponse.ts#flowFailureMessage (prefers the served `errorMessage`, handed to `translatedFlowMessage`, over the raw `error`); ui/packages/app-shell/src/views/FlowRunner.tsx#FlowRunner (hands `interpretFlowResponse` this run's flow name and bundle, so the resume and both launch hosts through `judgeFlowLaunch` answer the same words)", "producer": "packages/spec/src/automation/flow.zod.ts#FlowSchema (`errorMessage`, the flow's own failure text); packages/services/service-automation/src/engine.ts#AutomationEngine (copies `flow.errorMessage` verbatim onto the terminal result, declared as `AutomationResult.errorMessage` in packages/spec/src/contracts/automation-service.ts)", "note": "FLIPPED planned → live 2026-10-16 (#23710). The console reads the translated `errorMessage`: objectui's `translatedFlowMessage` asks the spec's `translateFlow` for `flows..errorMessage` over the string the engine serves verbatim, and falls back to that string when the bundle carries none. The reader landed in objectui 42d8e8db6 (objectui#12075), and the `.objectui-sha` pin daeb77fc84dd carries it (`git merge-base --is-ancestor 42d8e8db6 daeb77fc84dd` answers yes), so every objectui pointer here was read at the pin this repo builds against. Re-read when the pin moved f96f771140f1 → daeb77fc84dd (2026-10-16): the reader files are byte-identical at both. It was `planned` (#22507, verified 2026-10-09) as the spec half of a contract-first split, the shape `screens` and `label` took: the face declared the key and `translateFlow` applied it while the console still drew the served string untranslated. No `authorWarn`, then or now: a child row's warn reaches no file-authored bundle (the group is a record keyed by flow name; see the CLI's `authorWarnedTranslationGroups` note)." }, "screens": { "status": "live", "verifiedAt": "2026-10-10", - "evidenceScope": "cross-repo", - "evidence": "packages/services/service-automation/src/builtin/screen-nodes.ts#registerScreenNodes (the `screen` executor asks for `flows..screens..title` and, where the screen authors body text, `.description`, spelled by `flowScreenCopyKey`, before it renders either template, and hands each slot to `AutomationEngine.renderFlowTextSlot`, which picks the translated template through the `i18n` service in the run's `AutomationContext.locale`, negotiated by `resolveBundleLocale`, and renders it through `renderTextSlot` in place of the authored one; a miss echoes the key and the authored template renders); packages/spec/src/system/i18n-resolver.ts#flowScreenCopyKey (spells the address the executor asks for, `flows..screens..`); objectui @31971ff1e: packages/app-shell/src/views/FlowRunner.tsx#localizeScreen (the heading through `resolveFlowScreenTitle`, and each field's copy from `bundle[language]?.flows?.[flowName]?.screens?.[screen.nodeId]?.fields`); objectui @31971ff1e: packages/app-shell/src/views/FlowRunner.tsx#overlayFieldCopy (walks the spec's imported `FLOW_SCREEN_FIELD_COPY_KEYS`, which are `label` / `placeholder` / `inlineHelpText`, and overlays each key on its own, falling back to the authored string); objectui @31971ff1e: packages/app-shell/src/views/FlowRunner.tsx#activeFlowsBundle (reads the active language's `flows` group out of the i18next `translation` resource tree); objectui @31971ff1e: packages/app-shell/src/views/FlowRunner.tsx#FlowRunner (draws `shown.title` as the dialog title and hands the localized screen to `ScreenView`); objectui @31971ff1e: packages/app-shell/src/views/ScreenView.tsx#ScreenView (draws each field's `label`, hands `placeholder` to the control, and draws `inlineHelpText` under the control, which names it in `aria-describedby`)", - "producer": "packages/runtime/src/app-plugin.ts#loadTranslations (hands each bundle's locale data to the i18n service whole, `flows` included); packages/runtime/src/domains/i18n.ts#handleI18nRequest (the translations route answers `getTranslations(locale)` with no group filter); objectui @31971ff1e: apps/console/src/loadLanguage.ts#loadLanguage (fetches the locale's translations and runs `transformSpecTranslations`); objectui @31971ff1e: packages/i18n/src/utils/spec-translations.ts#transformSpecTranslations (forwards every group it does not flatten, `flows` among them, verbatim under the `app` namespace); objectui @31971ff1e: packages/i18n/src/provider.tsx#I18nProvider (adds the loaded payload to the `translation` resource bundle that `activeFlowsBundle` reads)", + "evidenceScope": "in-repo", + "evidence": "packages/services/service-automation/src/builtin/screen-nodes.ts#registerScreenNodes (the `screen` executor asks for `flows..screens..title` and, where the screen authors body text, `.description`, spelled by `flowScreenCopyKey`, before it renders either template, and hands each slot to `AutomationEngine.renderFlowTextSlot`, which picks the translated template through the `i18n` service in the run's `AutomationContext.locale`, negotiated by `resolveBundleLocale`, and renders it through `renderTextSlot` in place of the authored one; a miss echoes the key and the authored template renders); packages/spec/src/system/i18n-resolver.ts#flowScreenCopyKey (spells the address the executor asks for, `flows..screens..`); ui/packages/app-shell/src/views/FlowRunner.tsx#localizeScreen (the heading through `resolveFlowScreenTitle`, and each field's copy from `bundle[language]?.flows?.[flowName]?.screens?.[screen.nodeId]?.fields`); ui/packages/app-shell/src/views/FlowRunner.tsx#overlayFieldCopy (walks the spec's imported `FLOW_SCREEN_FIELD_COPY_KEYS`, which are `label` / `placeholder` / `inlineHelpText`, and overlays each key on its own, falling back to the authored string); ui/packages/app-shell/src/views/FlowRunner.tsx#activeFlowsBundle (reads the active language's `flows` group out of the i18next `translation` resource tree); ui/packages/app-shell/src/views/FlowRunner.tsx#FlowRunner (draws `shown.title` as the dialog title and hands the localized screen to `ScreenView`); ui/packages/app-shell/src/views/ScreenView.tsx#ScreenView (draws each field's `label`, hands `placeholder` to the control, and draws `inlineHelpText` under the control, which names it in `aria-describedby`)", + "producer": "packages/runtime/src/app-plugin.ts#loadTranslations (hands each bundle's locale data to the i18n service whole, `flows` included); packages/runtime/src/domains/i18n.ts#handleI18nRequest (the translations route answers `getTranslations(locale)` with no group filter); ui/apps/console/src/loadLanguage.ts#loadLanguage (fetches the locale's translations and runs `transformSpecTranslations`); ui/packages/i18n/src/utils/spec-translations.ts#transformSpecTranslations (forwards every group it does not flatten, `flows` among them, verbatim under the `app` namespace); ui/packages/i18n/src/provider.tsx#I18nProvider (adds the loaded payload to the `translation` resource bundle that `activeFlowsBundle` reads)", "note": "FLIPPED planned → live 2026-09-27 (#20296). It was PLANNED with its container under the #7646 contract-first split: the spec declared the vocabulary and the screen-flow runner half was a downstream objectui card. That half has landed client-side, the side #11287 picked (objectui#5920, FlowRunner's `localizeScreen`), and the `.objectui-sha` pin f8a9d0fb carries it, so every objectui pointer above was read at the pin this repo builds against. Each read is unchanged at objectui main 256b4c9e. Per-screen heading + per-field copy, keyed by `FlowNode.id` / `ScreenFieldConfig.name`, the identifiers the client already holds as `ScreenSpec.nodeId` / `ScreenFieldSpec.name`. Each key falls back to the authored string on its own. The screen `description` and the runner chrome stay untranslated here by ruling. Deeper conventions (`screens..title`, `screens..fields..{label,placeholder,inlineHelpText}`) are governed by the runner and the spec's `FLOW_SCREEN_COPY_KEYS` / `FLOW_SCREEN_FIELD_COPY_KEYS`, not by ledger rows: the one-drill-level boundary this ledger's type note states. MOUNT CHAIN, closed by hand at the pin: `FlowRunner` is mounted by `useConsoleActionRuntime`'s dialogs (the console root `ConsoleShell` renders them, and ObjectView / DeclaredActionsBar each run their own runtime), by `RecordDetailView`, and by the console's `developer/flow-runs` route (`FlowRunsPage`). A `type: 'flow'` action whose run pauses at a screen node opens it with `{ flowName, runId, screen }`. The bundle reaches it through the console root's `I18nProvider loadLanguage` (apps/console main.tsx). ⚠️ NOT flipped with it: the container `flows` keeps `planned` + `authorWarn` for its `label` child, which nothing reads yet (#20318), and its `authorHint` now says that `screens` is read and the flow label is not. That bit is group-level and has two readers: @objectstack/lint's warn map, and the CLI i18n coverage gate (`authorWarnedTranslationGroups` in packages/cli i18n-extract.ts), which holds back the whole `flows.*` demand while it is set. Dropping it before the label has a reader would switch on demand for `flows..label` too, so it drops when #20318 lands (seat ruling on #20296). RE-VERIFIED 2026-10-02 (#17306) at the `.objectui-sha` pin 31971ff1e, which carries objectui 81778b955 (REST compare 81778b955...31971ff1e answers `ahead`, `behind_by 0`): the per-field face gained `inlineHelpText` beside `label` and `placeholder`, the screen field's own help-text key. Every objectui pointer in `evidence` and `producer` was re-read at that pin and repinned from f8a9d0fb. `overlayFieldCopy` imports the spec's key list rather than retyping it, so the new key needed no objectui edit, and `ScreenView` draws the help text under the control (objectui#9248). The status is unchanged: this row was already `live`. 2026-10-05: the `⚠️ NOT flipped with it` clause above is superseded. The `label` child is `live` now (objectui's FlowRunner reads it at the pin 0abd4f9f8), so the container flipped to `live` and dropped `authorWarn` and `authorHint`; see the container note. 2026-10-09 (#22507): the per-field face gained `options.` — a screen field's option labels, keyed by each option's value read as text (`flowScreenFieldOptionKey`) and resolved by `resolveFlowScreenFieldOptions`. It is NOT in `FLOW_SCREEN_FIELD_COPY_KEYS` (a map over `options[]`, not one string), so `overlayFieldCopy` does not reach it, and no objectui code reads it at main 20c6d351a: the option labels are declared ahead of their reader, the downstream runner change. The row stays `live` for the heading and the three string keys, which are unchanged; the one-drill-level boundary keeps the option map off a row of its own. 2026-10-10 (#22507, maintainer ruling A): the screen face gained `description`, and the screen's heading and body text moved to the ENGINE. Both are `{{ }}` templates the executor renders per run, so the `screen` executor picks each translated template in the run's locale through the one `i18n` channel the refusal message is read through, before it fills the holes; the served `ScreenSpec` arrives translated, and the authored template renders when the bundle has none. The body text is translated only where the screen authors one. objectui's `localizeScreen` still overlays the heading through `resolveFlowScreenTitle` at the `.objectui-sha` pin 20c6d351a; that overlay is retired by the ruling's consumer half (filed into objectui#12075). Until it lands the runner replaces a served heading with the translated template, which draws a hole literally exactly where it did before this change, so the window adds no regression. The per-field copy stays client-side, read by `overlayFieldCopy` as cited. The row stays `live`: the engine reads both screen keys in-repo, and objectui reads the field keys. objectui's `FlowRunner` overlay of `flows..screens` on the screen it draws was read at objectui @f8a9d0fb (the citation moved out of packages/spec/src/system/i18n-resolver.ts, #23434)." }, "refusals": { @@ -255,7 +255,7 @@ "settingsCommon": { "status": "live", "verifiedAt": "2026-08-01", - "evidence": "objectui @940ba24: apps/console/src/pages/settings/useSettingsLabel.ts:104", + "evidence": "ui/apps/console/src/pages/settings/useSettingsLabel.ts (:104 at objectui @940ba24)", "note": "cross-namespace Settings chrome strings (source badges by resolution layer); the client scans every namespace carrying a settingsCommon block." } } diff --git a/packages/spec/liveness/validation.json b/packages/spec/liveness/validation.json index a2fac312550..5a6a12c5783 100644 --- a/packages/spec/liveness/validation.json +++ b/packages/spec/liveness/validation.json @@ -11,17 +11,17 @@ "label": { "status": "live", "verifiedAt": "2026-09-07", - "evidenceScope": "cross-repo", - "evidence": "objectui: packages/app-shell/src/views/metadata-admin/previews/ValidationPreview.tsx#ValidationPreview @a472b071 — read off the draft with the rule name as fallback, rendered as the preview card title.", - "producer": "objectui: packages/app-shell/src/views/metadata-admin/previews/index.ts#registerBuiltinPreviews @a472b071 registers ValidationPreview for the `validation` type; objectui: packages/app-shell/src/views/metadata-admin/EmbeddedItemEditor.tsx#EmbeddedItemEditor resolves it through getMetadataPreview(editAs) and hands it the live draft; objectui: packages/app-shell/src/views/metadata-admin/anchors.ts#registerBuiltinAnchors binds the embedded rule to it with editAs set to `validation`. That chain IS the surface that resolves the preview — the half a read-point citation alone never proves.", + "evidenceScope": "in-repo", + "evidence": "ui/packages/app-shell/src/views/metadata-admin/previews/ValidationPreview.tsx#ValidationPreview @a472b071 — read off the draft with the rule name as fallback, rendered as the preview card title.", + "producer": "ui/packages/app-shell/src/views/metadata-admin/previews/index.ts#registerBuiltinPreviews @a472b071 registers ValidationPreview for the `validation` type; ui/packages/app-shell/src/views/metadata-admin/EmbeddedItemEditor.tsx#EmbeddedItemEditor resolves it through getMetadataPreview(editAs) and hands it the live draft; ui/packages/app-shell/src/views/metadata-admin/anchors.ts#registerBuiltinAnchors binds the embedded rule to it with editAs set to `validation`. That chain IS the surface that resolves the preview — the half a read-point citation alone never proves.", "note": "Governance / editor metadata, declared deliberately (the schema header says so): surfaced in rule listings, never evaluated on the write path. Docs-shaped and KEPT, and still NOT authorWarn-ed — pure display annotation must not warn (the job.label / translation.label precedent), and ADR-0033 exempts it from enforce-or-remove either way. LIVE under the maintainer ruling that a designer preview rendering a key to a human IS a runtime consumer (2026-08-10, #7131; README, \"Designer previews count as consumers\"), which settles a DISPLAY key outright: being shown to a human is the whole of the claimed effect, so no second layer is being stood in for. RE-DERIVED 2026-09-07 (#16362, ledger audit ahead of the #16094 authorWarn flip) — verdict FLIPPED dead to live, on the re-open condition the 2026-08-10 note stated for itself. That note upheld `dead` at objectui @e9ab52f9 on REACHABILITY, not on the read: ValidationPreview genuinely read and rendered the key, but the only route that mounted it was the standalone `validation` resource door, and ADR-0088 / #4509 had retired that kind, so on the governed (embedded `object.validations`) path the preview was never handed a draft. That note closed by naming its own falsifier — \"Filed as objectui#4132; if that lands as 'wire ValidationPreview into the embedded editor', this row re-grades to live and should.\" objectui#4132 HAS landed, and it did both halves. (1) The standalone door is gone: anchors.ts now records that there is no standalone Validations group and no create affordance, and that this file registered that door until objectui#4132 removed it. (2) The preview is wired into the embedded editor instead — EmbeddedItemEditor resolves getMetadataPreview(editAs) and mounts the result above the form with draft set to the live authored value, and the `__object_validation` anchor sets editAs to `validation` — so a rule edited where the framework actually evaluates it (embedded in its object) now renders through ValidationPreview. The read point that never ran, runs. Measured at objectui @a472b071 with a firing control on every leg: the preview-registration grep that returns zero for hook / qa / manifest / realtime_subscription returns one for validation, and the sibling PermissionPreview only COUNTS its rowLevelSecurity array and descends no further — which is why permission.rowLevelSecurity.label / .description / .tags stay dead on this same instrument." }, "description": { "status": "live", "verifiedAt": "2026-09-07", - "evidenceScope": "cross-repo", - "evidence": "objectui: packages/app-shell/src/views/metadata-admin/previews/ValidationPreview.tsx#ValidationPreview @a472b071 — read off the draft and rendered beneath the title, guarded so an empty description renders nothing.", - "producer": "objectui: packages/app-shell/src/views/metadata-admin/previews/index.ts#registerBuiltinPreviews @a472b071 registers ValidationPreview for the `validation` type; objectui: packages/app-shell/src/views/metadata-admin/EmbeddedItemEditor.tsx#EmbeddedItemEditor resolves it through getMetadataPreview(editAs) and hands it the live draft; objectui: packages/app-shell/src/views/metadata-admin/anchors.ts#registerBuiltinAnchors binds the embedded rule to it with editAs set to `validation`. That chain IS the surface that resolves the preview — the half a read-point citation alone never proves.", + "evidenceScope": "in-repo", + "evidence": "ui/packages/app-shell/src/views/metadata-admin/previews/ValidationPreview.tsx#ValidationPreview @a472b071 — read off the draft and rendered beneath the title, guarded so an empty description renders nothing.", + "producer": "ui/packages/app-shell/src/views/metadata-admin/previews/index.ts#registerBuiltinPreviews @a472b071 registers ValidationPreview for the `validation` type; ui/packages/app-shell/src/views/metadata-admin/EmbeddedItemEditor.tsx#EmbeddedItemEditor resolves it through getMetadataPreview(editAs) and hands it the live draft; ui/packages/app-shell/src/views/metadata-admin/anchors.ts#registerBuiltinAnchors binds the embedded rule to it with editAs set to `validation`. That chain IS the surface that resolves the preview — the half a read-point citation alone never proves.", "note": "Same as `label` — governance annotation, deliberately kept, docs-shaped, not authorWarn-ed. RE-DERIVED 2026-09-07 (#16362, ledger audit ahead of the #16094 authorWarn flip) — verdict FLIPPED dead to live, on the re-open condition the 2026-08-10 note stated for itself. That note upheld `dead` at objectui @e9ab52f9 on REACHABILITY, not on the read: ValidationPreview genuinely read and rendered the key, but the only route that mounted it was the standalone `validation` resource door, and ADR-0088 / #4509 had retired that kind, so on the governed (embedded `object.validations`) path the preview was never handed a draft. That note closed by naming its own falsifier — \"Filed as objectui#4132; if that lands as 'wire ValidationPreview into the embedded editor', this row re-grades to live and should.\" objectui#4132 HAS landed, and it did both halves. (1) The standalone door is gone: anchors.ts now records that there is no standalone Validations group and no create affordance, and that this file registered that door until objectui#4132 removed it. (2) The preview is wired into the embedded editor instead — EmbeddedItemEditor resolves getMetadataPreview(editAs) and mounts the result above the form with draft set to the live authored value, and the `__object_validation` anchor sets editAs to `validation` — so a rule edited where the framework actually evaluates it (embedded in its object) now renders through ValidationPreview. The read point that never ran, runs. Measured at objectui @a472b071 with a firing control on every leg: the preview-registration grep that returns zero for hook / qa / manifest / realtime_subscription returns one for validation, and the sibling PermissionPreview only COUNTS its rowLevelSecurity array and descends no further — which is why permission.rowLevelSecurity.label / .description / .tags stay dead on this same instrument." }, "active": { @@ -45,9 +45,9 @@ "tags": { "status": "live", "verifiedAt": "2026-09-07", - "evidenceScope": "cross-repo", - "evidence": "objectui: packages/app-shell/src/views/metadata-admin/previews/ValidationPreview.tsx#ValidationPreview @a472b071 — reads the array and renders one chip per tag.", - "producer": "objectui: packages/app-shell/src/views/metadata-admin/previews/index.ts#registerBuiltinPreviews @a472b071 registers ValidationPreview for the `validation` type; objectui: packages/app-shell/src/views/metadata-admin/EmbeddedItemEditor.tsx#EmbeddedItemEditor resolves it through getMetadataPreview(editAs) and hands it the live draft; objectui: packages/app-shell/src/views/metadata-admin/anchors.ts#registerBuiltinAnchors binds the embedded rule to it with editAs set to `validation`. That chain IS the surface that resolves the preview — the half a read-point citation alone never proves.", + "evidenceScope": "in-repo", + "evidence": "ui/packages/app-shell/src/views/metadata-admin/previews/ValidationPreview.tsx#ValidationPreview @a472b071 — reads the array and renders one chip per tag.", + "producer": "ui/packages/app-shell/src/views/metadata-admin/previews/index.ts#registerBuiltinPreviews @a472b071 registers ValidationPreview for the `validation` type; ui/packages/app-shell/src/views/metadata-admin/EmbeddedItemEditor.tsx#EmbeddedItemEditor resolves it through getMetadataPreview(editAs) and hands it the live draft; ui/packages/app-shell/src/views/metadata-admin/anchors.ts#registerBuiltinAnchors binds the embedded rule to it with editAs set to `validation`. That chain IS the surface that resolves the preview — the half a read-point citation alone never proves.", "note": "Categorization for reporting / management — governance metadata like label and description, deliberately kept. The declared effect here is ANNOTATION, not filtering: the describe reads \"Categorization tags\" and the schema header calls all three keys governance / editor metadata carrying authoring intent, not enforcement. That distinction is load-bearing and survives this re-grade — a tags key whose describe PROMISES filtering is behavioural, and a chip render would not settle it in either direction. `qa.scenarios.tags` is that other case and stays dead: its describe promises filtering, and `os test` ships three flags (--url, --token, --fail-on-empty), none of which selects scenarios. RE-DERIVED 2026-09-07 (#16362, ledger audit ahead of the #16094 authorWarn flip) — verdict FLIPPED dead to live, on the re-open condition the 2026-08-10 note stated for itself. That note upheld `dead` at objectui @e9ab52f9 on REACHABILITY, not on the read: ValidationPreview genuinely read and rendered the key, but the only route that mounted it was the standalone `validation` resource door, and ADR-0088 / #4509 had retired that kind, so on the governed (embedded `object.validations`) path the preview was never handed a draft. That note closed by naming its own falsifier — \"Filed as objectui#4132; if that lands as 'wire ValidationPreview into the embedded editor', this row re-grades to live and should.\" objectui#4132 HAS landed, and it did both halves. (1) The standalone door is gone: anchors.ts now records that there is no standalone Validations group and no create affordance, and that this file registered that door until objectui#4132 removed it. (2) The preview is wired into the embedded editor instead — EmbeddedItemEditor resolves getMetadataPreview(editAs) and mounts the result above the form with draft set to the live authored value, and the `__object_validation` anchor sets editAs to `validation` — so a rule edited where the framework actually evaluates it (embedded in its object) now renders through ValidationPreview. The read point that never ran, runs. Measured at objectui @a472b071 with a firing control on every leg: the preview-registration grep that returns zero for hook / qa / manifest / realtime_subscription returns one for validation, and the sibling PermissionPreview only COUNTS its rowLevelSecurity array and descends no further — which is why permission.rowLevelSecurity.label / .description / .tags stay dead on this same instrument." }, "severity": { diff --git a/packages/spec/liveness/view.json b/packages/spec/liveness/view.json index f2190cd3e76..87904106853 100644 --- a/packages/spec/liveness/view.json +++ b/packages/spec/liveness/view.json @@ -38,9 +38,9 @@ }, "data": { "status": "live", - "evidenceScope": "cross-repo", + "evidenceScope": "in-repo", "verifiedAt": "2026-10-13", - "evidence": "objectui @7578323a544e packages/plugin-list/src/ListView.tsx and objectui @7578323a544e packages/plugin-gantt/src/ObjectGantt.tsx — the list's fetch effect reads `schema.data.provider` and charts `value` rows inline, and hands a gantt the whole `data` block, which the gantt resolves through `resolveDataSource`; framework: packages/spec/src/ui/view.zod.ts#ViewDataSchema", + "evidence": "ui/packages/plugin-list/src/ListView.tsx and ui/packages/plugin-gantt/src/ObjectGantt.tsx — the list's fetch effect reads `schema.data.provider` and charts `value` rows inline, and hands a gantt the whole `data` block, which the gantt resolves through `resolveDataSource`; framework: packages/spec/src/ui/view.zod.ts#ViewDataSchema", "note": "objectui: ListView.tsx fetch effect supports the ViewDataSchema provider modes it is handed ('value' inline; the gantt owns its data block). Supersedes the 2026-06 audit's 'api/schema dead in list path'. The 'schema' provider remains form/metadata-admin-only — not honored on lists. RETIREMENT RECORD (2026-10-13, ADR-0049 enforce-or-remove, maintainer ruling 2026-10-12 letter C): the `api` provider VALUE left ViewDataSchema — refused by name on the union's own error map (`VIEW_DATA_API_PROVIDER_RETIRED`), no tombstone key, no D2 conversion, D3 `view-data-api-provider-retired`. A renderer reader EXISTED in objectui, measured at 7578323a544e — `.objectui-sha` has since moved to 1fb1ced36243 and the next cross-repo re-measure re-reads it there (ListView.tsx routed an `api` block to `ApiDataSource`; ObjectGantt.tsx built one from the block's `read`/`write`) — and is now unreachable from authored metadata: zero producers wrote the value outside documentation in objectstack (bcebd8409), objectui (origin/main f228d710 — the console's approval-requests source constructs `ApiDataSource` in code from an `ApiDataSourceConfig`, never a `ViewData`) and hotcrm (0f84c9c), each with a lit `provider: 'object'` control. This key stays `live` because the surviving arms are read; the arm's record sits on this row because the walk never descends a union arm." }, "columns": { @@ -128,8 +128,8 @@ "map": { "status": "live", "verifiedAt": "2026-08-24", - "evidenceScope": "cross-repo", - "evidence": "objectui: packages/plugin-list/src/ListView.tsx:113 resolveListMapConfig merges the view-level `map` block over the legacy `options.map` bag (view-level wins); :1849-1858 the visualization-switcher capability gate asks the SAME merged config, not `options.map` alone, before offering 'map' in allowedVisualizations; :2181-2205 `case 'map'` forwards the merged config into the object-map schema. objectui: packages/plugin-list/src/__tests__/ListView.mapViewLevelConfig.test.tsx pins the forward against a spy; objectui: packages/plugin-map/src/ObjectMap.listViewMapConfigReach.test.tsx pins the end-to-end read through a real ObjectMap (markers/titles/camera) — measured objectui @08ca73f8", + "evidenceScope": "in-repo", + "evidence": "ui/packages/plugin-list/src/ListView.tsx (:113 at objectui @08ca73f8) resolveListMapConfig merges the view-level `map` block over the legacy `options.map` bag (view-level wins); :1849-1858 the visualization-switcher capability gate asks the SAME merged config, not `options.map` alone, before offering 'map' in allowedVisualizations; :2181-2205 `case 'map'` forwards the merged config into the object-map schema. ui/packages/plugin-list/src/__tests__/ListView.mapViewLevelConfig.test.tsx pins the forward against a spy; ui/packages/plugin-map/src/ObjectMap.listViewMapConfigReach.test.tsx pins the end-to-end read through a real ObjectMap (markers/titles/camera) — measured objectui @08ca73f8", "note": "FLIPPED planned → live 2026-08-24: objectui#5908 (merged 2026-08-23T19:52:08Z; squash commit `e2e8e68` on objectui main, confirmed an ancestor of the objectui @08ca73f8 checkout this row cites) landed the view→renderer forwarding the prior PLANNED note's flip condition named. `resolveListMapConfig` now merges the view-level `map` block this key declares over the legacy `options.map` bag, `case 'map'` forwards the merge into `ObjectMap`, and the same merged config also feeds the visualization-switcher's capability gate — closing a second hole the prior note didn't need to describe (a view binding coordinates only in the spec `map` block no longer gets silently filtered out of `allowedVisualizations`, objectui#5042). The ADR-0047 `InterfaceListPage` path (packages/app-shell/src/views/InterfaceListPage.tsx:406-409) forwards the authored `map` block verbatim onto the schema it hands to `ListView`, so it reaches the same resolver rather than dead-ending. Pins: `ListView.mapViewLevelConfig.test.tsx` (the forward, against a spy) and `ObjectMap.listViewMapConfigReach.test.tsx` (the end-to-end read). Prior PLANNED note (verifiedAt 2026-08-17, #9340) is superseded; its flip instruction is discharged. Read points of the `map` view-binding row in packages/spec/src/kernel/functional-completeness.ts (moved out of spec source, #23434), re-read at objectui @b9814b334 (2026-10-14): objectui @b9814b334 packages/plugin-list/src/ListView.tsx#ListView (`case 'map'`) forwards objectui @b9814b334 packages/plugin-list/src/ListView.tsx#resolveListMapConfig with no `locationField || 'location'` floor; objectui @b9814b334 packages/plugin-map/src/ObjectMap.tsx#getMapConfig guesses no coordinate names, and objectui @b9814b334 packages/plugin-map/src/ObjectMap.tsx#hasCoordinateBinding gates every branch, a declared block included, rendering the `map-missing-location-binding` refusal (objectui#8169). Strictness record of ListMapConfigSchema (packages/spec/src/ui/view.zod.ts, moved out of spec source with its seven quoted anchors, #23434; first measured by executing the declarations at objectui @53ded82b, last re-read at objectui @b9814b334 on 2026-10-14): the flatten whitelist is objectui @b9814b334 packages/plugin-list/src/ListView.tsx#FLAT_MAP_CONFIG_SPELLING, applied by objectui @b9814b334 packages/plugin-list/src/ListView.tsx#resolveListMapConfig and by objectui @b9814b334 packages/plugin-view/src/ObjectView.tsx#ObjectView (`case 'map':`); the renderer schema objectui @b9814b334 packages/types/src/zod/objectql.zod.ts#ObjectMapConfigSchema is closed with `.strict()` (objectui#5157) and declares the eight keys including `style`; objectui @b9814b334 packages/plugin-map/src/ObjectMap.tsx#getMapConfig consults its `safeParse` only to `console.warn` and returns `{ ...config, style }`, reading `schema.mapStyle || schema.map?.style`; `map` is absent from objectui @b9814b334 packages/types/src/zod/objectql.zod.ts#LIST_VIEW_LOCAL_OVERRIDES, so objectui's ListViewSchema imports the spec block by reference; objectui content/docs/plugins/plugin-map.mdx documents `style` in the block. The `style` read was first measured at objectui @53ded82b packages/plugin-map/src/ObjectMap.tsx#getMapConfig (`schema.mapStyle || schema.map?.style`; pinned in packages/spec/src/ui/view.test.ts). The legacy `options.map` bag stays forwarded on its own path: objectui @f8a9d0fb0 packages/app-shell/src/views/InterfaceListPage.mapConfig.test.tsx (\"CONTROL: the legacy `options.map` bag is still forwarded on its own path\"), mirrored by packages/spec/src/ui/view-overlay-options-bag.test.ts." }, "description": { @@ -139,8 +139,8 @@ "sharing": { "status": "live", "verifiedAt": "2026-08-10", - "evidenceScope": "cross-repo", - "evidence": "objectui: packages/plugin-list/src/ListView.tsx:2544 gates the toolbar sharing badge on schema.sharing.type, :2549 renders its tooltip from the same value and :2573 counts it when laying out the toolbar's left side; objectui: packages/plugin-list/src/__tests__/ListView.test.tsx:2428 pins the lockedBy tooltip — measured objectui @11c1e71e", + "evidenceScope": "in-repo", + "evidence": "ui/packages/plugin-list/src/ListView.tsx (:2544 at objectui @11c1e71e) gates the toolbar sharing badge on schema.sharing.type, :2549 renders its tooltip from the same value and :2573 counts it when laying out the toolbar's left side; ui/packages/plugin-list/src/__tests__/ListView.test.tsx:2572 pins the lockedBy tooltip — measured objectui @11c1e71e", "note": "RE-CITED 2026-08-10 (#7133/#7142): the old citation was the TEST ALONE, at ListView.test.tsx:2276. The test survives with its content intact — it is now packages/plugin-list/src/__tests__/ListView.test.tsx:2428, 'should display lockedBy in sharing tooltip when set' — so this was line drift, not deletion. A test is the weaker half of a liveness claim, so the production reader is cited first and the test kept as the pin. Verdict unchanged: LIVE. Supersedes the 2026-06 audit's 'sharing.lockedBy dead' line." }, "requiredPermissions": { @@ -165,16 +165,16 @@ "hiddenFields": { "status": "live", "verifiedAt": "2026-08-10", - "evidenceScope": "cross-repo", - "evidence": "objectui: packages/react/src/spec-bridge/bridges/list-view.ts:123 copies the authored key onto the list node; objectui: packages/plugin-list/src/ListView.tsx:763 seeds the hidden-field set from schema.hiddenFields and :1491-1494 filters those names out of the effective column set, with :1509 listing it as a memo dependency — measured objectui @11c1e71e", - "note": "RE-CITED 2026-08-10 (#7132/#7142): the previous citation was 'ObjectGrid.tsx (audit L15)', which has ZERO occurrences of hiddenFields at objectui @11c1e71e. The zero belongs to the key, not the scanner — the same substring instrument returns 2-31 hits for the nine sibling keys still cited to that file. The applying reader is ListView's effective-fields memo: the grid is handed an already-filtered `columns` array and never sees the key." + "evidenceScope": "in-repo", + "evidence": "ui/packages/plugin-list/src/ListView.tsx:2020 seeds the hidden-field set from schema.hiddenFields and :3812-3815 filters those names out of the effective column set, with :3830 listing it as a memo dependency — measured objectui @11c1e71e", + "note": "RE-CITED 2026-08-10 (#7132/#7142): the previous citation was 'ObjectGrid.tsx (audit L15)', which has ZERO occurrences of hiddenFields at objectui @11c1e71e. The zero belongs to the key, not the scanner — the same substring instrument returns 2-31 hits for the nine sibling keys still cited to that file. The applying reader is ListView's effective-fields memo: the grid is handed an already-filtered `columns` array and never sees the key. 2026-10-26 (#25712, objectui merged into this repo as ui/): the spec-bridge clause (`list-view.ts:123` copying the key onto the list node) was dropped, not repointed: objectui#6632 retired the spec-bridge. ListView's lines were relocated by exact match from objectui @11c1e71e." }, "fieldOrder": { "status": "live", "verifiedAt": "2026-09-21", - "evidenceScope": "cross-repo", - "evidence": "objectui: packages/plugin-list/src/ListView.tsx#effectiveFields:2750-2784 is the applying reader — it takes schema.columns as the candidate set, subtracts the hidden-field set, 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, so it sorts last), with :2784 listing schema.fieldOrder as a memo dependency — measured objectui @87af769e, the pin this tree carries", - "producer": "objectui: packages/plugin-view/src/ObjectView.tsx#renderContent:2284 relays a named view's fieldOrder onto the list-view node that memo reads, so an authored value reaches the consumer instead of arriving undefined — view-sourced only, no host fallback, per objectui#8980 — measured objectui @87af769e", + "evidenceScope": "in-repo", + "evidence": "ui/packages/plugin-list/src/ListView.tsx#effectiveFields (:2750-2784 at objectui @87af769e) is the applying reader — it takes schema.columns as the candidate set, subtracts the hidden-field set, 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, so it sorts last), with :2784 listing schema.fieldOrder as a memo dependency — measured objectui @87af769e, the pin this tree carries", + "producer": "ui/packages/plugin-view/src/ObjectView.tsx#renderContent:3115 relays a named view's fieldOrder onto the list-view node that memo reads, so an authored value reaches the consumer instead of arriving undefined — view-sourced only, no host fallback, per objectui#8980 — measured objectui @87af769e", "note": "RE-CITED 2026-09-21 (objectstack#15184 ruling B, maintainer decision batch #115 of 2026-09-11): the key STAYS, and the composition it belongs to is now declared in the contract — `columns` projects, `hiddenFields` subtracts, `fieldOrder` orders what survives, and a survivor absent from `fieldOrder` sorts last. Both halves of the 2026-08-10 citation had rotted. Its first path, objectui packages/react/src/spec-bridge/bridges/list-view.ts, is GONE: `git ls-tree -r` at objectui @87af769e returns 0 files under packages/react/src/spec-bridge/ while packages/react/ itself returns 160, so the zero belongs to the directory and not to the instrument. Its second rotted IN RANGE — the ListView memo was at :1499-1500 when measured at @11c1e71e, ~:2424-2434 when ruling B was written on 2026-09-11, and :2774-2781 here — which is why both pointers now carry symbol anchors. The spec-side declaration is the three `.describe()` strings on `columns` / `hiddenFields` / `fieldOrder` in packages/spec/src/ui/view.zod.ts, held together with the accept set by packages/spec/src/ui/view-field-order-composition.pin.test.ts. One measured gap, objectui's to close and 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." }, "rowActions": { @@ -202,8 +202,8 @@ "inlineEdit": { "status": "live", "verifiedAt": "2026-08-10", - "evidenceScope": "cross-repo", - "evidence": "objectui: packages/plugin-list/src/ListView.tsx:791-794 seeds inline-edit state from schema.inlineEdit and :1559 forwards it to the grid node under the renamed key `editable`; objectui: packages/plugin-grid/src/ObjectGrid.tsx:2197 passes editable down to the data-table and :2204 mounts renderCellEditor only when it is set, with :2194 gating the save/cancel action column — measured objectui @11c1e71e", + "evidenceScope": "in-repo", + "evidence": "ui/packages/plugin-list/src/ListView.tsx (:791-794 at objectui @11c1e71e) seeds inline-edit state from schema.inlineEdit and :1559 forwards it to the grid node under the renamed key `editable`; ui/packages/plugin-grid/src/ObjectGrid.tsx (:2197 at objectui @11c1e71e) passes editable down to the data-table and :2204 mounts renderCellEditor only when it is set, with :2194 gating the save/cancel action column — measured objectui @11c1e71e", "note": "RE-CITED 2026-08-10 (#7132/#7142): the previous 'ObjectGrid.tsx (audit L15)' citation greps to zero because of a ONE-HOP RENAME, not because the key is inert — ObjectGrid never spells `inlineEdit`, it reads `editable`, which is what ListView renames the key to at :1559. The value is applied (no cell editor is mounted without it); only the spelling changes at the boundary. That rename is exactly the instrument blindness that made the old citation read as dead." }, "exportOptions": { @@ -341,15 +341,15 @@ "groups": { "status": "live", "verifiedAt": "2026-08-09", - "evidenceScope": "cross-repo", - "evidence": "packages/spec/src/ui/view.zod.ts (foldFormGroupsIntoSections + the .overwrite on FormViewSchema — the PRODUCER fold, #6926: groups-only folds onto sections, sections wins when both are present, and groups is absent from every parsed form); objectui: packages/react/src/spec-bridge/bridges/form-view.ts:169 packages/plugin-form/src/ObjectForm.tsx:129-142 (objectui @7b3e048 — the pre-existing boundary folds, now redundant for parsed forms and retiring in a later cross-repo lap)", - "note": "WHERE THE FOLD LIVES: at the producer, as of #6926 — a `.overwrite()` on FormViewSchema, inherited by every parse door (ViewSchema.form/.formViews.*, both ViewItemSchema form arms, and ViewMetadataSchema's container AND flattened form-overlay members, which picks it up through .extend()). Before #6926 the alias was declared but folded NOWHERE in this repo: it was honored one boundary downstream in objectui (alias-at-one-boundary, objectui#2545, after a groups-only spec rendered nothing), while the framework's REST public-form routes read `sections` only — so the same authored form rendered in the console and degraded on GET /forms/:slug, POST /forms/:slug/submit and GET /forms/:slug/lookup/:field. That omission is what let #6926 be filed reading a live key as dead; it is recorded here so the next reader cannot repeat it. STILL NOT FOLDED (measured 2026-08-09, #6926): a RUNTIME-saved sys_metadata row — saveMetaItem validates through ViewMetadataSchema and then deliberately discards parsed.data to keep Studio round-trip keys, and the read replays the ADR-0087 stored-row conversion chain, which is not a zod parse. Code-authored views (defineView/defineForm → registry) DO reach REST folded. Pre-parse consumers still read the authored key and are right to: packages/lint's view rules walk authored sources. Audit L24 drift resolved by alias-at-one-boundary; superseded by the producer fold." + "evidenceScope": "in-repo", + "evidence": "packages/spec/src/ui/view.zod.ts (foldFormGroupsIntoSections + the .overwrite on FormViewSchema — the PRODUCER fold, #6926: groups-only folds onto sections, sections wins when both are present, and groups is absent from every parsed form); ui/packages/plugin-form/src/ObjectForm.tsx (:129-142 at objectui @7b3e048) (objectui @7b3e048 — the pre-existing boundary folds, now redundant for parsed forms and retiring in a later cross-repo lap)", + "note": "WHERE THE FOLD LIVES: at the producer, as of #6926 — a `.overwrite()` on FormViewSchema, inherited by every parse door (ViewSchema.form/.formViews.*, both ViewItemSchema form arms, and ViewMetadataSchema's container AND flattened form-overlay members, which picks it up through .extend()). Before #6926 the alias was declared but folded NOWHERE in this repo: it was honored one boundary downstream in objectui (alias-at-one-boundary, objectui#2545, after a groups-only spec rendered nothing), while the framework's REST public-form routes read `sections` only — so the same authored form rendered in the console and degraded on GET /forms/:slug, POST /forms/:slug/submit and GET /forms/:slug/lookup/:field. That omission is what let #6926 be filed reading a live key as dead; it is recorded here so the next reader cannot repeat it. STILL NOT FOLDED (measured 2026-08-09, #6926): a RUNTIME-saved sys_metadata row — saveMetaItem validates through ViewMetadataSchema and then deliberately discards parsed.data to keep Studio round-trip keys, and the read replays the ADR-0087 stored-row conversion chain, which is not a zod parse. Code-authored views (defineView/defineForm → registry) DO reach REST folded. Pre-parse consumers still read the authored key and are right to: packages/lint's view rules walk authored sources. Audit L24 drift resolved by alias-at-one-boundary; superseded by the producer fold. 2026-10-26 (#25712, objectui merged into this repo as ui/): the spec-bridge clause (`form-view.ts:169`) was dropped, not repointed: objectui#6632 retired the spec-bridge. ObjectForm's line range is kept as the @7b3e048 reading; it does not match today's file." }, "subforms": { "status": "live", "verifiedAt": "2026-08-10", - "evidenceScope": "cross-repo", - "evidence": "objectui: packages/plugin-form/src/ObjectForm.tsx:175 branches on schema.subforms and :197 hands it to MasterDetailForm as `details`; objectui: packages/plugin-form/src/MasterDetailForm.tsx:315 reads schema.details and :240 renders one panel per entry, with :227 summing their amount fields; objectui: packages/plugin-form/src/ModalForm.tsx:736 and packages/plugin-form/src/DrawerForm.tsx:558 take the same key through their own envelopes (ModalForm applies at :756/:786, DrawerForm at :570) — measured objectui @11c1e71e", + "evidenceScope": "in-repo", + "evidence": "ui/packages/plugin-form/src/ObjectForm.tsx (:175 at objectui @11c1e71e) branches on schema.subforms and :197 hands it to MasterDetailForm as `details`; ui/packages/plugin-form/src/MasterDetailForm.tsx (:315 at objectui @11c1e71e) reads schema.details and :240 renders one panel per entry, with :227 summing their amount fields; ui/packages/plugin-form/src/ModalForm.tsx:1080 and ui/packages/plugin-form/src/DrawerForm.tsx:909 take the same key through their own envelopes (ModalForm applies at :1117/:1153, DrawerForm at :921) — measured objectui @11c1e71e", "note": "RE-CITED 2026-08-10 (#7133/#7142): the old citation named deriveMasterDetail.ts:338 and LineItemsPanel.tsx:45, and neither file mentions `subforms` at objectui @11c1e71e. deriveMasterDetail DERIVES master-detail panels from relationship metadata — its output is merged with the authored key one level up, in app-shell MetadataProvider.tsx:304-307 — and LineItemsPanel.tsx:41-55 is the per-panel schema, one hop past the rename to `details`. The form renderers are the readers that branch on the authored key. Verdict unchanged: LIVE, master-detail fully wired (audit L17)." }, "sharing": { diff --git a/packages/spec/liveness/webhook.json b/packages/spec/liveness/webhook.json index 8e40abf689f..5a2a735d70f 100644 --- a/packages/spec/liveness/webhook.json +++ b/packages/spec/liveness/webhook.json @@ -11,9 +11,9 @@ "label": { "status": "live", "verifiedAt": "2026-10-20", - "evidenceScope": "cross-repo", - "evidence": "objectui @616df6598b42, the .objectui-sha pin: packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx#defaultColumns:1116 (the Studio metadata list's default `label` column, which line 495 falls back to because `webhook` registers no `listColumns`, and whose cell line 813 reads as `row.item[c.key]`); objectui @616df6598b42: packages/app-shell/src/views/metadata-admin/QuickFind.tsx#MetadataQuickFind:114 (the Studio metadata quick-find indexes every item's `label` off the same `client.list` read and draws it beside the item's name)", - "producer": "objectui @616df6598b42: packages/app-shell/src/console/AppContent.tsx#AppContent:163 (mounts `MetadataResourceListPage` on the Studio route `metadata/:type`); objectui @616df6598b42: packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx#MetadataResourceListPage:145 (renders `DefaultMetadataList`: no objectui registration gives `webhook` a `ListPage` or `listColumns` at this pin); objectui @616df6598b42: packages/data-objectstack/src/metadata-client.ts#MetadataClient:1117 (`list('webhook')` reads `GET /api/v1/meta/webhook`); framework: packages/rest/src/meta-item-read-gate.ts#createMetaListAnswer (the one list answer both transports serve; `webhook` has no metadata-type redactor, so the authored value reaches the body)", + "evidenceScope": "in-repo", + "evidence": "objectui @616df6598b42, the .objectui-sha pin: ui/packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx#defaultColumns:1292 (the Studio metadata list's default `label` column, which line 573 falls back to because `webhook` registers no `listColumns`, and whose cell line 902 reads as `row.item[c.key]`); ui/packages/app-shell/src/views/metadata-admin/QuickFind.tsx#MetadataQuickFind:114 (the Studio metadata quick-find indexes every item's `label` off the same `client.list` read and draws it beside the item's name)", + "producer": "ui/packages/app-shell/src/console/AppContent.tsx#AppContent:163 (mounts `MetadataResourceListPage` on the Studio route `metadata/:type`); ui/packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx#MetadataResourceListPage:158 (renders `DefaultMetadataList`: no objectui registration gives `webhook` a `ListPage` or `listColumns` at this pin); ui/packages/data-objectstack/src/metadata-client.ts#MetadataClient:1125 (`list('webhook')` reads `GET /api/v1/meta/webhook`); framework: packages/rest/src/meta-item-read-gate.ts#createMetaListAnswer (the one list answer both transports serve; `webhook` has no metadata-type redactor, so the authored value reaches the body)", "note": "RE-GRADED dead → live 2026-10-20 (#24900) under the #7131 previews ruling (README, 'Designer previews count as consumers'), on the `hook.label` / `email_template.label` precedent: for a display key, being shown to a human is the whole of the claimed effect, and a Studio list column and the metadata quick-find are that showing. The 2026-10-19 `dead` verdict looked at the Setup `sys_webhook` list only; the Studio metadata list reads the registry item itself, so retiring the seeder never cut it. READER, read at the `.objectui-sha` pin 616df6598b42: `MetadataResourceListPage` falls through to `DefaultMetadataList`, whose columns are `config.listColumns ?? defaultColumns(...)` = the primary key, `label` and `description`; `webhook` has no registered preview (`registerMetadataPreview('webhook'` has zero hits at the pin) and no Setup catalog (`SETUP_CATALOG_TYPES` is permission/position/sharing_rule). PRODUCER, measured booted rather than read: a throwaway `pnpm dev -- --fresh` boot of the real examples/app-showcase composition at 223ae939bb, with a probe `label` and `description` authored onto its declared webhook (reverted after), signed in as the dev admin, answered `GET /api/v1/meta/webhook` 200 with `items` holding the one declared webhook carrying both probe values and `_packageId: com.example.showcase`; `GET /api/v1/meta/types` listed `webhook`, and `GET /api/v1/meta/package` listed that package with `scope: project`, which is what the list page's package scope admits. Docs-shaped annotation, deliberately KEPT and not authorWarn'd; `live` here does not mean the dispatcher acquired a use for it — `auto-enqueuer.ts#projectDeclared` still reads only what it delivers on. HISTORY, kept as history — the note this row carried before the re-grade: Display-only — surfaced in Studio, never sent on the wire. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) — see the file `_note` for what the re-closure found across this ledger; re-closed by hand against c459da6bc, DATED for the first time. 2026-10-19: DEAD since ADR-0131 D2 retired the boot seeder `bootstrapDeclaredWebhooks`, which was its only reader — it copied this value into the declared webhook's `sys_webhook` row, where the Setup list showed it. A declared webhook has no row any more, and nothing displays a declaration: the dispatcher reads only what it delivers on (`auto-enqueuer.ts#projectDeclared`), the activation door only the name. Display-only, so the loss is a label nobody sees, never a delivery that changes. Enforce-or-remove (ADR-0049) is owed: a Setup surface that lists declared webhooks (the declared-sharing-rule Disable and Clone surface is the pattern) makes it live again, otherwise it retires." }, "object": { @@ -68,9 +68,9 @@ "description": { "status": "live", "verifiedAt": "2026-10-20", - "evidenceScope": "cross-repo", - "evidence": "objectui @616df6598b42, the .objectui-sha pin: packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx#defaultColumns:1117 (the Studio metadata list's default `description` column, which line 495 falls back to because `webhook` registers no `listColumns`, and whose cell line 813 reads as `row.item[c.key]`); objectui @616df6598b42: packages/app-shell/src/views/metadata-admin/QuickFind.tsx#MetadataQuickFind:115 (the Studio metadata quick-find indexes every item's `description` off the same `client.list` read and draws it beside the item's name)", - "producer": "objectui @616df6598b42: packages/app-shell/src/console/AppContent.tsx#AppContent:163 (mounts `MetadataResourceListPage` on the Studio route `metadata/:type`); objectui @616df6598b42: packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx#MetadataResourceListPage:145 (renders `DefaultMetadataList`: no objectui registration gives `webhook` a `ListPage` or `listColumns` at this pin); objectui @616df6598b42: packages/data-objectstack/src/metadata-client.ts#MetadataClient:1117 (`list('webhook')` reads `GET /api/v1/meta/webhook`); framework: packages/rest/src/meta-item-read-gate.ts#createMetaListAnswer (the one list answer both transports serve; `webhook` has no metadata-type redactor, so the authored value reaches the body)", + "evidenceScope": "in-repo", + "evidence": "objectui @616df6598b42, the .objectui-sha pin: ui/packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx#defaultColumns:1293 (the Studio metadata list's default `description` column, which line 573 falls back to because `webhook` registers no `listColumns`, and whose cell line 902 reads as `row.item[c.key]`); ui/packages/app-shell/src/views/metadata-admin/QuickFind.tsx#MetadataQuickFind:115 (the Studio metadata quick-find indexes every item's `description` off the same `client.list` read and draws it beside the item's name)", + "producer": "ui/packages/app-shell/src/console/AppContent.tsx#AppContent:163 (mounts `MetadataResourceListPage` on the Studio route `metadata/:type`); ui/packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx#MetadataResourceListPage:158 (renders `DefaultMetadataList`: no objectui registration gives `webhook` a `ListPage` or `listColumns` at this pin); ui/packages/data-objectstack/src/metadata-client.ts#MetadataClient:1125 (`list('webhook')` reads `GET /api/v1/meta/webhook`); framework: packages/rest/src/meta-item-read-gate.ts#createMetaListAnswer (the one list answer both transports serve; `webhook` has no metadata-type redactor, so the authored value reaches the body)", "note": "RE-GRADED dead → live 2026-10-20 (#24900) under the #7131 previews ruling (README, 'Designer previews count as consumers'), on the `hook.label` / `email_template.label` precedent: for a display key, being shown to a human is the whole of the claimed effect, and a Studio list column and the metadata quick-find are that showing. The 2026-10-19 `dead` verdict looked at the Setup `sys_webhook` list only; the Studio metadata list reads the registry item itself, so retiring the seeder never cut it. READER, read at the `.objectui-sha` pin 616df6598b42: `MetadataResourceListPage` falls through to `DefaultMetadataList`, whose columns are `config.listColumns ?? defaultColumns(...)` = the primary key, `label` and `description`; `webhook` has no registered preview (`registerMetadataPreview('webhook'` has zero hits at the pin) and no Setup catalog (`SETUP_CATALOG_TYPES` is permission/position/sharing_rule). PRODUCER, measured booted rather than read: a throwaway `pnpm dev -- --fresh` boot of the real examples/app-showcase composition at 223ae939bb, with a probe `label` and `description` authored onto its declared webhook (reverted after), signed in as the dev admin, answered `GET /api/v1/meta/webhook` 200 with `items` holding the one declared webhook carrying both probe values and `_packageId: com.example.showcase`; `GET /api/v1/meta/types` listed `webhook`, and `GET /api/v1/meta/package` listed that package with `scope: project`, which is what the list page's package scope admits. Docs-shaped annotation, deliberately KEPT and not authorWarn'd; `live` here does not mean the dispatcher acquired a use for it — `auto-enqueuer.ts#projectDeclared` still reads only what it delivers on. HISTORY, kept as history — the note this row carried before the re-grade: Display-only — surfaced/editable in Studio, inert in dispatch. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) — see the file `_note` for what the re-closure found across this ledger; re-closed by hand against c459da6bc, DATED for the first time. 2026-10-19: DEAD since ADR-0131 D2 retired the boot seeder `bootstrapDeclaredWebhooks`, which was its only reader — it copied this value into the declared webhook's `sys_webhook` row, where the Setup list showed it. A declared webhook has no row any more, and nothing displays a declaration: the dispatcher reads only what it delivers on (`auto-enqueuer.ts#projectDeclared`), the activation door only the name. Display-only, so the loss is a label nobody sees, never a delivery that changes. Enforce-or-remove (ADR-0049) is owed: a Setup surface that lists declared webhooks (the declared-sharing-rule Disable and Clone surface is the pattern) makes it live again, otherwise it retires." } } diff --git a/packages/spec/scripts/liveness/check-liveness.mts b/packages/spec/scripts/liveness/check-liveness.mts index 141dc51f5a6..dffbaad58b2 100644 --- a/packages/spec/scripts/liveness/check-liveness.mts +++ b/packages/spec/scripts/liveness/check-liveness.mts @@ -77,8 +77,10 @@ // population decision with its own history — see EVIDENCE_SCANNED_STATUSES // below, where the `planned`/`experimental` widening and the `dead` exclusion // are argued from measurements. Cross-repo attribution -// (`objectui: …`, `cloud: …`, `packages/services/service-ai/…`) is counted, not -// resolved — those files are legitimately absent here. This was a ⚠ until #5623, +// (`cloud: …`, `packages/services/service-ai/…`) is counted, not resolved — +// those files are legitimately absent here; the renderer is not cross-repo since +// it merged in as `ui/` (#25677), and an `objectui: …` attribution FAILS with the +// in-repo spelling as its remedy (RETIRED_REALMS). This was a ⚠ until #5623, // for one historical reason: the pre-#3857 `evidence.split(':')[0]` parser // flagged 48 of 227 entries with a ~100% false-positive rate, so failing on it // would have failed every build. The parse fix is what turned a hit into signal. @@ -201,7 +203,7 @@ import { type VerificationEntry, type VerificationReport, } from './verification.mts'; -import { checkCitationLines, checkEvidence, checkEvidenceAnchors, countLines, type EvidenceScan } from './evidence.mts'; +import { checkCitationLines, checkEvidence, checkEvidenceAnchors, countLines, scanEvidence, type EvidenceScan } from './evidence.mts'; import { ELSEWHERE_EXPIRED_GUIDANCE, ELSEWHERE_GUIDANCE, @@ -740,6 +742,7 @@ const report: any = { verification: null as VerificationReport | null, // `verifiedAt` ages — the re-verification worklist producers: null as ProducerReport | null, // `producer` / `evidenceScope` — the #4837 / #4895 worklists producerMissing: [] as string[], // a `producer` pointer into thin air — FAILS, like a rotted `evidence` + retiredRealm: [] as string[], // a path still attributed to a realm that merged into this repo (`objectui:`) — FAILS // The three evidence counters, and the distinction between the first two is // the whole point: `evidenceLocal` is how many repo-rooted paths the SCANNED // statuses DECLARE (EVIDENCE_SCANNED_STATUSES — see there for why `planned` @@ -750,7 +753,7 @@ const report: any = { // "330 resolved" (#5623). Count and word now agree. evidenceLocal: 0, // repo-rooted evidence paths attributed to THIS repo — DECLARED, not yet proven evidenceMissing: 0, // ...of which this many do not exist here (=== staleEvidence.length) — FAILS the gate - evidenceForeign: 0, // evidence paths attributed to objectui / cloud — not resolvable here, never failed + evidenceForeign: 0, // evidence paths attributed to cloud / ee — not resolvable here, never failed // The LINE half of a citation. `evidenceMissing` above answers "does the cited // FILE exist"; nothing answered "does the cited LINE". A consumer that moves out // of a file which still exists therefore rotted its pointer in complete silence, @@ -869,6 +872,11 @@ function collectAnchorFindings(scan: EvidenceScan, label: string): void { } } +/** Record every path a scan attributed to a retired realm, with its in-repo spelling. */ +function collectRetired(scan: Pick, label: string): void { + for (const r of scan.retired) report.retiredRealm.push(`${label} → ${r.realm}: ${r.path} (now ${r.inRepo})`); +} + function classify(type: string, path: string, status: string, led: any, cat: any) { cat.classified++; cat.byStatus[status] = (cat.byStatus[status] || 0) + 1; @@ -897,12 +905,17 @@ function classify(type: string, path: string, status: string, led: any, cat: any if (typeof led?.producer === 'string') { const pv = checkEvidence(led.producer, (p) => existsSync(join(repoRoot, p))); for (const miss of pv.missing) report.producerMissing.push(`${type}/${path} → ${miss}`); + collectRetired(pv, `${type}/${path} [producer]`); collectOutOfRange(pv, `${type}/${path} [producer]`); collectAnchorFindings(pv, `${type}/${path} [producer]`); } + // A retired-realm pointer fails whatever the status: the realm no longer + // exists, so the attribution is wrong on a `dead` row too (#25712 — measured + // zero across every status once the objectui rows were re-rooted under ui/). + if (typeof led?.evidence === 'string') collectRetired(scanEvidence(led.evidence), `${type}/${path}`); if (EVIDENCE_SCANNED_STATUSES.has(status) && led?.evidence) { // Extract every repo-rooted path the evidence claims and resolve the ones - // attributed to THIS repo. Cross-repo pointers (objectui / cloud) are + // attributed to THIS repo. Cross-repo pointers (cloud / ee) are // counted, not resolved — see evidence.mts for why the old // `split(':')[0]` heuristic reported ~100% false positives. const ev = checkEvidence(led.evidence, (p) => existsSync(join(repoRoot, p))); @@ -1511,6 +1524,10 @@ const failed = report.verification.errors.length > 0 || report.producers.errors.length > 0 || report.producerMissing.length > 0 || + // A path still attributed to a realm that merged into this repo (#25712). Red + // from day one on the zero-census argument: the change that retired `objectui` + // re-rooted every such pointer under ui/, so only a NEW one can red it. + report.retiredRealm.length > 0 || report.ungoverned.length > 0 || report.stalePending.length > 0 || report.undrilledNew.length > 0 || @@ -1548,7 +1565,7 @@ if (asJson) { `\nevidence paths: ${report.evidenceLocal} repo-local path(s) declared by ${EVIDENCE_SCANNED_LABEL} entries, ` + `${report.evidenceLocal - report.evidenceMissing} resolved against this checkout` + (report.evidenceMissing ? `, ${report.evidenceMissing} MISSING` : '') + - `; ${report.evidenceForeign} attributed to another repo (objectui / cloud — not resolvable here).`, + `; ${report.evidenceForeign} attributed to another repo (cloud / ee — not resolvable here).`, ); // Same two-number discipline one level down, and for the same reason: printing // only "in range" would read as a pass on a run where the parser had degraded @@ -1685,7 +1702,7 @@ if (asJson) { ' • the consumer (or the refuser) MOVED inside this repo → repoint the path, and\n' + ' stamp `verifiedAt` while you have the call graph open;\n' + ' • it moved to ANOTHER repo → say so with a realm marker\n' + - ' (`objectui: packages/app-shell/…`, `cloud: …`). Attributed paths are counted,\n' + + ' (`cloud: packages/…`). Attributed paths are counted,\n' + ' never resolved, and never fail here — that boundary is deliberate;\n' + ' • it is GONE → the verdict no longer holds. A `live` entry whose consumer left\n' + ' re-classifies under ADR-0049 enforce-or-remove; a `planned` / `experimental`\n' + @@ -1952,6 +1969,23 @@ if (asJson) { ); report.producerMissing.forEach((s: string) => console.log(` ${s}`)); } + if (report.retiredRealm.length) { + console.log( + `\n✗ ${report.retiredRealm.length} pointer(s) attributed to a retired realm — that repo merged into this one:`, + ); + report.retiredRealm.forEach((s: string) => console.log(` ${s}`)); + console.log( + '\n objectui is no longer another repository: the renderer lives at `ui/` here (#25677),\n' + + ' so its files resolve like any other path. Cite the in-repo spelling printed above —\n' + + ' `ui/packages/app-shell/…#Symbol` — and drop the realm marker. The marker cannot\n' + + ' stay as foreign (counted, never resolved, it would never fail), and it cannot be\n' + + ' read as local either: both trees have a `packages/core/`, so the bare path could\n' + + ' resolve against a file that never was the reader. A line read at an old objectui\n' + + ' commit is NOT a current line: keep the path and anchor, and keep the reading as\n' + + ' prose (`(:961 at objectui @940ba24)`) unless the cited block is byte-identical at\n' + + ' one offset of today\'s file.', + ); + } const scopes = Object.entries(pr.byScope).map(([s, n]) => `${n} ${s}`).join(', ') || 'none'; console.log( `\nevidence quality: ${pr.withProducer} live entr(ies) cite a PRODUCER (${pr.withoutProducer.length} do not); ` diff --git a/packages/spec/scripts/liveness/check-liveness.test.ts b/packages/spec/scripts/liveness/check-liveness.test.ts index f8a5f6962db..76a8a80d01c 100644 --- a/packages/spec/scripts/liveness/check-liveness.test.ts +++ b/packages/spec/scripts/liveness/check-liveness.test.ts @@ -85,6 +85,14 @@ function setChildEvidence(root: string, type: string, prop: string, child: strin writeFileSync(file, `${JSON.stringify(ledger, null, 2)}\n`); } +/** Rewrite one property's `producer` in a copied ledger. */ +function setProducer(root: string, type: string, prop: string, producer: string): void { + const file = path.join(root, `${type}.json`); + const ledger = JSON.parse(readFileSync(file, 'utf8')); + ledger.props[prop].producer = producer; + writeFileSync(file, `${JSON.stringify(ledger, null, 2)}\n`); +} + /** Rewrite one property's `status` in a copied ledger. */ function setStatus(root: string, type: string, prop: string, status: string): void { const file = path.join(root, `${type}.json`); @@ -158,14 +166,16 @@ describe('check:liveness — evidence pointers (#5623)', () => { } }); - // THE BOUNDARY. Tightening the local case must not drag the ~101 cross-repo + // THE BOUNDARY. Tightening the local case must not drag the cross-repo // attributions in with it: those files are legitimately absent from this // checkout, and failing on them would make the gate unsatisfiable for every - // property whose consumer is the renderer or the closed cloud runtime. + // property whose consumer is the closed cloud runtime. (The renderer was the + // other such realm until it merged in as `ui/` — #25712 — and its marker now + // FAILS instead; see the retired-realm case below.) it('stays green when the missing path is attributed to ANOTHER repo', () => { const root = path.join(tmp, 'foreign'); cpSync(LEDGERS, root, { recursive: true }); - setEvidence(root, 'query', 'limit', 'objectui: packages/app-shell/src/no-such-file.tsx:12'); + setEvidence(root, 'query', 'limit', 'ee: packages/ee-runtime/src/no-such-file.ts:12'); setEvidence(root, 'query', 'offset', 'cloud: packages/ee-runtime/src/also-not-here.ts:9'); // The closed cloud runtime is repo-ROOTED and still foreign — the shape that // would break first if the boundary were drawn on the path text alone. @@ -227,10 +237,10 @@ describe('check:liveness — evidence pointers (#5623)', () => { it('never bounds a citation attributed to ANOTHER repo', () => { // Same boundary as the existence check: those files are absent here, so // every line in them would read as past EOF and the gate would become - // unsatisfiable for every renderer-side property. + // unsatisfiable for every cloud-side property. const root = path.join(tmp, 'foreign-line'); cpSync(LEDGERS, root, { recursive: true }); - setEvidence(root, 'query', 'limit', 'objectui: packages/app-shell/src/RecordDetailView.tsx:99999'); + setEvidence(root, 'query', 'limit', 'cloud: packages/ee-runtime/src/publish-gate.ts:99999'); const { status, output } = runGate(root); expect(status, output).toBe(0); @@ -255,26 +265,71 @@ describe('check:liveness — evidence pointers (#5623)', () => { // the shape this regex documents — drop the line, rename it, or collapse it // to one number and this reds. A `path:NNN` citation written again gives this // case a population back; restore a floor with it. + // + // RESTORED 2026-10-26 (#25712) on that instruction: re-rooting the objectui + // pointers under `ui/` put `path:NNN` citations back into the local + // population, so the floor and the equality come back with it. The same + // standing instruction holds in reverse: when that population is converted + // to `#symbol` anchors and reaches zero again, delete the floor as #13043 did. const { status, output } = runGate(path.join(tmp, 'liveness')); expect(status, output).toBe(0); const line = output.split('\n').find((l) => l.startsWith('line citations:')) ?? ''; const m = /line citations: (\d+) pointer\(s\) written .*?, (\d+) inside the cited file/.exec(line); expect(m, line).not.toBeNull(); + expect(Number(m![1]), line).toBeGreaterThan(0); + expect(m![2], line).toBe(m![1]); expect(line).not.toContain('PAST EOF'); }); it('still fails a local path that shares a string with a foreign clause', () => { // A realm marker's scope ends at the clause boundary. If it did not, one - // `objectui:` anywhere in an entry would silence the whole entry — a + // `cloud:` anywhere in an entry would silence the whole entry — a // one-token opt-out of the gate. const root = path.join(tmp, 'mixed'); cpSync(LEDGERS, root, { recursive: true }); - setEvidence(root, 'query', 'limit', `objectui: packages/app-shell/src/x.tsx; ${ROTTED}:1345`); + setEvidence(root, 'query', 'limit', `cloud: packages/ee-runtime/src/x.ts; ${ROTTED}:1345`); const { status, output } = runGate(root); expect(status, output).toBe(1); expect(output).toContain(`query/limit → ${ROTTED}`); }); + + // #25712 — objectui merged into this repo as `ui/` (#25677). Its realm marker + // is RETIRED: a path still attributed to it is neither counted foreign (it + // would never fail) nor resolved as local (both trees have `packages/core/`, so + // it could resolve against a file that never was the reader). The gate fails + // it on a list of its own, with the in-repo spelling as the remedy. + it('FAILS on a path attributed to the retired objectui realm, and prints its ui/ spelling', () => { + const root = path.join(tmp, 'retired-realm'); + cpSync(LEDGERS, root, { recursive: true }); + // `packages/core/src/index.ts` EXISTS in this repo — the shape that a + // local reading of the retired marker would wrongly pass. + setEvidence(root, 'query', 'limit', 'objectui @f8a9d0fb: packages/core/src/index.ts'); + + const { status, output } = runGate(root); + expect(status, output).toBe(1); + expect(output).toMatch(/✗ 1 pointer\(s\) attributed to a retired realm/); + expect(output).toContain('query/limit → objectui: packages/core/src/index.ts (now ui/packages/core/src/index.ts)'); + // Never read as a missing LOCAL file, and never counted foreign. + expect(output).not.toContain('cite a file that is missing from THIS repo'); + }); + + it('FAILS on a retired-realm pointer in `producer`, and on a row of any status', () => { + // The realm no longer exists, so the attribution is wrong whatever the row + // says about liveness: a `dead` row's evidence is not resolved, but it is + // still read for a retired marker. + const root = path.join(tmp, 'retired-realm-dead'); + cpSync(LEDGERS, root, { recursive: true }); + setProducer(root, 'query', 'limit', 'objectui: packages/core/src/index.ts#nope'); + setEvidence(root, 'query', 'offset', 'objectui: packages/core/src/index.ts'); + setStatus(root, 'query', 'offset', 'dead'); + moveCount(root, 'query', String(readRow(LEDGERS, 'query', 'offset').status), 'dead'); + + const { status, output } = runGate(root); + expect(status, output).toBe(1); + expect(output).toContain('query/limit [producer] → objectui: packages/core/src/index.ts (now ui/packages/core/src/index.ts)'); + expect(output).toContain('query/offset → objectui: packages/core/src/index.ts (now ui/packages/core/src/index.ts)'); + }); }); // #12516 — the SYMBOL half of a citation. A line citation rots IN RANGE: the @@ -417,8 +472,9 @@ describe('check:liveness — the evidence-scan population (#13041)', () => { // case used to rely on `field.useGrouping` being `planned` in the shipped // ledger, and when that row went `live` the case kept passing while it drove // a `live` row — the one leg it exists for went unexercised, silently. The - // row's shipped `producer` and `evidenceScope` cite only objectui-attributed - // paths and trip nothing on a `planned` row, and `moveCount` keeps the copy's + // row's shipped `producer` cites only `ui/` paths that resolve (they were + // objectui-attributed until #25712), so it trips nothing on a `planned` row, + // and `moveCount` keeps the copy's // count shard in step with the flip, so the pointer written below is still // the run's only cause — and the rot is the plainest kind, the one the // existence check has caught for `live` entries since #5623. @@ -882,7 +938,7 @@ describe('check:liveness — the evidence summary line (#5623)', () => { it('reports foreign attributions separately and never as missing', () => { const { output } = runGate(); const line = summaryLine(output); - expect(line).toMatch(/; \d+ attributed to another repo \(objectui \/ cloud — not resolvable here\)\./); + expect(line).toMatch(/; \d+ attributed to another repo \(cloud \/ ee — not resolvable here\)\./); }); }); @@ -957,8 +1013,8 @@ describe('check:liveness — an unrecognized ledger `status` (#13083)', () => { // evidence-scan finding can be confused for it. The gate scans `evidence` // only for a status in `EVIDENCE_SCANNED_STATUSES`, which `planed` is not, // and it resolves `producer` at any status, but every path this row's - // `producer` cites is objectui-attributed, so none of them is resolved - // against this checkout. + // `producer` cites is a `ui/` path that resolves, with every anchor present + // (objectui-attributed until #25712), so none of them trips anything. setStatus(root, 'field', 'useGrouping', 'planed'); return root; } diff --git a/packages/spec/scripts/liveness/empty-state.mts b/packages/spec/scripts/liveness/empty-state.mts index 9e4e2e7b46b..f4786218f68 100644 --- a/packages/spec/scripts/liveness/empty-state.mts +++ b/packages/spec/scripts/liveness/empty-state.mts @@ -402,8 +402,10 @@ export function checkEmptyState({ sources, registry, exists }: CheckInput): Chec // // Evidence is parsed with the liveness ledger's own `checkEvidence`, so the // two surfaces agree: prose around the paths is fine and encouraged, several - // paths may be cited, and a path attributed to another repo (`objectui: …`) - // is recorded without being resolved here. Reusing it also keeps this honest + // paths may be cited, and a path attributed to another repo (`cloud: …`) + // is recorded without being resolved here; one attributed to the retired + // `objectui` realm counts as neither, so it reads as missing evidence until it + // is spelled `ui/…`. Reusing it also keeps this honest // — the README promises evidence resolves "like the ledger's", and a // single-raw-path `exists()` would have quietly not. if (entry.semantics === 'closed' || entry.semantics === 'open') { diff --git a/packages/spec/scripts/liveness/evidence.mts b/packages/spec/scripts/liveness/evidence.mts index 06c16f0c843..9d29cc62581 100644 --- a/packages/spec/scripts/liveness/evidence.mts +++ b/packages/spec/scripts/liveness/evidence.mts @@ -22,20 +22,39 @@ // (`object.enable.clone` → a file that moved repos) sat there unnoticed. // // So: extract path-shaped tokens properly, and honour the cross-repo attribution -// the entries already write ("objectui: …"). What's left is signal. +// the entries already write ("cloud: …", and "objectui: …" until the renderer +// merged in as `ui/` — see RETIRED_REALMS). What's left is signal. -/** Top-level directories of THIS repo — the anchor for a repo-relative path claim. */ -export const REPO_ROOTS = ['apps', 'content', 'docker', 'docs', 'examples', 'packages', 'scripts', 'skills']; +/** + * Top-level directories of THIS repo — the anchor for a repo-relative path claim. + * `ui` is the renderer, merged in from the objectui repo (#25677): its paths are + * cited `ui/packages/…` and resolve like any other. + */ +export const REPO_ROOTS = ['apps', 'content', 'docker', 'docs', 'examples', 'packages', 'scripts', 'skills', 'ui']; /** * Realm markers an evidence string may use to attribute a path to another repo. - * `objectui` is the renderer repo; `cloud` is the closed EE runtime. `framework` - * switches back explicitly. These are already the house convention in prose — - * this makes them machine-read instead of decorative. + * `cloud` is the closed EE runtime. `framework` switches back explicitly. These + * are already the house convention in prose — this makes them machine-read + * instead of decorative. */ -export const FOREIGN_REALMS = ['objectui', 'cloud', 'ee']; +export const FOREIGN_REALMS = ['cloud', 'ee']; export const LOCAL_REALM = 'framework'; +/** + * Realm markers that USED to name another repo and now name a directory of this + * one, mapped to that directory. `objectui` merged in as `ui/` (#25677, #25712). + * A path still attributed to it is neither foreign nor local: counted foreign it + * would never fail, and read as local it would resolve against the wrong root — + * both trees have a `packages/core/`, so `objectui: packages/core/…` could pass + * on a file that never was the reader. So it is a bucket of its own, and the + * gate fails on it with the in-repo spelling as the remedy. A path already + * spelled `ui/…` is in-repo whatever prose precedes it: `objectui` survives in + * history prose ("read at objectui @sha"), and that word must not re-attribute + * the in-repo path after it. + */ +export const RETIRED_REALMS: Readonly> = { objectui: 'ui' }; + /** * Paths that are repo-rooted in shape but never present in the OPEN edition. * `@objectstack/service-ai` is the closed cloud runtime — `packages/services/` @@ -85,11 +104,23 @@ export interface EvidenceAnchor { symbol: string; } +/** A path attributed to a retired realm: `objectui: packages/x.ts`, now `ui/packages/x.ts`. */ +export interface RetiredPointer { + /** The retired realm marker, lower-cased (`objectui`). */ + realm: string; + /** The path exactly as attributed to it. */ + path: string; + /** The same path spelled in-repo — the remedy the gate prints. */ + inRepo: string; +} + export interface EvidenceScan { /** Repo-rooted paths attributed to THIS repo — these must resolve. */ local: string[]; /** Paths attributed to another repo (realm marker or foreign prefix) — not resolved here. */ foreign: string[]; + /** Paths attributed to a realm that merged into this repo (RETIRED_REALMS) — these FAIL. */ + retired: RetiredPointer[]; /** * The subset of `local` whose citation names a line, paired with that line — * the only citations a line-bound check can falsify. Deduped on `path:line`, @@ -156,11 +187,11 @@ function bareToken(raw: string): TokenParts { } /** - * Split an evidence string into local vs foreign path claims. + * Split an evidence string into local vs foreign (vs retired-realm) path claims. * - * A realm marker (`objectui:`, `(objectui`, `cloud:`) attributes the paths that - * FOLLOW it, and its scope ends at the next clause boundary (`;` or a closing - * paren) — so `"objectui X gates … (plugin-audit, packages/plugins/…/y.ts) …"` + * A realm marker (`cloud:`, `(cloud`, and the retired `objectui:`) attributes the + * paths that FOLLOW it, and its scope ends at the next clause boundary (`;` or a + * closing paren) — so `"cloud X gates … (plugin-audit, packages/plugins/…/y.ts) …"` * still resolves the framework path in the trailing clause. Anything that is not * repo-rooted (`app-shell/MetadataProvider.tsx`, `action-button/-group`) is * prose and is neither resolved nor reported. @@ -170,18 +201,21 @@ export function scanEvidence(evidence: string): EvidenceScan { const foreign: string[] = []; const citations: EvidenceCitation[] = []; const anchors: EvidenceAnchor[] = []; + const retired: RetiredPointer[] = []; let realm = LOCAL_REALM; for (const raw of String(evidence).split(/\s+/)) { const { path: token, line, anchor } = bareToken(raw); const asRealm = token.toLowerCase(); - if (FOREIGN_REALMS.includes(asRealm)) { realm = asRealm; continue; } + if (FOREIGN_REALMS.includes(asRealm) || Object.hasOwn(RETIRED_REALMS, asRealm)) { realm = asRealm; continue; } if (asRealm === LOCAL_REALM) { realm = LOCAL_REALM; continue; } if (PATH_RE.test(token)) { const isForeignPath = FOREIGN_PATH_PREFIXES.some((p) => token.startsWith(p)); - if (realm !== LOCAL_REALM || isForeignPath) foreign.push(token); + const into = Object.hasOwn(RETIRED_REALMS, realm) ? RETIRED_REALMS[realm] : null; + if (into !== null && !token.startsWith(`${into}/`)) retired.push({ realm, path: token, inRepo: `${into}/${token}` }); + else if (into === null && (realm !== LOCAL_REALM || isForeignPath)) foreign.push(token); else { local.push(token); // Every token is scanned, so a multi-citation entry ("…file.ts:267 (prose) @@ -201,6 +235,7 @@ export function scanEvidence(evidence: string): EvidenceScan { return { local: dedupe(local), foreign: dedupe(foreign), + retired: dedupeRetired(retired), localCitations: dedupeCitations(citations), localAnchors: dedupeAnchors(anchors), }; @@ -220,6 +255,16 @@ function dedupeCitations(cs: EvidenceCitation[]): EvidenceCitation[] { }); } +function dedupeRetired(rs: RetiredPointer[]): RetiredPointer[] { + const seen = new Set(); + return rs.filter((r) => { + const k = `${r.realm}:${r.path}`; + if (seen.has(k)) return false; + seen.add(k); + return true; + }); +} + function dedupeAnchors(as: EvidenceAnchor[]): EvidenceAnchor[] { const seen = new Set(); return as.filter((a) => { @@ -237,7 +282,7 @@ export interface EvidenceCheck extends EvidenceScan { /** Scan an evidence string and resolve its local paths against the filesystem. */ export function checkEvidence(evidence: unknown, exists: (path: string) => boolean): EvidenceCheck { - if (typeof evidence !== 'string') return { local: [], foreign: [], localCitations: [], localAnchors: [], missing: [] }; + if (typeof evidence !== 'string') return { local: [], foreign: [], retired: [], localCitations: [], localAnchors: [], missing: [] }; const scan = scanEvidence(evidence); return { ...scan, missing: scan.local.filter((p) => !exists(p)) }; } diff --git a/packages/spec/scripts/liveness/evidence.test.ts b/packages/spec/scripts/liveness/evidence.test.ts index b0bd308be2c..84c1628b04d 100644 --- a/packages/spec/scripts/liveness/evidence.test.ts +++ b/packages/spec/scripts/liveness/evidence.test.ts @@ -6,7 +6,15 @@ import { describe, it, expect } from 'vitest'; import { readFileSync, existsSync, readdirSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname, join, resolve } from 'node:path'; -import { checkCitationLines, checkEvidence, checkEvidenceAnchors, countLines, isSymbolNamed, scanEvidence } from './evidence.mts'; +import { + checkCitationLines, + checkEvidence, + checkEvidenceAnchors, + countLines, + isSymbolNamed, + RETIRED_REALMS, + scanEvidence, +} from './evidence.mts'; const here = dirname(fileURLToPath(import.meta.url)); const specRoot = resolve(here, '../..'); @@ -39,32 +47,39 @@ describe('scanEvidence — path extraction', () => { }); it('ignores prose that merely contains a slash', () => { - const r = scanEvidence('objectui: components action-button/-group/-icon/-menu gate the Button'); + const r = scanEvidence('cloud: components action-button/-group/-icon/-menu gate the Button'); expect(r.local).toEqual([]); expect(r.foreign).toEqual([]); }); it('ignores a path that is not repo-rooted (another repo\'s internal layout)', () => { - expect(scanEvidence('objectui: app-shell/MetadataProvider.tsx reads it').local).toEqual([]); + expect(scanEvidence('cloud: app-shell/MetadataProvider.tsx reads it').local).toEqual([]); + }); + + it('reads `ui/` as a root of THIS repo — the renderer merged in from objectui (#25677)', () => { + const r = scanEvidence('ui/packages/app-shell/src/x.tsx#XView'); + expect(r.local).toEqual(['ui/packages/app-shell/src/x.tsx']); + expect(r.localAnchors).toEqual([{ path: 'ui/packages/app-shell/src/x.tsx', symbol: 'XView' }]); + expect(r.foreign).toEqual([]); }); }); describe('scanEvidence — cross-repo attribution', () => { - it('attributes paths after an `objectui:` marker to objectui', () => { - const r = scanEvidence('objectui: packages/app-shell/src/views/RecordDetailView.tsx:573'); - expect(r.foreign).toEqual(['packages/app-shell/src/views/RecordDetailView.tsx']); + it('attributes paths after a `cloud:` marker to cloud', () => { + const r = scanEvidence('cloud: packages/marketplace/src/publish-gate.ts:573'); + expect(r.foreign).toEqual(['packages/marketplace/src/publish-gate.ts']); expect(r.local).toEqual([]); }); it('handles the parenthesised marker form', () => { - const r = scanEvidence('registered into ActionEngine.shortcuts[] (objectui packages/core/src/actions/ActionEngine.ts:150) but no caller'); - expect(r.foreign).toEqual(['packages/core/src/actions/ActionEngine.ts']); + const r = scanEvidence('enforced at publish (cloud packages/marketplace/src/publish-gate.ts:150) but not at load'); + expect(r.foreign).toEqual(['packages/marketplace/src/publish-gate.ts']); expect(r.local).toEqual([]); }); it('ends a realm at the clause boundary so a later framework path is still resolved', () => { const r = scanEvidence( - 'objectui RecordDetailView gates the tab (historyEnabled memo); pairs with packages/plugins/plugin-audit/src/audit-writers.ts', + 'cloud PublishGate refuses the tier (HTTP 422); pairs with packages/plugins/plugin-audit/src/audit-writers.ts', ); expect(r.foreign).toEqual([]); expect(r.local).toEqual(['packages/plugins/plugin-audit/src/audit-writers.ts']); @@ -79,16 +94,59 @@ describe('scanEvidence — cross-repo attribution', () => { }); it('lets `framework` switch attribution back explicitly', () => { - const r = scanEvidence('objectui packages/app-shell/src/x.tsx framework packages/runtime/src/y.ts'); - expect(r.foreign).toEqual(['packages/app-shell/src/x.tsx']); + const r = scanEvidence('cloud packages/marketplace/src/x.ts framework packages/runtime/src/y.ts'); + expect(r.foreign).toEqual(['packages/marketplace/src/x.ts']); expect(r.local).toEqual(['packages/runtime/src/y.ts']); }); }); +// #25712: objectui merged into this repo as `ui/` (#25677). Its marker is RETIRED, +// not deleted: a path still attributed to it lands in a bucket of its own that +// the gate fails on, because neither of the other two readings is safe — foreign +// never fails, and local would resolve `packages/core/…` against this repo's own +// `packages/core/`, a file that never was the reader. +describe('scanEvidence — the retired objectui realm (#25712)', () => { + it('maps objectui onto ui/ — the one retired realm', () => { + expect(RETIRED_REALMS).toEqual({ objectui: 'ui' }); + }); + + it('puts a path attributed to objectui in `retired`, with its in-repo spelling — never in `local` or `foreign`', () => { + for (const ev of [ + 'objectui: packages/core/src/actions/x.ts:573', + 'objectui @f8a9d0fb: packages/core/src/actions/x.ts#XRunner', + 'registered into ActionEngine (objectui packages/core/src/actions/x.ts:150) but no caller', + ]) { + const r = scanEvidence(ev); + expect(r.retired, ev).toEqual([ + { realm: 'objectui', path: 'packages/core/src/actions/x.ts', inRepo: 'ui/packages/core/src/actions/x.ts' }, + ]); + expect(r.local, ev).toEqual([]); + expect(r.foreign, ev).toEqual([]); + expect(r.localCitations, ev).toEqual([]); + expect(r.localAnchors, ev).toEqual([]); + } + }); + + it('reads a `ui/` path as local even after the word objectui — history prose must not re-attribute it', () => { + const r = scanEvidence('ui/packages/layout/src/x.tsx (:316 at objectui @940ba24) and objectui ui/packages/layout/src/y.tsx#YShell'); + expect(r.local).toEqual(['ui/packages/layout/src/x.tsx', 'ui/packages/layout/src/y.tsx']); + expect(r.retired).toEqual([]); + expect(r.localCitations).toEqual([]); + }); + + it('ends the retired realm at the clause boundary like any other', () => { + const r = scanEvidence('objectui RecordDetailView gates the tab (historyEnabled memo); pairs with packages/plugins/plugin-audit/src/audit-writers.ts'); + expect(r.retired).toEqual([]); + expect(r.local).toEqual(['packages/plugins/plugin-audit/src/audit-writers.ts']); + }); +}); + describe('checkEvidence', () => { it('reports only unresolvable LOCAL paths', () => { - const r = checkEvidence('packages/a/src/x.ts (prose) objectui: packages/b/src/y.tsx', none); + const r = checkEvidence('packages/a/src/x.ts (prose) cloud: packages/b/src/y.tsx', none); expect(r.missing).toEqual(['packages/a/src/x.ts']); + // A retired-realm path is not resolved either — the gate fails it on its own list. + expect(checkEvidence('objectui: packages/b/src/y.tsx', none).missing).toEqual([]); }); it('is silent when every local path resolves', () => { @@ -96,7 +154,7 @@ describe('checkEvidence', () => { }); it('tolerates a non-string evidence value', () => { - expect(checkEvidence(undefined, none)).toEqual({ local: [], foreign: [], localCitations: [], localAnchors: [], missing: [] }); + expect(checkEvidence(undefined, none)).toEqual({ local: [], foreign: [], retired: [], localCitations: [], localAnchors: [], missing: [] }); expect(checkEvidence(42, none).missing).toEqual([]); }); }); @@ -149,14 +207,14 @@ describe('scanEvidence — line citations', () => { }); it('never bounds a FOREIGN citation — those files are legitimately absent here', () => { - const r = scanEvidence('objectui: packages/app-shell/src/views/RecordDetailView.tsx:573'); + const r = scanEvidence('cloud: packages/marketplace/src/publish-gate.ts:573'); expect(r.localCitations).toEqual([]); }); - it('still reduces a realm marker written `objectui:` rather than reading it as a line', () => { + it('still reduces a realm marker written `cloud:` rather than reading it as a line', () => { // The trailing-punctuation pass strips `:`; only a digit suffix is a line. - const r = scanEvidence('objectui: packages/app-shell/src/x.tsx:12'); - expect(r.foreign).toEqual(['packages/app-shell/src/x.tsx']); + const r = scanEvidence('cloud: packages/marketplace/src/x.ts:12'); + expect(r.foreign).toEqual(['packages/marketplace/src/x.ts']); expect(r.localCitations).toEqual([]); }); @@ -196,8 +254,8 @@ describe('scanEvidence — symbol anchors (#12516)', () => { }); it('never collects a FOREIGN anchor — the file is legitimately absent here', () => { - const r = scanEvidence('objectui: packages/app-shell/src/x.tsx#RecordDetailView'); - expect(r.foreign).toEqual(['packages/app-shell/src/x.tsx']); + const r = scanEvidence('cloud: packages/marketplace/src/x.ts#PublishGate'); + expect(r.foreign).toEqual(['packages/marketplace/src/x.ts']); expect(r.localAnchors).toEqual([]); }); @@ -370,6 +428,43 @@ describe('shipped ledgers', () => { // two-number line stays pinned in check-liveness.test.ts. If a `path:NNN` // citation is ever written again the ledger has a new population — restore a // contract test here rather than trusting the unit tests to cover shipped data. + // + // Restored 2026-10-26 (#25712) on exactly that instruction: re-rooting the + // objectui pointers under `ui/` brought `path:NNN` citations back into the + // LOCAL population — only the ones whose cited block is byte-identical at one + // offset of today's file, relocated there; the rest dropped the line and keep + // the old reading as prose. Both fields are walked, as the gate walks them. + it('every local `path:NNN` citation is inside its file (#11210, restored by #25712)', () => { + const outOfRange: string[] = []; + let citations = 0; + const lineCount = (p: string): number | null => { + const f = join(repoRoot, p); + return existsSync(f) ? countLines(readFileSync(f, 'utf8')) : null; + }; + for (const f of readdirSync(ledgerRoot).filter((x) => x.endsWith('.json'))) { + const ledger = JSON.parse(readFileSync(join(ledgerRoot, f), 'utf8')); + const visit = (key: string, entry: any) => { + for (const field of ['evidence', 'producer']) { + if (typeof entry?.[field] !== 'string') continue; + const scan = scanEvidence(entry[field]); + citations += scan.localCitations.length; + checkCitationLines(scan, lineCount).forEach((c) => + outOfRange.push(`${ledger.type}/${key} → ${c.path}:${c.line} (file has ${c.lines})`), + ); + } + }; + for (const [key, entry] of Object.entries(ledger.props || {})) { + visit(key, entry); + for (const [ck, centry] of Object.entries(entry?.children || {})) visit(`${key}.${ck}`, centry); + } + } + expect(outOfRange).toEqual([]); + // Non-vacuity, and NOT a pin on the population: the floor is the one shape + // a degraded parser cannot fake. When a later sweep converts these to + // `#symbol` anchors and the count reaches zero, retire this case the way + // #13043 did, rather than parking a citation to keep it green. + expect(citations).toBeGreaterThan(0); + }); it('every local `path#symbol` anchor names a symbol its file contains (#12516)', () => { const bad: string[] = []; diff --git a/packages/spec/scripts/liveness/key-mention.baseline.json b/packages/spec/scripts/liveness/key-mention.baseline.json index ce57fd6b887..7f4cbd3d47f 100644 --- a/packages/spec/scripts/liveness/key-mention.baseline.json +++ b/packages/spec/scripts/liveness/key-mention.baseline.json @@ -9,6 +9,71 @@ "entry": "email_template/fromOverride", "path": "packages/plugins/plugin-email/src/email-service.ts", "why": "A compound CHILD-key remap, which no fold of the parent key can reach: the bridge persists `fromOverride.address` → `from_address` and `fromOverride.name` → `from_name` (bootstrap-declared-email-templates.ts:85-88), so the send path reads `row.from_address` / `row.from_name` at :1282-1283 and the parent key `fromOverride` appears nowhere in the file. The entry's OTHER citation is anchored — bootstrap-declared-email-templates.ts names `tpl.fromOverride` — so the read is not in doubt; only this second, downstream pointer is unfalsifiable by name. Its two siblings `bodyHtml`/`bodyText` need no row because the fold reaches `body_html`/`body_text` directly." + }, + { + "entry": "page/print.paperSize", + "path": "ui/packages/components/src/renderers/layout/page.tsx", + "why": "The renderer names only the PARENT key `print`: `isPagePrintDeclaration(schema.print)` gates whether the page is a print page, and the declaration is handed whole to `buildPagePrintStylesheet`. The member `paperSize` is read in page-print.ts, the entry's other, anchored citation; this pointer is the mount site, unfalsifiable by the member's name. Surfaced when the citation moved from objectui into ui/ (#25712)." + }, + { + "entry": "page/print.orientation", + "path": "ui/packages/components/src/renderers/layout/page.tsx", + "why": "The renderer names only the PARENT key `print`: `isPagePrintDeclaration(schema.print)` gates whether the page is a print page, and the declaration is handed whole to `buildPagePrintStylesheet`. The member `orientation` is read in page-print.ts, the entry's other, anchored citation; this pointer is the mount site, unfalsifiable by the member's name. Surfaced when the citation moved from objectui into ui/ (#25712)." + }, + { + "entry": "page/print.margins.right", + "path": "ui/packages/components/src/renderers/layout/page.tsx", + "why": "The renderer names only the PARENT key `print`: `isPagePrintDeclaration(schema.print)` gates whether the page is a print page, and the declaration is handed whole to `buildPagePrintStylesheet`. The member `margins.right` is read in page-print.ts, the entry's other, anchored citation; this pointer is the mount site, unfalsifiable by the member's name. Surfaced when the citation moved from objectui into ui/ (#25712)." + }, + { + "entry": "page/print.margins.bottom", + "path": "ui/packages/components/src/renderers/layout/page.tsx", + "why": "The renderer names only the PARENT key `print`: `isPagePrintDeclaration(schema.print)` gates whether the page is a print page, and the declaration is handed whole to `buildPagePrintStylesheet`. The member `margins.bottom` is read in page-print.ts, the entry's other, anchored citation; this pointer is the mount site, unfalsifiable by the member's name. Surfaced when the citation moved from objectui into ui/ (#25712)." + }, + { + "entry": "page/print.pageNumbers", + "path": "ui/packages/components/src/renderers/layout/page.tsx", + "why": "The renderer names only the PARENT key `print`: `isPagePrintDeclaration(schema.print)` gates whether the page is a print page, and the declaration is handed whole to `buildPagePrintStylesheet`. The member `pageNumbers` is read in page-print.ts, the entry's other, anchored citation; this pointer is the mount site, unfalsifiable by the member's name. Surfaced when the citation moved from objectui into ui/ (#25712)." + }, + { + "entry": "page/print.repeatTableHeaders", + "path": "ui/packages/components/src/renderers/layout/page.tsx", + "why": "The renderer names only the PARENT key `print`: `isPagePrintDeclaration(schema.print)` gates whether the page is a print page, and the declaration is handed whole to `buildPagePrintStylesheet`. The member `repeatTableHeaders` is read in page-print.ts, the entry's other, anchored citation; this pointer is the mount site, unfalsifiable by the member's name. Surfaced when the citation moved from objectui into ui/ (#25712)." + }, + { + "entry": "page/print.avoidBreakInside", + "path": "ui/packages/components/src/renderers/layout/page.tsx", + "why": "The renderer names only the PARENT key `print`: `isPagePrintDeclaration(schema.print)` gates whether the page is a print page, and the declaration is handed whole to `buildPagePrintStylesheet`. The member `avoidBreakInside` is read in page-print.ts, the entry's other, anchored citation; this pointer is the mount site, unfalsifiable by the member's name. Surfaced when the citation moved from objectui into ui/ (#25712)." + }, + { + "entry": "dashboard/widgets.chartConfig.showDataLabels", + "path": "ui/packages/plugin-dashboard/src/DatasetWidget.tsx", + "why": "The widget names the HELPER, not the key: it imports `chartConfigPresentation` from @object-ui/core and spreads its result onto the chart schema. The helper reads `showDataLabels` in core's chart-presentation.ts, the entry's other, anchored citation. Surfaced when the citation moved from objectui into ui/ (#25712)." + }, + { + "entry": "dashboard/widgets.chartConfig.annotations", + "path": "ui/packages/plugin-dashboard/src/DatasetWidget.tsx", + "why": "The widget names the HELPER, not the key: it imports `chartConfigPresentation` from @object-ui/core and spreads its result onto the chart schema. The helper reads `annotations` in core's chart-presentation.ts, the entry's other, anchored citation. Surfaced when the citation moved from objectui into ui/ (#25712)." + }, + { + "entry": "dashboard/widgets.chartConfig.interaction", + "path": "ui/packages/plugin-dashboard/src/DatasetWidget.tsx", + "why": "The widget names the HELPER, not the key: it imports `chartConfigPresentation` from @object-ui/core and spreads its result onto the chart schema. The helper reads `interaction` in core's chart-presentation.ts, the entry's other, anchored citation. Surfaced when the citation moved from objectui into ui/ (#25712)." + }, + { + "entry": "view/list.inlineEdit", + "path": "ui/packages/plugin-grid/src/ObjectGrid.tsx", + "why": "A RENAMED key: ListView forwards `inlineEdit` to the grid node as `editable`, and ObjectGrid reads `schema.editable` (the cell editor, renderCellEditor, and the save/cancel column). The entry's ListView citation names `inlineEdit`. Surfaced when the citation moved from objectui into ui/ (#25712)." + }, + { + "entry": "view/form.subforms", + "path": "ui/packages/plugin-form/src/MasterDetailForm.tsx", + "why": "A RENAMED key: ObjectForm hands `subforms` to MasterDetailForm as `details`, and this file reads `details` (`MasterDetailDetailConfig[]`, one panel per entry). The entry's ObjectForm citation names `subforms`. Surfaced when the citation moved from objectui into ui/ (#25712)." + }, + { + "entry": "translation/flows.screens", + "path": "ui/packages/app-shell/src/views/ScreenView.tsx", + "why": "Draws the screen FlowRunner already localized, so it names the overlaid field keys `label` / `placeholder` / `inlineHelpText`, never the bundle group `screens`; FlowRunner.tsx#localizeScreen, the entry's anchored citation, reads `screens`. Surfaced when the citation moved from objectui into ui/ (#25712)." } ] } diff --git a/packages/spec/scripts/liveness/key-mention.mts b/packages/spec/scripts/liveness/key-mention.mts index 6c2521e967a..7456b732f23 100644 --- a/packages/spec/scripts/liveness/key-mention.mts +++ b/packages/spec/scripts/liveness/key-mention.mts @@ -199,9 +199,9 @@ export const KEY_MENTION_GUIDANCE = [ ' promoted into another package (#10101), or a citation that named a', ' plausible SIBLING file all leave the old path resolving perfectly.', ' Find the file that names the key and cite that one, with a line.', - ' 2. RE-CLASSIFY it — if no file in this repo names the key, the consumer (or,', - ' for a `planned` / `experimental` entry, the refuser) may be in another', - ' realm (prefix it `objectui:` and pin the commit, per the ledger README) or', + ' 2. RE-CLASSIFY it — if no file in this repo names the key (`ui/` included), the', + ' consumer (or, for a `planned` / `experimental` entry, the refuser) may be in', + ' another realm (prefix it `cloud:` and pin the commit, per the ledger README) or', ' may not exist at all, in which case the honest verdict is `dead` under', ' ADR-0049 rather than a repoint to a plausible survivor.', ' 3. EXEMPT it — only when the consumer genuinely reads the key under a name',