Skip to content

spec: re-derive the liveness ledger before its verdicts start warning authors — 3 errors in 33 rows (9.1%), list.tabs flipped to dead - #16542

Merged
huangyiirene merged 3 commits into
mainfrom
claude/issue-16362-liveness-ledger-audit
Sep 7, 2026
Merged

spec: re-derive the liveness ledger before its verdicts start warning authors — 3 errors in 33 rows (9.1%), list.tabs flipped to dead#16542
huangyiirene merged 3 commits into
mainfrom
claude/issue-16362-liveness-ledger-audit

Conversation

@huangyiirene

Copy link
Copy Markdown
Collaborator

Fixes #16362

Clause-②: no — ledger JSON rows plus the generated count table. No export, no key, no accept-set movement. Confirmed mechanically, and both gates proved able to go red (below).

⭐ The error rate

3 wrong out of 33 dead rows re-derived — 9.1%.
On the subset that can actually reach an author: 3 of 22 — 13.6%.

Every row was re-derived against a literal-sha anchor (objectstack 5d55afec4d, objectui a472b071 from this repo's .objectui-sha) with a positive control fired on the same instrument and the same corpus before any zero was read as a reading. Where a control went dark the reading was discarded and the corpus corrected, not reported.

⚠️ The number that changes what the flip means

The card says "the 90 dead rows". The ledger holds 94 at the pinned base — and, more importantly, they are not one population:

rows can a dead warning ever reach an author?
retiredKey tombstones 72 (77%) No — authoring the key is already a tsc error and a parse error
authorable keys 22 (23%) Yes — these are the only rows the flip actually speaks to

So "every dead row starts warning every downstream author" is true of 22 rows, not 94. That reframes the blast radius by roughly 4x, and it is why this audit covers all 22 authorable rows rather than 10 sampled ones. The 11 tombstones in the sample are there to keep the denominator honest, not to pad it.

Sampled 33 of 94 (35%), spanning 15 of the 22 files that carry dead rows.

Scope item 1 — list.tabs: livedead

The old note was wrong in both directions in one sentence, which is the two-direction rot the ledger README warns an entry accumulates:

objectui: TabBar.tsx — icon/visible/pinned/filter wired (audit L15). Sub-key tabs[].order is NOT used for sorting (audit L20) — dead sub-surface.

  • Overstated. TabBar reads those sub-keys, but nothing mounts TabBar. At objectui a472b071, every TabBar render site in the whole tree is the component's own definition or one of its two test files; zero production sites, zero references under apps/, no component-registry entry. ListView never reads tabs off the view schema — its only list.tabs token is a translation-table label, and the toolbar slot whose comments still say "view tabs above" holds UserFilters alone. This is the seed.env producer-gap shape: a real consumer no producer ever calls. Firing control: the same search for the JSX open-tag spelling of ListView returns production render sites on the same corpus and instrument; the pathspec was separately proved to reach nested app-shell directories before any zero was read.
  • Understated. tabs[].order is sorted on — TabBar.tsx#getVisibleTabs ends .sort((a, b) => (a.order ?? 0) - (b.order ?? 0)). The note called it a dead sub-surface.

Independent corroboration, already in this repo: packages/cli/src/utils/i18n-extract.ts scopes its tab-label extractor to a page's interfaceConfig.userFilters.tabs and states in so many words that ListViewSchema.tabs has no reader in either repo — deliberately emitting no scaffolding keys for it.

Why not live on the two author-time readers. packages/lint/src/validate-list-view-field-refs.ts#checkTabs and packages/metadata-protocol/src/metadata-diagnostics.ts both walk tabs[].filter[].field for reference integrity, and the protocol normalizes filter operators inside it. None delivers the key's declared effect ("Tab definitions for multi-tab view interface"). That is the route_generation precedent verbatim — an enum validated at the door and then ignored is accept/reject, which this ledger has always kept separate from liveness — and the opposite of dashboard.widgets[].suppressWarnings, whose declared effect is the lint read.

🔵 The hotcrm premise is UPHELD — nothing to file there

hotcrm#1307's test/view-tab-label-inert.test.ts asserts the object-view switcher never reads list.tabs. The switcher is plugin-view/src/ViewTabBar.tsx, which takes a views: ViewTabItem[] prop (saved views) and reads no tabs key at all. My re-derivation lands on the same side as that test, so the losing side is view.json — which is in scope and is corrected here.

⚠️ Inherited, not re-measured: hotcrm is outside this session's repo scope, so what that test contains is taken from the card and #16094, not read. What is measured here is the objectui switcher the test describes.

Scope item 2 — the sampled dead audit

The three errors, all in validation.json, all the same shape. The 2026-08-10 sweep upheld dead on reachability, not on the read: ValidationPreview genuinely read and rendered label, description and tags, but the only route that mounted it was the standalone validation resource door, and ADR-0088 had retired that kind — so on the governed path (a rule embedded in its object) the preview was never handed a draft.

That note named its own falsifier, and the re-derivation found it had fired:

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 did both halves. The standalone door is gone (anchors.ts now records that there is no standalone Validations group and no create affordance), and the preview is wired into the embedded editor instead: EmbeddedItemEditor resolves getMetadataPreview(editAs) and mounts it with draft set to the live authored value, while the __object_validation anchor sets editAs to validation. The read point that never ran, runs. Under the 2026-08-10 ruling these are display keys and are settled outright. They stay docs-shaped and are still not author-warned.

The 30 rows that held did so on measurement, not assumption. Highlights:

  • realtime_subscription.* — all six settled by one producer measurement: SubscriptionSchema has zero references outside packages/spec in either repo, so nothing ever parses one. Controls fired both sides (ViewSchema outside spec; ViewTabSchema in 8 objectui files). The sub.channel read in in-memory-realtime-adapter.ts is the adapter's own record type, exactly as that row's note already predicted.
  • permission.rowLevelSecurity[].label / .description / .tagsPermissionPreview only counts the array (${rls.length} RLS rules) and descends no further. Same instrument that re-graded validation, opposite answer.
  • view.label — held on both of its stated re-open conditions still being unmet: the named-view tab bar returns null when the entry map holds one entry or fewer, and the preview injects exactly one, and the registered ObjectViewRenderer passes no renderListView.
  • qa.scenarios[].tagsos test ships three flags (--url, --token, --fail-on-empty); none selects scenarios. Correctly dead, and the sharpest genuine enforce-or-remove row in the set.

One row flagged, not changed: manifest.integrity is dead while os plugin publish refuses on a digest mismatch — a real local enforcer. Its own note is aware of this and explicitly routes the flip to the #11331 chain rather than a rider. Left alone as out of scope, but the maintainer should know the ledger holds a self-declared open question there.

Scope item 3 — the one live-elsewhere row

manifest.runtime re-derived. Local half holds exactly: the only reads are the two CLI echo lines the note names (build.ts:127, publish.ts:110 — the latter drifted 16 lines, symbol intact), both runtime: ${manifest.runtime ?? 'unset'}, and there is no runtime === dispatch anywhere in the repo. Control fired (transport === returns a hit on the same instrument). All four gate-executable criteria pass, attestation 9 days old against a 180-day window.

⚠️ Foreign half inherited, not re-measured — the cloud enforcer is unreachable from this seat, the same constraint class as hotcrm. Its re-attestation clock is unaffected by this PR.

Gates

check:liveness green — and it earned it: it caught a real defect in my first draft (six producer pointers that did not resolve, because the realm marker attributes paths only up to the next clause boundary and I wrote one marker for three clauses). That is a useful counterweight to the measured false-green on row contents: the gate does check evidence shape, it simply cannot check a verdict. check:objectui-pin-citations independently validates all 8 asserting objectui citations against .objectui-sha.

Clause ② proved, not read as a bare green — each gate mutated and re-run with no rebuild in between, so neither leg is the vacuous kind that moves its own baseline:

gate clean ablated gate's own verdict when red
check:api-surface exit 0, "public API surface + factory signatures unchanged ✓" exit 1 "public API changed: 0 breaking (removed/narrowed), 1 added."
check:authorable-surface exit 0, "🔒 authorable-defaults/ verified against upstream 5d55afe — 1211 default(s) unchanged" exit 1 "❌ Failed to read authorable-defaults/: ai.json has no defaults array"

Both mutations were proved on disk by blob-hash inequality against the HEAD blob, and both restores proved by hash equality plus an empty git diff HEAD — never by an exit code.

54 derived gate families, 53 run, all green. The one not run is check:dual-build-cjs-loads, which exited 3 printing "⛔ This is NOT a pass: nothing was measured" — it needs a whole-repo pnpm build (81 packages lack dist). Recorded as NOT MEASURED and declared to CI, not as a pass.

Lint, narrowed and declared. All four changed paths are outside eslint's configured population — its own isPathIgnored says so, and --format json returns 4 results, 0 errors, 4 warnings, every one of them "File ignored because no matching configuration was supplied". The config never enables type-aware linting for any file (stated at eslint.config.mjs:327, with its own measured positive control), so this diff cannot move the verdict on a file it did not touch. Measured against final commit da68a1d.

Changeset

Present — .changeset/liveness-ledger-reaudit-16362.md, patch on @objectstack/spec. Four ledger rows changed, so one is owed; skip-changeset would be wrong here.


Generated by Claude Code

…/tags to live, view list.tabs to dead

Ledger audit ahead of the authorWarn flip. Three corrections, each re-derived
against a literal-sha anchor (objectstack 5d55afe, objectui a472b071) with a
firing positive control on the same instrument and corpus.

validation.label / .description / .tags: dead -> live. The 2026-08-10 note
upheld dead on REACHABILITY and named its own falsifier — objectui#4132 wiring
ValidationPreview into the embedded editor. That has landed: the standalone
door is gone and EmbeddedItemEditor now resolves getMetadataPreview(editAs) and
mounts the preview on the live draft, so the read point that never ran, runs.
Display keys, settled outright by the previews ruling.

view list.tabs: live -> dead. The old note was wrong in both directions. TabBar
reads the sub-keys but nothing mounts it — zero production render sites in the
whole objectui tree — so authoring list.tabs draws no tab bar; and tabs[].order
IS sorted on, which the note called a dead sub-surface. The hotcrm#1307 premise
is upheld, not contradicted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T6HeZvT9wdSJD1ZxJb5Eno
…ion producer chain

The realm marker attributes paths only up to the next clause boundary, so the
second and third clauses' paths were being resolved as repo-local and
check:liveness reported six unresolvable producer pointers.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T6HeZvT9wdSJD1ZxJb5Eno
@github-actions github-actions Bot added the size/s label Sep 7, 2026
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️ 3 changed file(s) yielded no anchor (packages/spec/liveness/state-counts.md, packages/spec/liveness/validation.json, packages/spec/liveness/view.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 3 changed file(s) yielded no anchor (packages/spec/liveness/state-counts.md, packages/spec/liveness/validation.json, packages/spec/liveness/view.json) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s tooling

Projects

None yet

2 participants