Skip to content

docs(audits): the strictness ledger stops asserting a union flattening the tree no longer does - #16766

Merged
zhuangjianguo merged 2 commits into
mainfrom
claude/issue-14977-strictness-ledger-stale-flattening
Sep 8, 2026
Merged

docs(audits): the strictness ledger stops asserting a union flattening the tree no longer does#16766
zhuangjianguo merged 2 commits into
mainfrom
claude/issue-14977-strictness-ledger-stale-flattening

Conversation

@claude

@claude claude Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Part of #14977 — this delivers the card's narrow ask only. The card's other half, "a decision by the ledger's owner on how a row records a limitation that was later lifted elsewhere", is deliberately not settled here, so the card stays open for that ruling. See Scope below.

Three clauses in docs/audits/2026-07-unknown-key-strictness-ledger.md asserted, in the present tense, that a strict union branch's prescription is flattened away before it reaches an author. The tree has not done that since 2026-08-05. Each clause is corrected in place and now names packages/spec/src/shared/union-branch-policy.ts as what lifted it.

Exactly three lines change — :676, :737, :940, the three the card names. 3 insertions, 3 deletions; the file is 1870 lines before and after. The os-regen companion .counts.md is untouched.

Green is not the evidence here

pnpm --filter @objectstack/spec check:strictness-ledger grades row structure and the generated counts, not prose, and is green either way. It is reported below for completeness and proves nothing about this diff. The evidence is the reproduction.

What the tree actually does — measured on this branch at 88717a548, @objectstack/spec freshly built

A. defineStack, the state-machine.zod.ts row's OWN door (AgentSchema.lifecycle -> StateNode.entry[] -> ActionRefSchema, a union whose object arm is strict), given entry: [{ type: 'log', parms: { a: 1 } }]:

defineStack validation failed (1 issue):

  ✗ agents.0.lifecycle.states.idle.entry.0: Invalid input
    ✗ agents.0.lifecycle.states.idle.entry.0: Unrecognized key(s) on this action reference: `parms`. Did you mean `parms` → `params`? Until this shape was closed, an undeclared key here was dropped silently ...

The key, the surface and the rename all arrive. The union's cost is one wrapper line, not a lost message.

B. zodIssuesToFields, the wire door #5014 named — same body, prescription delivered as a second entry:

[
  { "field": "agents.0.lifecycle.states.idle.entry.0", "code": "invalid_shape", "message": "Invalid input" },
  { "field": "agents.0.lifecycle.states.idle.entry.0", "code": "unknown_field", "message": "Unrecognized key(s) on this action reference: `parms`. Did you mean `parms` → `params`? ..." }
]

C. what zod itself still raises at the top — so the parenthesis each corrected row KEEPS is still true:

code=invalid_union path=["agents",0,"lifecycle","states","idle","entry",0] message="Invalid input" branches=2

D. the chart.zod.ts row's own site, with no packages/lint anywhere in the pathChartAggregateSchema.safeParse({ field: 'amount', function: 'sum', groupBy: { field: 'stage', dateGranularty: 'month' } }) rendered by spec's own formatZodError:

chart aggregate (1 issue):

  ✗ groupBy: Invalid input
    ✗ groupBy: Unrecognized key(s) on this chart groupBy: `dateGranularty`. Did you mean `dateGranularty` → `dateGranularity`? ...

Mechanism: formatZodIssue (packages/spec/src/shared/error-map.zod.ts:254) descends invalid_union and ranks the branches through selectUnionBranches; zodIssuesToFields (packages/spec/src/api/zod-issues-to-fields.ts:213) calls the same function. Since #8318 there is one implementation of that policy, packages/spec/src/shared/union-branch-policy.ts. packages/spec/src/automation/state-machine.zod.ts:90-93 already records the lift in its own JSDoc — the ledger row is the copy that was never updated.

The probe was a scratch file, run and deleted; it is not committed and nothing in this diff is a test.

Provenance — why the three clauses are NOT corrected the same way

git log -S on each clause, against the dates the three flatteners landed:

