Skip to content

finding: two lint readers cannot move onto recordsOf — dropping a member renumbers the finding paths #15494 pins #15740

Description

@claude

Found while implementing #15636; filed as a finding only, not claimed.

#15636 re-pointed 20 lint collection readers onto the shared guarded recordsOf. Two of the 23 it set out to re-point could NOT be re-pointed, and the reason is a contract collision worth settling once rather than rediscovering per reader.

What collides

recordsOf DROPS a non-record member. Every reader that reports a positional path therefore renumbers: an author who writes

objects:
  -
  - name: crm_lead

is told about objects[0] where their file says the object is the second item.

#15552 already ruled that this is acceptable for stack.objects — non-record-object-entry.test.ts says the index "is allowed to differ … That is a difference in the path, never in whether the object was judged." But #15494 ruled the OTHER way for the reference-integrity suite, and pinned it:

  • packages/lint/src/reference-integrity-suite.test.ts asserts validateObjectFieldRefs reports objects[1].highlightFields[1] and validateListViewFieldRefs reports objects[1].listViews.all.columns[1], with the comment "the guard drops it from the GRAPH, while each member's own loop keeps walking the raw array, so reported positions stay stable against the author's file."

So validate-object-field-refs.ts and validate-list-view-field-refs.ts keep a private copy of the coercion not because anyone forgot them, but because the copy is what supplies the INDEX. Both already guard every member with isRec inside the loop, so neither has ever thrown; re-pointing them buys no crash fix and costs the pinned path. #15636 measured that directly: re-pointing them turned those two pins red and nothing else, and the change was reverted to byte-identical-to-main.

They are carried in COPY_LEDGER and UNGUARDED_ALLOWANCE in packages/lint/src/collection-coercion-single-copy.test.ts, both dated and pointing here.

The decision

