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.objectsnon-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

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