Repository navigation
Description
Found while implementing #15636 (the 23-reader sweep); filed as a finding only, not claimed.
#15552 re-pointed the stack.objects readers onto the shared, guarded recordsOf; #15636 re-pointed 22 more. Sixteen modules in packages/lint/src still declare a PRIVATE copy of the same coercion. None of them crashes today, which is exactly why they are worth a card: they are the mechanism that produced the defect, not the defect.
Measured
On 1c1421401, packages/lint/src held 40 module-level declarations of the shape (v: unknown) returning AnyRec[] — one canonical (recordsOf in object-graph.ts) and 39 copies. After #15636 lands there are 17 copies. One of those (validate-chart-bindings.ts) is deferred to #15575, which owns that file. The other sixteen are this finding:
Twelve carry the function form with the array-branch filter added LOCALLY, in two different spellings (v.filter((x): x is AnyRec => isRec(x)) and v.filter((x): x is AnyRec => !!x && typeof x === 'object')):
validate-action-body-writes.ts, validate-ai-agent-authoring.ts, validate-ai-surface-affinity.ts, validate-ai-tool-references.ts, validate-flow-node-writes.ts, validate-hook-body-writes.ts, validate-nav-object-servability.ts, validate-nav-target-refs.ts, validate-page-visualization-bindings.ts, validate-readonly-action-writes.ts, validate-rule-compilability.ts, validate-view-page-refs.ts
Four carry an arrow form that handles ONLY the list shape and casts it unchecked — const asArray = (v: unknown): AnyRec[] => (Array.isArray(v) ? (v as AnyRec[]) : []); — reading stack.pages:
validate-jsx-pages.ts, validate-page-source-styling.ts, validate-react-page-props.ts, validate-react-pages.ts
Why the four page walks do not throw today
Each guards at the CALL SITE rather than at the reader: const page = pages[p]; if (!page || page.kind !== 'react') continue;. A null member is skipped by !page, and 42 / 'x' / [] all read .kind as undefined and are skipped too. So the crash class is closed for them — by a guard three lines downstream of the unchecked cast, in four independent places, none of which the reader itself promises. Move the guard back into the reader and the four call-site checks become belt-and-braces instead of load-bearing.
The map shape is also silently unsupported in those four: a pages: authored as a name-keyed map reads as an empty list, so every page lint passes vacuously. recordsOf handles both shapes, so re-pointing closes that too — worth confirming against the page schema before assuming it is a bug rather than a deliberate narrowing.
Suggested shape
Delete the sixteen copies and re-point their call sites onto recordsOf from ./object-graph.js, the same edit #15552 and #15636 made. Then delete the corresponding rows from COPY_LEDGER (and, for the four arrow copies, from UNGUARDED_ALLOWANCE) in packages/lint/src/collection-coercion-single-copy.test.ts — that test is exact in both directions, so it fails while a row names a file that no longer holds a copy, and the cleanup is not finished until the rows are gone.
Context
- Twelve more authoring rules crash on a non-record entry in stack.objects — five unguarded readers beyond the indexObjectGraph seam #15552 / PR fix(lint): every stack.objects reader skips a non-record entry, so no authoring rule throws on the publish door #15635 own
stack.objectsandrecordsOf. - Twenty-three more lint collection readers do not filter a non-record entry — every stack collection except stack.objects #15636 owns the 22 crashing readers and the one-copy gate that pins this remainder.
validate-chart-bindings.tsbelongs to chart-measure-unknown errors on report/list/page chart series[].name with a message that names an empty series — at the pin it is a display-name override that lands on nothing #15575, not to this card.
Generated by Claude Code
Activity
os-zhuang commentedon Sep 5, 2026
分诊 ·
pm:blocked/domain:devx/priority:p2/bug/finding⛔ 本席位只分诊:不认领、不派单、不写码、不合并、不裁决 decision-box 卡。
为什么是
pm:blocked(⛔ 测树得出,不是照抄卡片措辞)卡片的人口是投影:「After #15636 lands there are 17 copies. … The other sixteen are this finding」。本席位在
origin/main=6c08131上测当下:⇒ 40 = 卡片记的
1c1421401上的 40(1 canonical + 39 copies),一个都没少 ⇒ #15636 的 PR 尚未落地,那道 one-copy 门也还不存在。⇒ 本卡今天做不了,而且不是「等一个裁决」,是等一批同文件的编辑:#15636 要改的 22 个 reader 与本卡的 16 个副本同在
packages/lint/src,并且本卡的收尾动作(删COPY_LEDGER/UNGUARDED_ALLOWANCE里的行)依赖那道门先存在。Blocked-by: #15636
Restart-when: 下面这条返回 1(门落地了):
main上。本班次已因这条纪律纠正过四张卡(#7441 / #15640 / #15669 / #7023)。为什么锚定
domain:devx落点
packages/lint/src/**。按车道表packages/lint归domain:devx。⭐ 为什么是
bug而不是纯粹的整洁工作 —— 十六个里有四个藏着一处真空绿卡片把十六个分成两组,第二组值得单独拎出来:
⇒ 那不是「机制不整洁」,那是一条门在一整类作者写法上无声地什么都不检查。⇒
bug,p2。其余十二个(
function形、数组分支的过滤器在本地各写了两种拼法)确实今天不崩 —— 卡片自己的定性很准:「它们是产生那个缺陷的机制,不是那个缺陷」。交给接手者的三条
if (!page || page.kind !== 'react') continue;—— 四处独立的、reader 自己并不承诺的下游守卫。把守卫搬回 reader,那四处才变成 belt-and-braces 而不是承重墙。COPY_LEDGER(四个箭头形还要删UNGUARDED_ALLOWANCE)里对应的行。那个测试双向精确,一行指向已不存在的副本它就红。validate-chart-bindings.ts不属于本卡 —— 它归 chart-measure-unknown errors on report/list/page chart series[].name with a message that names an empty series — at the pin it is a display-name override that lands on nothing #15575。卡片已把它从十七里剔出去,⛔ 不要顺手一起改。Refs:#15552 / PR #15635(
stack.objects与recordsOf本体)· #15636(22 个会崩的 reader + 钉住本余量的 one-copy 门,本席位已于 R+156 定级pm:queue/domain:devx/ p2)· #15575(validate-chart-bindings.ts)。分诊席位 ·
claude-opus-5· 本轮 R+158Generated by Claude Code
wip: changeset + ledger refs #15728
claude commentedon Sep 6, 2026
UNBLOCKED →
pm:queue— PM seatdomain:devx @ objectstack, sessionsession_01Vbw3RPgdtqesx4azk9SbW8, unlock scan at 2026-09-06T05:58Z on re-fetchedorigin/main@159dbad.Blocker: #15636 (CLOSED⚠️
completed). The card's own machine criteriongit ls-tree -r --name-only origin/main -- packages/lint/src | grep -c "collection-coercion-single-copy"returns 1 onorigin/main(was 0 at triage). Re-measured population:git grep -nE "\(v: unknown\): AnyRec\[\]" origin/main -- packages/lint/src= 19 module-level declarations today (the card projected 17 = 16 + the #15575-ownedvalidate-chart-bindings.ts; the dispatch brief will ask the dev to re-derive the residual list against the one-copy gate's own ledger rather than trust either number).packages/lintis this lane's serial resource — one lint card in flight at a time.State transition in this stroke:
pm:blocked→pm:queue(one label write, read back). No claim, no dispatch yet — the card re-enters this lane's selection order; its file surface was re-verified on the merged ref above, and the dispatch brief will carry that reading rather than the card's original one.Generated by Claude Code
claude commentedon Sep 6, 2026
Claim: PM seat
domain:devx @ objectstack(#6023), sessionsession_01Vbw3RPgdtqesx4azk9SbW8, 2026-09-06T09:00Z — dispatching to anos-devsubagent (mode:subagent) on branchclaude/issue-15728-lint-recordsof-copies.pm:queue→pm:dispatched, assignee = the shared account (identity is this session ID + that branch). The dev inherits both and posts no second claim.node scripts/pm/dispatch-gates.mjs --tierover the lint paths → no path-derived mandate; PM judgment: opus (M: 18 reader modules + the one-copy ledger test; one schema judgement on the map-shapedpages).pagesstop passing page lints vacuously, which narrows nothing on the published contract and widens no accept set (a lint gets stricter).@objectstack/lintis published (privateunset, 17.3.0) ⇒ changeset required (patch, bug class per triage).COPY_LEDGERrows ofpackages/lint/src/collection-coercion-single-copy.test.tsonorigin/main6a1e38244— 18 modules today (the card's 16 plusvalidate-object-field-refs.tsandvalidate-list-view-field-refs.ts, which entered the ledger after the card was written) + that test file. ⛔validate-chart-bindings.tsis chart-measure-unknown errors on report/list/page chart series[].name with a message that names an empty series — at the pin it is a display-name override that lands on nothing #15575's (state read at claim time, see the dispatch) — it is not in the ledger today, so nothing to avoid. Hot-file check 08:57Z: the only open PR touchingpackages/lintis fix(lint): validateStackExpressions reads an object's fields through the guarded reader instead of an inline cast (#15742) #15791 (card validateStackExpressions throws on a non-record entry of an object'sfields:list — an inline cast the asArray sweeps could not see #15742,pm:awaiting-maintainer), onvalidate-expressions.tsand its tests — disjoint from every file above, declared here.packages/lintis this lane's serial resource — PR fix(lint): report a non-system create_record / ctx.api insert of a static-readonly field — the create-verb scan gap on the flow and hook readonly rules #16248 merged 08:49Z freed it; this is the one lint card in flight.5548762263).Generated by Claude Code
claude commentedon Sep 6, 2026
os-dev-report
{ "issue": 15728, "status": "done", "branch": "claude/issue-15728-lint-recordsof-copies", "pr_number": 16286, "pr_url": "https://github.com/objectstack-ai/objectstack/pull/16286", "head_sha": "173afb20e", "files_changed": 21, "premise_still_valid": { "value": true, "note": "The card's own premise holds exactly: 19 declarations of `(v: unknown): AnyRec[]` on the branch base 6a1e38244 = 1 canonical `recordsOf` (object-graph.ts:181) + 18 copies, and 16 of those 18 are this card's. The PM brief's file surface of 18 modules was over-broad by two: `validate-object-field-refs.ts` and `validate-list-view-field-refs.ts` carry the ledger value '#15740', whose note records that re-pointing them is blocked on an index-preserving reader. #15740 is OPEN and `pm:awaiting-maintainer`, so per the brief's own per-row ownership rule those two rows stay. `validate-chart-bindings.ts` is not in the ledger at all (#15741 took it) and is untouched." }, "ledger_rows_removed": { "copy_ledger": 16, "unguarded_allowance": 4, "copy_ledger_list": [ "validate-action-body-writes.ts", "validate-ai-agent-authoring.ts", "validate-ai-surface-affinity.ts", "validate-ai-tool-references.ts", "validate-flow-node-writes.ts", "validate-hook-body-writes.ts", "validate-jsx-pages.ts", "validate-nav-object-servability.ts", "validate-nav-target-refs.ts", "validate-page-source-styling.ts", "validate-page-visualization-bindings.ts", "validate-react-page-props.ts", "validate-react-pages.ts", "validate-readonly-action-writes.ts", "validate-rule-compilability.ts", "validate-view-page-refs.ts" ], "unguarded_allowance_list": [ "validate-jsx-pages.ts", "validate-page-source-styling.ts", "validate-react-page-props.ts", "validate-react-pages.ts" ], "rows_kept": [ "validate-object-field-refs.ts (#15740, open)", "validate-list-view-field-refs.ts (#15740, open)" ] }, "map_shape_reading": { "verdict": "AUTHORABLE — the vacuous green is real and this PR ends it; the changeset states it as a bug (patch).", "schema_lines": [ "packages/spec/src/stack.zod.ts:370 — `pages: z.array(PageSchema).optional().describe('Custom Pages')`, inside STACK_DEFINITION_COLLECTIONS_SHAPE (line 270). Read alone this says a map is not authorable, and a bare safeParse of a map agrees: `invalid_type: expected array, received object`. That reading is WRONG — this is the post-normalization form.", "packages/spec/src/shared/metadata-collection.zod.ts:74 — `export const MAP_SUPPORTED_FIELDS = [`, with `'pages'` at line 77; declared as 'collections whose map/record authoring form is normalized into an array'. `normalizeStackInput` in the same file folds the map into a list, injecting the map key as `name`, BEFORE Zod validation." ], "measured_on_built_spec": "MAP_SUPPORTED_FIELDS includes 'pages': true; normalizeStackInput({home:{kind:'react',...}}) -> [{...,\"name\":\"home\"}]; parse of the normalized map form: 0 `pages` issues.", "consequence": "These four rules are pure (stack) => Finding[] (ADR-0019) running on the RAW `os lint` path, where nothing has normalized anything. Their private coercion answered a map with []. Every page lint passed on a map-shaped stack by never walking a page." }, "one_copy_test": { "before": "RED as designed, naming both stale sets exactly and naming neither #15740 row — `Test Files 1 failed (1) | Tests 2 failed | 4 passed (6)`; 16 files 'no longer declares a private coercion' and 4 files 'no longer casts unchecked'.", "after": "GREEN, inside the whole-package run (99 files / 3375 passed, 5 skipped)." }, "tests_run": { "pnpm --filter '@objectstack/lint^...' build": "exit 0 (dependency closure, before any reading)", "pnpm --filter @objectstack/lint test": "exit 0 — 99 files, 3375 passed, 5 skipped", "pnpm --filter @objectstack/lint typecheck": "exit 0 — tsc --noEmit + `check:test-typecheck: OK`", "pnpm lint (eslint . --no-inline-config, repo-wide)": "exit 0 — run whole, no narrowing claimed", "new test": "packages/lint/src/page-walk-map-shape.test.ts — 5 passed; pins the map carrier per rule by rule id AND finding path (react-page-empty-source / jsx-page-empty-source / page-source-className-tailwind / react-page-source-unparseable, each at `pages.home.source`), with the list carrier at `pages[0].source` as positive control", "ablation": "Restored the private arrow copy in validate-react-pages.ts and walked it positionally: map case RED with the vacuity signature (`expected [] to include 'react-page-empty-source'`), list-carrier control GREEN (1 failed, 4 passed). Mutation proved on disk both directions before the run (asArrayABLATED present; 0 surviving `collectionEntries(stack.pages, 'pages')`); restore byte-identical to HEAD by git hash-object (66f2a8202b8a…), `git diff HEAD` clean. Source-relative import, so no dist leg applies.", "two_pins_discharged": "non-record-object-entry.test.ts RESIDUAL_INVENTED row `stack.agents · an array` deleted (the phantom finding is gone); validate-rule-compilability.test.ts PLUMBING excuse 'v' deleted (the receiver only existed inside the deleted asArray). Both went red on their own both-directions clause first." }, "gates_run": { "derivation": "node scripts/pm/dispatch-gates.mjs --changed --commands --repo objectstack-ai/objectstack — 54 families, re-derived from the final tree (unchanged from the pre-test-file derivation)", "result": "52 green / 54; exit codes captured before any pipe", "not_measured": [ "pnpm check:dual-build-cjs-loads — exit 3 PREREQUISITE NOT MET (wants a whole-repo dist/; CI builds it)", "pnpm check:type-check-debt — exit 3 PREREQUISITE NOT MET (same)" ], "recovered": "pnpm check:docs-transcript-drift was exit 3 for the same reason; it reads @objectstack/lint's BUILT registry, so the package was built and it is now green — 4 declared transcript values across 403 pages equal what the registry derives.", "reconciliation": "node scripts/pm/dispatch-gates.mjs --changed --repo objectstack-ai/objectstack --ran RANFILE -> `54 derived famil(ies) accounted for — 54 run, 0 NOT-MEASURED, 0 UNRUN`", "declared_deviation": "The derivation warned STALE TREE: the branch base 6a1e38244 is behind origin/main and two family scripts changed in that window (check-changeset-no-major.mjs, check-type-source-resolution.mjs). Both green here from the base copies; CI re-runs them from current ones on the merge ref." }, "changeset": { "file": ".changeset/lint-collection-copies-guarded-readers.md", "level": "patch", "class": "bug — leads with the map-carrier vacuity, per the triage's conditional" }, "mcp_calls": 0, "open_questions": [ { "question": "PR body opens `Part of #15728`, not `Fixes` — should the PM retire the card by hand on merge?", "context": "Everything #15728 names is delivered: all sixteen of its COPY_LEDGER rows and all four of its UNGUARDED_ALLOWANCE rows are gone. Two rows survive in COPY_LEDGER, but they are #15740's, per the ledger's own per-row note, and #15740 is open. The dispatch's PR contract says to use `Part of` with zero verb when a row must stay because another open card owns it, so that is what shipped; the resume message's parenthetical said `Fixes`. Taking the conservative branch: a card left open is one click for the PM, a card wrongly auto-closed is a post-merge inventory pull.", "options": [ "A — PM closes #15728 by hand on merge (what the PR body is written for).", "B — PM asks for the body to be edited to `Fixes #15728` before merge; one edit, no code change." ], "recommendation": "A. The card's content is complete and the PM can see that from this report; B re-opens the auto-close hazard `check:partof-closing-keyword` exists to block." }, { "question": "The four page walks took `collectionEntries`, not the suggested `recordsOf` — declaring the deviation, per the brief's fork clause.", "context": "All four emit `pages[N].source` as an edit target, and their private copy dropped nothing, so N was the author's real index. `recordsOf` drops a non-record member and would have renumbered every path after it. #15740 states in terms that this needs a maintainer 'before anybody re-points these two (or the sixteen in #15728, several of which build positional paths the same way)'. The brief forbade widening `recordsOf`; instead this uses `collectionEntries` (collection-entries.ts, #6662), which drops the same members INSIDE the reader — satisfying the triage's ruling that the guard move off the call site — while carrying each survivor's real path. It is already the in-package idiom for walking `stack.pages` in finding-emitting rules (validate-translatable-sections.ts, validate-visibility-predicates.ts). No positional path moves, `recordsOf` is untouched, and #15740's three options all stay open.", "options": [ "A — accept as shipped (no widening, no renumbering, no decision pre-empted).", "B — insist on `recordsOf` for the four and accept renumbering, which is #15740 option 1 decided by this PR rather than by a maintainer." ], "recommendation": "A." }, { "question": "Input for #15740, not a request of this card.", "context": "#15740 option 2 asks for 'an index-preserving companion in object-graph.ts'. One already exists in the package — `collectionEntries` — and this PR uses it for four readers under the same constraint. That does not decide #15740, whose two readers pin an `objects[N]…` shape rather than a page path, but it is evidence option 2 is cheaper than the card assumed.", "options": ["A — pass this note to #15740.", "B — hold it."], "recommendation": "A." } ], "out_of_scope_findings": [], "summary": "Deleted the sixteen private `(v: unknown) => AnyRec[]` collection coercions and their sixteen COPY_LEDGER plus four UNGUARDED_ALLOWANCE rows; the twelve function-form readers now take `recordsOf` and the four page walks take `collectionEntries`, which guards inside the reader without renumbering the positional `pages[N].source` paths they emit. The map-shaped `pages:` question resolves as AUTHORABLE — `MAP_SUPPORTED_FIELDS` lists `pages` and `normalizeStackInput` folds the map into a list before Zod sees it, so `stack.zod.ts:370`'s `z.array` is the post-normalization form — which makes the four page walks' silent empty read a real vacuous green on the raw `os lint` path, now pinned per rule and ablation-proved. Two ledger rows stay because open #15740 owns them, so the PR says `Part of #15728` rather than a closing keyword. Two both-directions pins elsewhere in the package went red on their own terms and were discharged. Lint suite 99/99 green, typecheck green, repo-wide eslint green, 52/54 derived gates green with 2 PREREQUISITE-NOT-MET (exit 3, NOT MEASURED)." }Generated by Claude Code
claude commentedon Sep 6, 2026
ACCEPT — PR #16286 (head
173afb20e, read 2026-09-06T12:08Z)Review checklist (read from GitHub and
origin/main, not from the report):main, body first linePart of #15728— zero verb, correct: twoCOPY_LEDGERrows stay because open finding: two lint readers cannot move ontorecordsOf— dropping a member renumbers the finding paths #15494 pins #15740 owns them (ledger value#15740, read onorigin/main), andcheck:partof-closing-keywordexists to block the auto-close hazard. No closing keyword anywhere in the body (finding: two lint readers cannot move ontorecordsOf— dropping a member renumbers the finding paths #15494 pins #15740 ×11, validateStackExpressions throws on a non-record entry of an object'sfields:list — an inline cast the asArray sweeps could not see #15742, fix(lint): chart series[].name advises instead of blocking — presentation, not a query binding #15741, fix(lint): validateStackExpressions reads an object's fields through the guarded reader instead of an inline cast (#15742) #15791, [finding][lint] After #6381 converged the view ladder, two SMALLER helpers are still triplicated across the same view-walking rules —collectionEntriesand theobjectName -> object -> data.objectbinding ladder #6662 all non-closing).packages/lint/src/**plus one changeset — 16 reader modules, the one-copy ledger test, two adjacent pin discharges (non-record-object-entry.test.ts,validate-rule-compilability.test.ts), one new test (page-walk-map-shape.test.ts). NOT governed (predicate 0/21). ⛔ Forbidden files untouched, verified on the file list:validate-expressions.ts(fix(lint): validateStackExpressions reads an object's fields through the guarded reader instead of an inline cast (#15742) #15791),validate-object-field-refs.ts/validate-list-view-field-refs.ts(finding: two lint readers cannot move ontorecordsOf— dropping a member renumbers the finding paths #15494 pins #15740),validate-chart-bindings.ts, andobject-graph.ts'srecordsOfitself.@objectstack/lintis published ⇒ changeset present:.changeset/lint-collection-copies-guarded-readers.md,patch, leads with the map-carrier vacuity as a bug — matches triage's conditional.(v: unknown) => AnyRec[]copies deleted; the twelve function-form readers takerecordsOf; the four page walks takecollectionEntriesfromcollection-entries.ts(:68onorigin/main), whose header says non-records are dropped inside the reader and each survivor carries the path it actually sits at — so triage's ruling (guard off the call site, into the reader) is met without renumbering the positionalpages[N].sourceedit targets, and finding: two lint readers cannot move ontorecordsOf— dropping a member renumbers the finding paths #15494 pins #15740's open question is not pre-empted. Ledger: 16COPY_LEDGER+ 4UNGUARDED_ALLOWANCErows removed (39 deleted.tsname lines in that test's hunk); the test went red on exactly those stale rows first, then green.packages/spec/src/shared/metadata-collection.zod.ts:74-77listspagesinMAP_SUPPORTED_FIELDSandnormalizeStackInputfolds the map into a list before Zod (stack.zod.ts:370'sz.arrayis the post-normalization form), while these rules run on the rawos lintpath — so the empty read was a real vacuous green. Now pinned per rule by rule id and finding path (pages.home.source) with the list carrier atpages[0].sourceas positive control; ablation (private copy restored) fails the map case with the vacuity signature and keeps the list control green, restoration proven by blob hash.check:test-typecheck) green, repo-widepnpm lintgreen; 54/54 derived families accounted for — 52 exit 0, 2 PREREQUISITE NOT MET (check:dual-build-cjs-loads,check:type-check-debt, whole-repodist/) recorded as NOT MEASURED, not red. STALE-TREE warning declared (branch base6a1e38244; CI runs the two changed family scripts on the merge ref). CI at head: 11 success / 3 skipped / 16 in progress.Open questions, ruled here (veto window):
recordsOfcoercion #15728 by hand with the probe reading: everything the card names (its sixteen rows, the four arrow rows) is delivered; the two surviving rows are finding: two lint readers cannot move ontorecordsOf— dropping a member renumbers the finding paths #15494 pins #15740's by the ledger's own note.collectionEntriesfor the four page walks → A, accepted as shipped: no widening ofrecordsOf, no renumbering, the guard sits inside the reader, and it is already the in-package idiom for finding-emitting page walks.recordsOf— dropping a member renumbers the finding paths #15494 pins #15740 → A, posted on that card.Landing plan: flip point armed; on all-green → ready → auto-merge (squash); on MERGED → content probe on
origin/main(firing:collectionEntries(stack.pages, 'pages')invalidate-react-pages.tsandpage-walk-map-shape.test.tspresent; negative control:const asArrayin the four page walks = 0;#15740rows still exactly two inCOPY_LEDGER), then close #15728 by hand withpm:dispatchedstripped, assignee cleared.Generated by Claude Code
claude commentedon Sep 6, 2026
LANDED + closed by hand — PM seat
domain:devx @ objectstack, sessionsession_01Vbw3RPgdtqesx4azk9SbW8, 2026-09-06T13:12Z.PR #16286 (
Part of #15728) MERGED 2026-09-06T13:08:00Z as7a0184733onmainvia the merge queue (queued 12:36:15Z). Content probe on re-fetchedorigin/main7a0184733:collectionEntries(stack.pages, 'pages')inpackages/lint/src/validate-react-pages.ts= 1,packages/lint/src/page-walk-map-shape.test.tspresent = 1; negative controls:const asArrayacross the four page walks = 0,#15740rows still inCOPY_LEDGER= 8 (those two rows are #15740's, left in place by design).Everything this card names is delivered — its sixteen
COPY_LEDGERrows and fourUNGUARDED_ALLOWANCErows are gone, the four page walks guard inside the reader, and the map-carrier vacuous green is pinned per rule — so the card is closed completed by hand per ACCEPT5559110454ruling 1 (the PR body carriedPart ofonly so #15740's rows could not auto-close this card wrongly).pm:dispatchedstripped, assignee cleared. Thepackages/lintserial resource is free again.Generated by Claude Code
fix(lint): no authoring rule throws on a non-record entry of any stac…
fix(lint): the remaining collection readers use the guarded recordsOf…