clause written true when written?
:737 state-machine.zod.ts — "which formatZodError then flattens away" 2026-08-04, 5278e11f1 (批 10) yes — expired the next day
:940 view.zod.ts — "the #5014 flattening, so the key that caused it is not in the message the author sees" 2026-08-04, 8064b079f (#5114 hotfix) yes — expired the next day
:676 chart.zod.ts — "reaches an author only through packages/lint/src/zod-issue-format.ts's arm unpacking" 2026-08-08, e0f300ba5 (#5583) NO — already false the day it was written

The three flatteners the campaign filed: #4971 formatZodError at 546ab3c49, 2026-08-05 · #5014 the wire mapper at 2d2530300, 2026-08-05 · #5341 the CLI at 3d94141ec, 2026-08-04. git merge-base --is-ancestor 546ab3c49 e0f300ba5 exits 0, so spec's own renderer was already unpacking in the tree of the commit that wrote the chart clause.

That asymmetry decides the edit shape:

Scope — what was deliberately NOT touched

The convention question is not settled, and did not have to be. The file already demonstrates its own idiom for a verdict that later expired, in two places outside this diff: the chart.zod.ts row's "✅ That fix landed at #5020, and this row's no gate verdict is spent", and the api/ row's "the flat wire verdict this row used to carry was correct when written and then silently expired, which is the finding". This diff applies that existing idiom per clause. It adds no rule to the Format contract section and rewrites no row.

Not touched, on purpose:

Gates

Derived, not guessed: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on the merged tree, then reconciled with --ran.

✓ dispatch-gates --ran: 17 derived famil(ies) accounted for — 17 run, 0 NOT-MEASURED.
Run reconciliation — 17 derived, 17 run, 0 NOT-MEASURED, 0 UNRUN.

All 17 exit 0. Two verdict lines worth quoting (each printed by the gate itself, not read from $?):

✓ strictness ledger: 60 file(s) across 5 triaged director(ies) — every row names a live sited file, no undeclared schema files.
✓ docs/audits/2026-07-unknown-key-strictness-ledger.counts.md is current — 444 site(s) measured, 1 authorable strip site(s) left.
check-nul-bytes: OK (scanned 8275 text file(s) -- 8275 tracked, 0 untracked-not-ignored; skipped 7 binary; no raw ASCII control bytes).

One gate first returned exit 3check:doc-formula-expressions, PREREQUISITE NOT MET, its own words for "nothing was measured". @objectstack/formula and @objectstack/lint were built as the gate instructed and it was re-run: exit 0. It is recorded as measured only for that second run.

The first derivation ran on a stale tree and said so; origin/main was fetched and merged (88717a548) and the derivation re-taken, which cleared the warning.

No changeset

skip-changeset. Nothing published moves: docs/audits/** appears in no package's files[] (checked across all 70 packages that declare one — zero mention docs or ..), and the root package is private: true. The changed file is a repo-internal audit document.

验收备注

A fourth site carries the same stale claim and is NOT corrected here. docs/audits/2026-07-unknown-key-strictness-ledger.md:973-978, in the ui/ batch narrative:

(a) The zod-4 union collapse is now load-bearing on this file. ... What carries the named surface and the rename to the author is packages/lint/src/zod-issue-format.ts's arm unpacking, which #5020 had already built; a strict object arm inside a union is only as loud as its consumer's unpacking, and that is a general fact about this campaign's remaining union sites, not a chart detail.

It is the same claim as :676, from the same commit (e0f300ba5, 2026-08-08), and therefore also false the day it was written. It is left alone because it is framed as "Two things #5583 recorded that a later batch will need" — a record of what its own batch recorded, which is exactly the shape this PR was told not to touch — and because the card and the dispatch both name three sentences, not four. Flagged rather than fixed: the ledger owner should decide it in the same pass as the convention question, since after this PR the file carries a corrected chart row and an uncorrected restatement of it 300 lines below. Noted, not filed — a stale prose sentence is not a reproducible defect, a declared-contract violation, or a metadata-authoring trap.

The ledger never names #4971. The state-machine.zod.ts row said "filed, not fixed here" with no number, which is part of why the filing was hard to follow to its fix. The corrected clause now names it.


Generated by Claude Code

…g the tree no longer does

Three clauses in the unknown-key strictness ledger asserted, in the present
tense, that a strict branch's prescription is flattened away behind a union.
It is not: `formatZodIssue` descends `invalid_union` and ranks the branches
through `selectUnionBranches`, the one policy both spec-side walks import from
`packages/spec/src/shared/union-branch-policy.ts`.

Each clause is corrected in place, with its own provenance:

- the `state-machine.zod.ts` row (written 2026-08-04) and the `view.zod.ts`
  row's overlay note (written 2026-08-04) were TRUE when written and expired
  on 2026-08-05, when #4971 / #5014 / #5341 landed. They now say so and name
  the module that lifted it.
- the `chart.zod.ts` row's clause (written 2026-08-08) was already false the
  day it was written, so the row's own "kept as WRITTEN AT THE TIME" rule
  cannot rescue it. Its `only` is struck, and the generalisation it grounded
  — "a general constraint on every remaining union site in this campaign" —
  is withdrawn, because that generalisation is what seeded a false-premise
  card and cost a full dispatch cycle.

Rows whose claims are framed as what their own batch recorded are untouched,
and the os-regen `.counts.md` companion is not edited. No published surface
moves; the ledger is a repo-internal audit document.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 8, 2026
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Sep 8, 2026
@zhuangjianguo
zhuangjianguo marked this pull request as ready for review September 8, 2026 04:15
@zhuangjianguo
zhuangjianguo added this pull request to the merge queue Sep 8, 2026
Merged via the queue into main with commit 9412320 Sep 8, 2026
37 checks passed
@zhuangjianguo
zhuangjianguo deleted the claude/issue-14977-strictness-ledger-stale-flattening branch September 8, 2026 05:01
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/xs skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants