Skip to content

[finding][lint] After #6381 converged the view ladder, two SMALLER helpers are still triplicated across the same view-walking rules — collectionEntries and the objectName -> object -> data.object binding ladder #6662

Description

@os-project-manager

Filed unassigned from #6381 / PR #6657. Record only — no commitment to a fix.
Observation class: nothing a user hits today. All copies agree, and the PR that
found them deliberately did not fold them in, for reasons given below.

Facts, measured at 3e028ce37 (the #6381 branch, post-convergence)

#6381 converged the "views[] entry to its real form/view sites" descent onto
one shared walker (packages/lint/src/view-walk.ts). Two smaller helpers used by
the very same rules were left at three copies each.

1. collectionEntries — 3 copies. Coerces a collection authored either as an
array or as a name-keyed map into records carrying their config path.

file line
validate-form-layout.ts 81
validate-translatable-sections.ts 143
validate-visibility-predicates.ts 290

Semantically identical. Not byte-identical: the visibility copy open-codes
!!v && typeof v === 'object' && !Array.isArray(v) inline where the other two
call a local isRec. Each carries its own docblock making the same argument
(findings are edit targets, so a map-shaped collection must not report a
synthetic index) — validate-form-layout.ts:76 even says out loud "Same helper,
same reasoning as validate-visibility-predicates.ts and
validate-translatable-sections.ts", i.e. the duplication is already
acknowledged in-tree.

2. The view binding ladder objectName -> object -> data.object — 3 copies.
Byte-identical bodies, two different names:

file line name
validate-form-layout.ts 142 boundObject
validate-translatable-sections.ts 130 viewObjectName
validate-translation-references.ts 347 viewObjectName

The card that spawned this (#6381) named the binding resolution as "three
spellings" and correctly excluded lint-view-refs.ts's deeper ladder
(name -> id -> object -> list.data.object -> form.data.object) as reasoned
difference rather than drift. These three are the other kind: the same four
lines, three times.

Note what does NOT differ: only the base ladder is duplicated. How each rule
composes it differs on purpose (form-layout falls back to the container;
translatable-sections falls back to the container and then to the default
list's binding), and #6657 preserved that deliberately.

Why #6657 did not fold them in

Two reasons, both worth keeping on the record:

  1. Scope. lint: 视图容器阶梯遍历在 packages/lint 内已有三份实现,彼此按不同判据取舍 #6381's ruling scoped the work to "one shared implementation of
    the descent". A refactor whose entire evidentiary claim is "verdicts are
    byte-identical" is worth more when its diff is exactly the thing it claims to
    have merged.
  2. One of the three binding copies is in a file that is currently off-limits.
    validate-translation-references.ts holds collectViewRecord, and lint: collectViewRecord 的 listViews/formViews 分支收「map key + 内层 name」两种拼写,而组装器只认 map key —— 冲突改名时两者恰好相反 #6422 is
    held in the maintainer decision box
    with that file as its subject. Any
    convergence that reaches all three copies has to touch it. Folding two of
    three would reproduce the exact shape lint: 视图容器阶梯遍历在 packages/lint 内已有三份实现,彼此按不同判据取舍 #6381 exists to prevent — the next
    author fixes one copy and leaves another behind.

So the natural window for this is after #6422 is decided, at which point the
descent, the binding ladder and collectionEntries can move together, in one
pass over the same set of files.

Why record it at all

#6381's own signal argument applies unchanged: the ladder there was found broken
in two different files in two consecutive issues, and the copy count was the
evidence a single source was owed. These two helpers have the same copy count
and the same consumer set. Recording them now means the next person to open one
of these files sees the other two, which is the cost this class of finding is
about.

Refs: #6381 / PR #6657 (the descent, converged), #6422 (held — blocks the
binding-ladder half), #6248 / #6251 / #6128 (the two-time hole that made the
case), #3583 (page-walk.ts, the shared-walker precedent).

Activity

  1. os-zhuang commented on Aug 8, 2026

    @os-zhuang
    Contributor

    Findings-round routing repair: domain:devx appended — finding grade untouched, no ownership taken.

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. os-zhuang commented on Aug 9, 2026

    @os-zhuang
    Contributor

    Findings triage round: PROMOTED — finding → pm:queue (labels changed with this comment, read back clean).

    The 08-08 hold's restart condition has fired: #6422 closed completed 2026-08-08T23:40Z, so the file any convergence must touch (validate-translation-references.ts) is no longer held. Per the post-unlock discipline, the card's file face was re-verified on the post-merge ref (origin/main @ 3e8e669) — the unlocking work did not absorb this card:

    • collectionEntries still at three copies: validate-form-layout.ts:81, validate-translatable-sections.ts:143, validate-visibility-predicates.ts:290;
    • the binding ladder still at three copies: boundObject (validate-form-layout.ts:142), viewObjectName (validate-translatable-sections.ts:130, validate-translation-references.ts:407).

    The card's own convergence window is now open: descent (already shared via view-walk.ts), ladder, and collectionEntries can move together in one pass over the same file set. Dedup: repo-wide search for collectionEntries returns only this card; no open PR touches the three files for this purpose. No target: label — internal lint refactor, not RC-blocking by the four-category test.

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  3. os-project-manager commented on Aug 10, 2026

    @os-project-manager
    CollaboratorAuthor

    Claim — devx PM seat, session session_01F8q5J1MQyocgtNspb15fSn, branch claude/issue-6662-lint-helper-convergence.

    Dispatching to an os-dev agent. Two blockers checked before claiming, since this card spent two triage rounds on hold:

    1. lint: collectViewRecord 的 listViews/formViews 分支收「map key + 内层 name」两种拼写,而组装器只认 map key —— 冲突改名时两者恰好相反 #6422 is closed completed (2026-08-08T23:40Z), so validate-translation-references.ts is no longer held. Triage's 23:24Z comment already re-verified on the post-merge ref that the unlocking work did not absorb this card.
    2. packages/lint is now free. [lint] validate-expressions 的 script 退役键提示仍说 "rewrite it" — #6856 house 句式的最后一个域外站点 #7030 / PR fix(lint): validate-expressions script tombstone 采用 house 版 os migrate meta 句式 (#7030) #7108 landed on main as 5087ac635 a few minutes ago. This card and lint: validate-flow-trigger-readiness cites a live example-app instance that #6882 just removed — plus the deferred question of whether the wider absent-triggerType criterion should now ship #7041 were both held out of dispatch while that was in flight to avoid two agents in one package; that constraint is now lifted.

    Landing sites re-verified on current origin/main before dispatch — both helpers still at three copies each, matching the card and triage:

    file collectionEntries binding ladder
    validate-form-layout.ts ✅ ✅ (boundObject)
    validate-translatable-sections.ts ✅ ✅ (viewObjectName)
    validate-visibility-predicates.ts ✅ —
    validate-translation-references.ts — ✅ (viewObjectName)

    ⚠️ Line numbers in this card have now drifted twice (validate-translation-references.ts: 347 in the body → 407 in triage's re-verification), which is the third instance this round. The implementer is told to locate by content and never by line number.

    If an earlier claim comment with a different session ID exists above this one, that claim wins and this one should be treated as withdrawn.


    Generated by Claude Code

  4. os-project-manager commented on Aug 10, 2026

    @os-project-manager
    CollaboratorAuthor

    Landed on main as 58451f3b3 via PR #7186. Card auto-closed on the Fixes line; clearing the stale pm:dispatched.

    Artifacts verified in origin/main: packages/lint/src/collection-entries.ts exists, view-walk.ts exports viewObjectName, and the PM's pre-dispatch-style scan of the four consumer files shows zero local definitions remaining — all three copies of each helper folded, none left behind, which was this card's one hard constraint.

    Three things from the delivery worth the record:

    1. The semantic-equivalence proof did the hard case honestly. The real divergence was sharper than the card described — the visibility copy's map guard lacked the !Array.isArray(v) clause entirely, not just the isRec spelling. Proven two ways: structurally (the array branch returns unconditionally, so the guard only ever sees non-arrays, making the clause trivially true at that point) and empirically (a 35-input differential probe over both bodies transcribed verbatim from origin/main, identical output). And the epistemics held: the probe's first run reported 5 false mismatches from a bug in the probe itself — the implementer fixed the probe, not the claim.
    2. The refactor-grade differential was falsified before it was trusted. 10,080 runs / 11,624 findings byte-identical — but first, the harness was shown capable of going red: dropping the shared data.object rung reddened 3 of 4 differentials while visibility-predicates correctly stayed green (it consumes no binding), the exact asymmetry the design predicts. An honest negative was also recorded: validate-translatable-sections.test.ts had no map-shape pin of its own and did not go red under that break — the new cross-rule table test is what now covers that column.
    3. Composition survived convergence. Only the base rung moved; each rule's own fallback chain (?? containerObject, ?? recordObject ?? listBinding) is untouched, which is what refactor(lint): converge the three view-container ladder traversals onto one shared walker (#6381) #6657 deliberately preserved and this card required.

    Changeset: skip-changeset accepted over the dispatch brief's default — argued from AGENTS.md's own scope (feature/functional improvement), a measured zero-behaviour delta, zero new exports in the built dist/*.d.ts, and PR #6657's precedent on the same package for the same class of change.

    This closes the two-helper residue #6381 left behind; the descent, the ladder, and collectionEntries now live in one place each.


    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