Roughly three ways out, and this needs a maintainer to pick one before anybody re-points these two (or the sixteen in #15728, several of which build positional paths the same way):

  1. Accept renumbering everywhere. Re-point the two, update the indexObjectGraph throws a TypeError on a null entry in stack.objects — every reference-integrity rule crashes on it #15494 pins, and state once that a positional index counts readable entries and not file lines. Cheapest; silently degrades every positional path a stack with a junk member produces.
  2. Add an index-preserving companion in object-graph.ts — something like an entriesOf returning each readable record with the index it occupied in the RAW list. One predicate still, two readings of it, and every reader can then report the author's position while skipping what it cannot read. This is the only option that satisfies both pins at once, and it is a change to a shared module.
  3. Leave the two as they are permanently and record that positional readers are exempt from the one-copy rule. Honest, but it keeps a copy of the predicate alive for reasons that are not in the predicate.

Option 2 looks right from here — the two contracts are not actually in conflict, they are two different questions being answered by one return value — but it changes an exported surface, so it is not a developer's call.

Context


Generated by Claude Code

Activity

  1. claude commented on Sep 5, 2026

    @claude
    ContributorAuthor

    Graded → pm:awaiting-maintainer (decision), domain:devx — PM seat domain:devx, session 012zGPuVVX3deAx9LdjK8jCk.

    The collision is real and is two contracts answering two questions with one return value: #15494 pins finding paths to the author's file (objects[1].highlightFields[1]), #15552 accepted renumbering for stack.objects. Which governs, and whether packages/lint/src/object-graph.ts gains an index-preserving companion, is a published-surface decision for the maintainer.

    option cost benefit
    A Accept renumbering everywhere; re-point the two readers, update the #15494 pins, state once that a positional index counts readable entries silently degrades every positional path a stack with a junk member produces cheapest, one predicate
    B Add entriesOf (each readable record with the index it occupied in the RAW list) beside recordsOf; both pins hold changes an exported surface the two contracts stop conflicting; #15728's positional readers get the same answer
    C Leave the two readers on the raw array and exempt positional readers from the one-copy rule keeps copies alive for a reason not in the predicate; #15728 half-closable honest, zero change

    Seat recommendation: B. Decides #15728's positional rows as well. Entered in seat post #6023's decision box; not nudged.


    Generated by Claude Code

  2. claude commented on Sep 6, 2026

    @claude
    ContributorAuthor

    Input from #15728's landing (PM seat domain:devx @ objectstack, session session_01Vbw3RPgdtqesx4azk9SbW8, 2026-09-06T12:09Z) — no state change on this card.

    PR #16286 (Part of #15728) re-points four page-walk readers that emit positional pages[N].source paths onto collectionEntries (packages/lint/src/collection-entries.ts:68, #6662) instead of recordsOf: it drops non-record members inside the reader and hands each survivor the path it actually sits at, so no positional path is renumbered. That is evidence bearing on this card's option 2 ("an index-preserving companion in object-graph.ts"): a companion with that property already exists in the package and is the in-package idiom for finding-emitting walks. It does not decide this card — its two readers (validate-object-field-refs.ts, validate-list-view-field-refs.ts) pin an objects[N]… shape and their COPY_LEDGER rows were deliberately left in place by #16286 because this card owns them. Whoever picks this up can cost option 2 against collectionEntries before assuming a new reader is needed.


    Generated by Claude Code

  3. huangyiirene commented on Sep 9, 2026

    @huangyiirene
    Collaborator

    状态转换:pm:awaiting-maintainer → pm:queue(2026-09-09)

    维护者回批逐字:「C 桶那 24 张的批量转 pm:queue 同意」。

    判据:两个 lint 读取方迁不到 recordsOf,因为删掉一个成员会重编号 #15494 钉住的 finding 路径。⇒ 这是技术任务之间的顺序与依赖(先改 pin 还是先改读取方),SKILL.md〈升级与决策〉把它逐字列在具名不升级类里:「技术任务间的顺序与依赖」。⛔ 无产品语义、⛔ 无公开契约movement。

    tooling、domain:devx、finding 留下。

    ⚠️ 根因见 #17017。


    Generated by Claude Code

  4. os-tesla commented on Sep 12, 2026

    @os-tesla
    Collaborator

    Ruling recorded — 2, through the companion that already exists: the two readers re-point onto collectionEntries, which preserves the author's index; the private copies go (director seat, decision batch #123 item 2, 2026-09-12)

    Maintainer, verbatim (live PM chat, 2026-09-12T07:1xZ), to decision batch #123 presented as 1B·2(2)·3D·4C·5(1): 「同意」.

    Derived first from the long-term axis: a diagnostic path always names the position in the author's file, and one shared implementation serves every reader. The triage speed-read priced option 2 as a new export; measured, the index-preserving companion already exists — collectionEntries (packages/lint/src/collection-entries.ts:68, #6662), which PR #16286 (Part of #15728) already used for four page-walk readers that emit positional pages[N].source paths. So option 2 costs no public surface, and axis ④'s only objection dissolves. Option 1 would institutionalise a wrong position; option 3 keeps two private copies alive against the one-copy gate.

    What is ruled

    1. validate-object-field-refs.ts and validate-list-view-field-refs.ts re-point onto collectionEntries; their private coercion copies are deleted.
    2. indexObjectGraph throws a TypeError on a null entry in stack.objects — every reference-integrity rule crashes on it #15494's pins (objects[1].highlightFields[1], objects[1].listViews.all.columns[1]) stay green unchanged — that is the acceptance.
    3. COPY_LEDGER and UNGUARDED_ALLOWANCE in collection-coercion-single-copy.test.ts drop these two entries.
    4. finding: sixteen lint collection readers still hold a private copy of the recordsOf coercion #15728's remaining positional readers follow the same route; skills(pm-dispatch): pm:awaiting-maintainer has a semantic contract and zero machine carrier — 49 of 55 cards in it are waiting for a JUDGEMENT, not for a hand, and nothing can tell the two apart #17017 (root cause) is unchanged by this ruling.
    5. Clause-②: no.

    State

    needs-user-decision → pm:queue; domain:devx / tooling / priority:p3 kept.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions