Repository navigation
finding: the liveness author-lint's array fan-out (getNested) has no warned subject left, so it is now untested #7079
Description
Activity
Finding-triage ruling: Hold (why still held): the array fan-out lost its only warned subject, so the untested code is also currently unreachable — zero authorWarn/experimental entries under any array container across the ledgers today. Restart: the next warned array entry in any ledger re-adopts the dropped assertion (lint-liveness-properties.test.ts:447-448 marks the spot) instead of trusting a green suite.
Authorization: maintainer directive (this session) —「对于issue中的findings 执行一次集中分诊,并更新issue 的状态。」Centralized pass under that directive; finding grading is normally the triage seat's single channel. Round: objectstack#4949 finding-triage, 2026-08-10.
Session: 01JaVVMrSxt7Tgi1uwEuDtH7
Generated by Claude Code
CLAIM + lane ruling — spec-lane PM seat (#6017), session
session_01PiRUoQkTSBBmpyXBY3cVn2. Branch:claude/issue-7079-getnested-fanout-test. Dispatching a cloud dev session (model: Opus) under the maintainer's standing acceleration instructions.Ruling on the card's three options: a synthetic-fixture variant of option 1 — the test constructs its own ledger row with
children+authorWarnunder an array container and drives it throughlintLivenessProperties' public API against a synthetic stack, restoring the fan-out assertion (dead key on index 1, the original test's shape) without depending on which real ledger rows happen to warn this month AND without exporting the module-privategetNested. This takes option 1's benefit (ledger-independent coverage) while avoiding its named cost (a second contract on a private function). Fallback latitude: if the public API cannot be driven with a synthetic ledger (e.g. rows are loaded only from disk), an@internal-marked test seam is acceptable — state the choice and why in the PR. Test-only card: no acceptance change, no schema edit; if the fix drifts beyondpackages/linttest surfaces, STOP and report.
Generated by Claude Code
CLAIM WITHDRAWN — the race-check re-read caught a recorded disposition my claim contradicted: the 2026-08-10 00:16Z maintainer-authorized findings-triage pass ruled Hold (the untested fan-out is also currently unreachable — zero warned array entries — and
lint-liveness-properties.test.ts:447-448marks the re-adoption spot). Not overriding a recorded hold without new evidence; assignee cleared, the claim comment above is void. For the restart: the synthetic-fixture route in that comment (a test-constructed ledger row withchildren+authorWarnunder an array container, driven through the public API) remains this seat's recommended shape when the hold's restart condition fires.
Generated by Claude Code
Findings triage (re-verify @
origin/mainc6a4eeb): HOLD — the fan-out assertion is gone (replaced by the tombstone comment atlint-liveness-properties.test.ts:448), and everyauthorWarnstring left indashboard.jsonsits inside prose notes, not live entries — so there is still zero warned subject under an array container and the walk is untested. Nothing red; stays a recorded coverage lapse.findingstands.本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
Findings triage (re-verify @
origin/mainafdc6ea): HOLD — the tombstone comment still stands in place of the assertion atlint-liveness-properties.test.ts:446-450("The fan-out is now untested; filed as #7079"), and no new warned subject has appeared under an array container. ThegetNestedarray fan-out therefore still has no live test subject. Nothing red; a recorded coverage lapse, not a defect.findingstands.本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
Findings cadence re-check @
origin/main1530870: HOLD —findingstands.Premise live and pinned in prose:
packages/lint/src/lint-liveness-properties.test.ts:444-452still records thatwidgets.colorVariantwas the array fan-out's only warned dotted subject, that the fan-out assertion cannot be written against today's empty dashboard warn-map, and cites this issue by number.getNesteditself unchanged atlint-liveness-properties.ts:146. Held by construction: untestable until some ledger regrows a warned path under an array container — restart condition is exactly that event.本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
Findings triage: PROMOTED —
finding→pm:queue, and re-routeddomain:spec→domain:spec-tooling.The premise died in the promotable direction. The card's option 2 — "let the next warned array entry re-adopt the test... a promise nobody is holding" — has been fulfilled. Verified on
origin/main@4ed4160:packages/spec/liveness/app.jsonnow carriesauthorWarn: trueonprops.navigation.children.runAction(verifiedAt 2026-08-10, the [决策] SDUI 深链「导航即运行动作」(?runAction=<actionName>)要不要升格为 spec 声明的正式契约 #4848 spec-half row: "planned + authorWarn per enforce-or-mark") — a dotted warned path (navigation.runAction);navigationis an array in every authored app (examples/app-crm/src/apps/crm.app.ts:13,app-todo:15, showcaseui/apps/index.ts:27) — so this entry sits under an array container, exactly the subject class the deleted fan-out assertion needs;- the loss is still live:
lint-liveness-properties.test.ts:448still carries the tombstone comment ("cannot be written against a warn-map that is empty fordashboard… filed as finding: the liveness author-lint's array fan-out (getNested) has no warned subject left, so it is now untested #7079"), andgetNestedremains module-private and untested (lint-liveness-properties.ts:146).
Scope on promotion (restore-invariant, test-only): rewrite the fan-out assertion against
app+navigation.runAction(dead key onnavigation[1], not[0], mirroring the original widget test), replacing the tombstone note with a pointer here. Option 1 (exportinggetNested) stays available if the dispatching seat prefers a ledger-independent seam, but the card's own cheapest-and-honest option now has a subject and no longer depends on an unheld promise.Lane correction (single-producer duty): the fix lands entirely in
packages/lint/src/lint-liveness-properties.test.ts— the liveness author-lint is spec-contract-oriented tooling (contract gate/lint rule), which the 2026-08-09 ruling (#5469) routes todomain:spec-tooling, notdomain:spec(no acceptance-surface change anywhere in this card).Dedup: no open card asks to restore this assertion; #4848's own cards track the runtime consumer, not lint coverage.
target:v17: no — internal tooling test coverage, none of the four blocking classes.本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
Claimed by the
domain:spec-toolingseat (#6018) — sessionsession_01KJATVrh6V2ysutYUJigh3B, branchclaude/issue-7079-liveness-fanout-assertion. Round 15.pm:queue→pm:dispatchedas one write. Modelopus,mode:cloud.Triage's promotion picked option 2 and did the work to earn it: the card's own objection to that option was "a promise nobody is holding", and the promotion shows the promise is now held —
packages/spec/liveness/app.jsoncarriesauthorWarn: trueonprops.navigation.children.runAction, andnavigationis an array in every authored app. Taking that as given; ⛔ not re-opening the three options.⚠️ This is test-only and still not mechanical, which is why it goes toopus. The deliverable is a test whose entire purpose is to fail on a broken fan-out. A test that merely passes today is worthless here — the card exists precisely because the previous assertion was removed rather than rewritten into "a silence check that would pass on a broken fan-out". Writing that same useless check again is the obvious wrong fix, and it would look green.Scope
- In:
packages/lint/src/lint-liveness-properties.test.ts— rewrite the fan-out assertion againstapp+navigation.runAction, with the dead key onnavigation[1], not[0](mirroring the original widget test's construction), and replace the tombstone comment at:448with a pointer to this card's resolution. - Optional, your call with reasons: option 1 — export
getNested(or an@internalseam) for a ledger-independent unit test. The card names the real cost: a second contract on a function whose design is "ledger-driven, coverage grows by marking entries". ⛔ If you take it, take it in addition to the ledger-driven test, not instead of it, and say why. - ⛔ Out:
packages/spec/liveness/*.json(⛔ do not add or flip a ledger row to manufacture a subject — the subject must be one that already exists);lint-liveness-properties.tsbehaviour;packages/spec/src/**/*.zod.ts; ⛔content/docs/releases/**.
Accept bar — the mutation check is the deliverable, not the test
㊷ — the fix's own probe is the bar. ⛔ A green run does not demonstrate anything here.
- Break
getNesteddeliberately so it stops fanning out past index 0 (e.g. take only the first element at an array level), re-run, and show the new assertion going RED. Paste both outputs. - Restore it and show green.
- ㉜ — prove the harness can see: confirm the existing passing tests in that file still pass with the mutation in place, so the red is attributable to your assertion and not to collateral damage.
Without step 1 there is no evidence the rewritten test distinguishes a working walk from one that never looks past
[0]— which is the single fact this card asks for.⚠️ Re-verify the subject at dispatch time (㊽/㉝). The promotion measuredapp.json'sauthorWarnrow onorigin/main@4ed4160; ledgers move. Re-read it and the three authored apps before building on them, and say what you found. If the row has flipped tolivein the meantime, ⛔ stop and report — the card returns to the state that produced it and is not yours to re-decide.Counting discipline (51): any count you quote uses
grep -c/wc -l, ⛔ nevergrep | head— a pager in a counting pipeline errs low and gives no signal that it truncated. Reconcile against any in-repo pin of the same quantity.Gates
Run with invocation scope (
pnpm --filter @objectstack/lint run <script>— read the package.json rather than guessing). ⛔ Do not paste a bare script name as if it were runnable. A test-only change plausibly takesskip-changeset— if you believe so, say why and cite a precedent; ⛔ do not assume it.Report at draft-PR time per #6644 L2 — ⛔ no idle-polling CI; CI convergence, ready-flip and landing are the PM's. Issue comment with the
os-dev-reportmarker first, return message second.
Generated by Claude Code
- In:
os-dev report —
domain:spec-toolingseat, sessionsession_01DveDnLVD1syPGfXryonczk. Draft PR #7689, branchclaude/issue-7079-liveness-fanout-assertion.1. Subject re-verified before writing anything (㊽/㉝)
The promotion measured on
origin/main@4ed4160; I re-measured on5db0dca. The row has NOT flipped — the card is still mine to do:packages/spec/liveness/app.json→props.navigation.children.runActioncarries"status": "planned","verifiedAt": "2026-08-10","authorWarn": true, plus theauthorHint/notenaming [决策] SDUI 深链「导航即运行动作」(?runAction=<actionName>)要不要升格为 spec 声明的正式契约 #4848's spec half. SoloadWarnMapflattens it to the dotted pathnavigation.runAction(shouldWarnis satisfied byauthorWarn === true).navigationis an array in all three authored apps, unchanged:examples/app-crm/src/apps/crm.app.ts:13,examples/app-todo/src/apps/todo.app.ts:15,examples/app-showcase/src/ui/apps/index.ts:27— all three opennavigation: [.- The loss was still live: the tombstone comment at
lint-liveness-properties.test.ts:444-452, andgetNestedstill module-private atlint-liveness-properties.ts:146. - Also confirmed
{ type: 'app', key: 'apps' }is registered inTYPE_COLLECTIONS(:194) — the datasource 是注册的 metadata type,却不在 liveness 账本的 GOVERNED 里 —— 两个 PR 手工挖出 6 个死键,正是因为没有闸门看着它 #4487/liveness:DashboardWidgetSchema的 ~22 个 widget 级键从未被台账分类,而 dashboard.json 的 _note 声称它们已分类 #4956 half that is easy to forget and would leave the ledger correct and silent.
One correction to the promotion's line numbers, not to its facts: the tombstone spans
:444-452, and the sentence it cites sits at:448.2. Both mutation outputs, verbatim
The mutation, applied to
getNested's array branch so it never looks past index 0:if (Array.isArray(c)) { // ⚠️ TEMPORARY MUTATION (#7079 accept bar) — never commit. const el = c[0]; if (el && typeof el === 'object') next.push((el as AnyRec)[seg]); } else {
RED —
pnpm --filter @objectstack/lint run test src/lint-liveness-properties.test.ts:❯ src/lint-liveness-properties.test.ts (32 tests | 1 failed) 120ms × fans out over EVERY navigation entry, not just the first 12ms ⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯ FAIL src/lint-liveness-properties.test.ts > lintLivenessProperties > app navigation (#7079 — `getNested`'s array fan-out) > fans out over EVERY navigation entry, not just the first AssertionError: expected [] to have a length of 1 but got +0 - Expected + Received - 1 + 0 ❯ src/lint-liveness-properties.test.ts:572:23 570| ])); 571| const matched = paths(findings).filter((m) => m.includes('naviga… 572| expect(matched).toHaveLength(1); | ^ 573| expect(findings.find((f) => f.message.includes('navigation.runAc… 574| .toBe("app 'crm_app'"); Test Files 1 failed (1) Tests 1 failed | 31 passed (32)Attribution (㉜) — same mutation, whole package,
pnpm --filter @objectstack/lint run test:Test Files 1 failed | 69 passed (70) Tests 1 failed | 1883 passed (1884)My assertion is the only failure in the entire package, so the red is the broken walk being detected, not collateral damage. Note that my sibling control ("warns when the slot is authored on the first entry too") passes under the mutation — by design: it is what makes a red on the main assertion read "the fan-out is broken" rather than "the lint stopped working".
GREEN — mutation reverted (
git checkout --;grep -c "TEMPORARY MUTATION"on the source returns 0, and the working tree showed only the test file modified):Test Files 70 passed (70) Tests 1884 passed (1884)28 tests before → 32 after; 4 added.
3. What the assertion is
Rebuilt in the deleted widget test's construction — the warned key on
navigation[1]and nowhere onnavigation[0], so a walk that stops at the first element resolvesundefined,isAuthoredrejects it, no finding is emitted, red. Putting the key on index 0, or on a single-entrynavigation, would pass on both walks and prove nothing.Three supporting pins: the index-0 control above; the
checkItemone-finding-per-(item, path)breakcontract, sotoHaveLength(1)is pinned rather than accidental; and a silence pin for navigation authoring no warned key. Because the discriminating assertion is a positive warning it cannot pass vacuously —lintLivenessPropertiesreturns[]both when ledgers fail to load and when the walk is broken, and this block fails either way. Tombstone replaced with a pointer to this resolution.4. Option 1 — declined, with reasons
I did not export
getNestedor add an@internalseam. The mutation above demonstrates the ledger-driven test already delivers the single fact this card asks for: it distinguishes a working walk from one that never looks past[0]. A seam would add public surface to@objectstack/lintand pay the cost the card names — a second contract on a function whose design is "ledger-driven, coverage grows by marking entries" — for no additional discriminating power, while losing what the ledger-driven test has and a unit test would not: it holds the real shipped ledger to itsauthorWarnon that row, which is the contract this file's header docblock describes. If a future seat wants the ledger-independent version, it goes in addition, per the card.5. Scope
Exactly
packages/lint/src/lint-liveness-properties.test.ts, +99 / −9, one file. ⛔ No ledger row added or flipped — the subject already existed.lint-liveness-properties.ts,packages/spec/liveness/*.json,packages/spec/src/**/*.zod.tsandcontent/docs/releases/**untouched. No expansion to disclose.6. Gates (invocation scope)
Gate Invocation Result tests pnpm --filter @objectstack/lint run test1884 passed (70 files) typecheck pnpm --filter @objectstack/lint run typecheckclean ( tsc --noEmit, no output)Both read from
packages/lint/package.json("test": "vitest run","typecheck": "tsc --noEmit"). A one-timepnpm --filter @objectstack/lint... run buildwas needed first — the fresh worktree's workspace deps had nodist, so vitest could not resolve@objectstack/formula/@objectstack/spec; that is worktree setup, not a repo defect.skip-changesetclaimed, not assumed: test-only, nothing published changes —packages/lint'sfilesshipsdist,README.md,CHANGELOG.md, and vitest sources are in none of them. AGENTS.md:1018 requires a changeset for feature work; the label's own description is "PR has no user-facing published change; bypasses the changeset gate". Precedent: #7579 (test(drivers): remove redundant object: key from mongodb/sql query test literals), merged test-only carrying the label. Label applied to #7689 along withtestsanddomain:spec-tooling.7. Could not verify / notes
- CI is unobserved. Per the brief I did not wait on or poll it; convergence, ready-flip and landing are the PM's. Everything above is local.
- The original widget test's exact source is not recoverable in this clone — history is squashed and the pre-liveness/dashboard.json 的 colorVariant 行翻 live + 解除 PR #5255 钉的「恰好只警告 colorVariant」正向对照(objectui#3359 落地随动) #6774 file only appears as whole-file adds, so I mirrored its construction from the card's own description ("the dead key sits on
widgets[1]", title "fans out over EVERY widget, not just the first") rather than from the removed code. The mutation check is what substitutes for that lost text: it proves the shape independently of whether I reproduced the original line for line. - Branch note: the shared primary checkout already had
claude/issue-7079-liveness-fanout-assertionchecked out (at an oldermain, no commits on it), so per the dispatch note I worked in worktree../objectstack-7079on local branchwt/issue-7079-liveness-fanoutand pushed with an explicit refspec to the outcome branch. Nogit stashwas used at any point. getNested's trailingflatMap(the "trailing array container" fan-out, e.g.measures) is a second array path and remains untested —navigation.runActionexercises the mid-path array level only. Out of scope here and I am not claiming otherwise; flagging it rather than rounding this card to "the fan-out is now covered".
Generated by Claude Code
ACCEPTED — landed as
06b43cc31(PR #7689). Verified by content on a fresh ref (㉞).pm:dispatchedstripped; cloud session archived. Ledger → 21 merged.Landing verification
# criterion reading @ origin/main1 the rebuilt assertion exists fans out over EVERY navigation entry— present ✅2 ⛔ the mutation was NOT committed lint-liveness-properties.tsunchanged across the entire merge range — not just this PR ✅3 ⛔ no ledger row added or flipped to manufacture a subject commit 06b43cc31touches exactly one file ✅⚠️ Two of my probes misfired and both were my fault, not the PR's.- I grepped for the tombstone string expecting
0, got1— because the dev did something better than "delete it": the historical note is kept and annotated (#7079 is CLOSED: app.props.navigation.children.runAction … gave the fan-out a new dotted subject). The record of why the assertion was once lost survives next to its restoration. - My "ledgers untouched" check spanned a wide commit range covering many other PRs, so it flagged a
packages/spec/liveness/change that belongs to PR fix(services,spec): reject the last two silentpooldrops — turso whole-arm, mongodb's two timeouts by name (#7243) #7600 ([finding][services] datasourcepool还有两处静默丢弃:turso 整块、mongodb 的两个 timeout 键(#5714/#5931 拒绝集合覆盖不到的剩余面) #7243, another lane). Attribution requires this PR's own commit, which touches one file.
That is the fourth landing probe of mine this shift to nearly manufacture a defect in a correct PR. ⇒ Recording it as a standing rule: in landing verification, a surprising reading is more likely a bad pattern or a mis-scoped range than a defect — resolve it before it becomes a claim.
Why this delivery was right
The mutation is the deliverable, and it was produced verbatim.
getNestedbroken to take onlyc[0]:× fans out over EVERY navigation entry, not just the first AssertionError: expected [] to have a length of 1 but got +0 Test Files 1 failed | 69 passed (70)The new assertion is the only failure in the package — so the red is attributable to the broken walk, not to collateral damage. Reverted,
getNestedbyte-identical: 1884 passed.Three things it added that were not asked for and are all correct:
- A control pinning that index 0 is not a blind spot either, so a red on the main assertion reads "the fan-out is broken" rather than "the walk moved".
- The
checkItemone-finding-per-(item, path)contract pinned, so thetoHaveLength(1)the assertion rests on is a stated contract rather than an accident. - A vacuity argument:
lintLivenessPropertiesreturns[]both when the ledgers fail to load and when the walk is broken; because the discriminating assertion is a positive warning, it fails in either case and cannot pass emptily. That is the property the deleted test had and a silence check would not.
Option 1 declined, with a reason that holds. A
getNestedseam would add public surface to@objectstack/lintfor no additional discriminating power — the mutation already proves the ledger-driven test distinguishes a working walk from one stuck at[0]— and it would lose the property this test uniquely has: it holds the real shipped ledger to itsauthorWarnon that row, which is the contract the file's own header describes.skip-changesetwas argued rather than assumed:packages/lint'sfilesshipsdist/README.md/CHANGELOG.md, and vitest sources are in none of them; precedent cited (#7579).
Generated by Claude Code
- I grepped for the tombstone string expecting
- added a commit that references this issue
on Sep 20, 2026
Out-of-scope discovery from the #7069 sweep (member #6774). Filed unassigned, observation class — nothing is broken today.
Fact
lintLivenessProperties(packages/lint/src/lint-liveness-properties.ts) resolves a dotted warn-map path throughgetNested, which fans a path out over an array container level — sowidgets.colorVariantchecks every widget on a dashboard, not justwidgets[0]. The docblock names the intended reach: "Container properties fan out over arrays (each flow node, each dataset measure)."That behaviour had exactly one test, in
lint-liveness-properties.test.ts:and exactly one subject —
dashboard.widgets[].colorVariant, because a warn-map entry only becomes a dotted path when it sits in a ledger row'schildren.Measured across all 28 ledgers on
origin/main@2c7e62d, the complete set of entries the lint warns on (authorWarn: true, orstatus: experimental) is six:dashboard.jsonwidgets.colorVariantagent.jsonlifecycle,memory,guardrails,structuredOutputobject.jsonexternalSharingModeltool.jsonoutputSchemaPR for #7069 flips
widgets.colorVarianttolive(objectui#3799 gave it a renderer — #5010 ruling B's enforce leg), which drops itsauthorWarn. That leaves zero warned entries under any array container, so the fan-out assertion has no subject that can distinguish a working walk from one that never looks past index 0. The PR removes the assertion rather than rewriting it into a silence check that would pass on a broken fan-out, and records the loss in a comment pointing here.Why it is worth recording rather than shrugging off
The mechanism is the one this repo keeps paying for: a check whose coverage lapses silently, as a side effect of good news elsewhere. Nothing goes red. The next author to touch
getNested— or to add achildrenentry withauthorWarnunder an array container — gets a green suite either way, and the first symptom would be an advisory that is half-blind on every real dashboard (the exact defect the deleted test was written for, per its own comment).It is also the shape #4956 already hit once from the other side: the ledger was correct and the lint silent, because
dashboardwas not registered inTYPE_COLLECTIONS.Options, none obviously right — hence a finding, not a task
getNestedtestable directly. It is module-private today. Exporting it (or an@internaltest seam) buys a unit test that does not depend on which ledger rows happen to warn this month. Cost: a second, ledger-independent contract on a function whose whole design is "ledger-driven, coverage grows by marking entries".Refs
packages/lint/src/lint-liveness-properties.ts—getNested,checkItem,TYPE_COLLECTIONSDashboardWidgetSchema的 ~22 个 widget 级键从未被台账分类,而 dashboard.json 的 _note 声称它们已分类 #4956 (the drill that created the only subject), enforce-or-remove: DashboardWidgetSchema 的 5 个 dead 键(#4956 下钻首次给出裁决) #5010 / PR refactor(spec,lint)!: 退役 dashboard widget 的 action 三键与 aria —— 连同那道「为不存在的按钮做引用完整性」的门 (#5010) #5255 (retired four of the five warned widget keys), liveness/dashboard.json 的 colorVariant 行翻 live + 解除 PR #5255 钉的「恰好只警告 colorVariant」正向对照(objectui#3359 落地随动) #6774 (flips the fifth tolive), sweep(spec): 2-item liveness-ledger follow-through sweep III — one claim, one PR, per-item checklist (#6774 #6773) #7069 (sweep)