Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

[finding] 落盘清单那条 lucide-react pin 用 inRepoRangesOf(...).sort()[0] 取仓内区间的任意一员(最小值),自己不断言一致性 —— 分裂时可能对着少数派判绿 #4991

Closed

Description

@yinlianghui

Filed unassigned while implementing #4974(PR 见该卡)。不由那个 PR 修:#4974 的完成范围是两条锚漂移规则的报告完整性(逐名 expect 改累积);这一条是同文件另一条 it 里的锚解析可靠性,是另一类事实,不该顺手改进那个 PR。

事实

packages/cli/src/__tests__/app-generator.test.ts 的 writes a manifest whose @object-ui ranges name this CLI version(#4974 的 PR 落地后在 :1178 附近;#4973 引用过它的旧行号 :1139)末尾一行:

expect(manifest.dependencies?.['lucide-react']).toBe(
  Object.keys(inRepoRangesOf('lucide-react')).sort()[0]
);

inRepoRangesOf(name) 返回的是按区间分组的字典({ range: [manifest, …] })—— 它被设计成能表达「仓内不一致」这件事,所以同文件那条锚规则在引用 ranges[0] 之前先断言 toHaveLength(1)(仓内必须一致),分裂时点名分歧清单并要求先收敛。

这一行没有那道前置:它直接取 Object.keys(...).sort()[0],即字符串序最小的那个区间。也就是说仓内一旦分裂,它判的是「模板 == 分裂集合里字典序最小的那一员」,而这一员既不是多数派,也与「该依赖在本仓的区间」无关 —— 它只是排序的副产品。

实测(#4974 反向验证 ②b 的副产物)

为构造「前置败」把 packages/plugin-tree/package.json 的 lucide-react 单独改成 ^1.30.0(其余 21 处为 ^1.31.0)后,这条断言的期望值变成 ^1.30.0 —— 一个清单的少数派 —— 并以此判模板的 ^1.31.0 为错:

FAIL … > writes a manifest whose @object-ui ranges name this CLI version
AssertionError: expected '^1.31.0' to be '^1.30.0'

它这次是红的,但红的理由是错的:模板与 21 个清单一致,分歧在仓内。反过来更值得注意:若分裂里字典序最小的那一员恰好等于模板当时的区间,这一行就会绿,而锚本身是有歧义的。

今天不可达:22 处声明一致,不存在分裂;并且同文件那条锚规则会在分裂时先红并点名(#4974 的 PR 把这一点固化为「前置整趟先跑」)。所以这条属观察类 —— 它的安全性来自邻居,而不是来自它自己。

建议方向(不预设,交 triage)

  • 走同一条一致性前置:让这条 pin 也经「仓内必须一致」后再引用唯一区间(或直接复用锚规则算出的期望值),而不是 .sort()[0];
  • 或者承认这条 pin 与锚规则重合(它判的是落盘清单,而锚规则判的是构建清单,而同文件 writes exactly the routed app file map, byte for byte 已经钉住两者逐字节相等),把 lucide 这一行删掉、只留 @object-ui/* 那一段。

第二个方向要先确认那条 byte-for-byte 断言是否真的覆盖同一份清单(看起来是),否则会丢覆盖。

同族参考:同文件注释自己写下过「这一行曾硬编码字面量,是锚规则的第二份、更弱的副本」——.sort()[0] 是那份副本残留的另一半。

Activity

  1. os-support-ai commented on Aug 19, 2026

    @os-support-ai
    Collaborator

    Triage first-touch — concentrated round #2 (triage seat, session session_019ZyCHcjXeTFPiS8RWRk4nN, 2026-08-19): promoted finding → pm:queue · Task. A pin whose expected value is a sort artifact judges the wrong thing in both directions the moment the repo splits (measured: it went red blaming the template for a 21-vs-1 split it was not party to). Dispatch route, in the card's own preference order: put the lucide-react line behind the same in-repo-consistency precondition the @object-ui/* anchor rule uses (assert toHaveLength(1) before referencing the single range, or reuse the anchor rule's computed expectation); deleting the line instead is acceptable only after verifying the same-file byte-for-byte manifest assertion actually covers the written manifest's lucide-react entry — verify, then choose, and say which in the PR. Size/model suggestion: S-mechanical, sonnet.


    Generated by Claude Code

  2. added
    domain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repo
    on Aug 21, 2026
  3. yinlianghui-tw commented on Aug 24, 2026

    @yinlianghui-tw
    Contributor

    Claim: PM loop round 9
    Session: session_019b5UBNMtTzKbVtZZGvFuxe · Branch: claude/issue-4991-lucide-pin-range-consistency · Worktree: objectui-issue-4991
    Domain: domain:devx (@ objectui, seat #5748) · Container & model: S, mode:subagent, model: sonnet · Clause-②: no
    File surface: packages/cli/src/__tests__/app-generator.test.ts
    Serial constraints cleared: in-flight 0. Disjoint from open PRs #5988 / #5989 / #5991 / #5995 and co-dispatched #5754, #4938, #4964, #4996.

    PM-verified premise on origin/main @ 53dc89db8

    inRepoRangesOf is defined at packages/cli/src/__tests__/app-generator.test.ts:292 and read at :588, :652 (Object.keys(inRepoRangesOf(name))[0]) and :672. The card's complaint is that the pin takes an arbitrary member of the in-repo ranges — the first key / .sort()[0] — and asserts against it, without asserting the ranges are consistent in the first place.

    ⚠️ So when the repo's declarations for a package split across two ranges, the pin quietly picks one and can validate against the minority spelling, reporting green while the repo is internally inconsistent. The pin's own subject — "the repo agrees on this dependency's range" — is the thing it never checks.

    Zone 2 — measure first

    Rulings (⛔ not re-adjudicable)

    • ⛔ Scope: the consistency assertion in this test file. Changing any package's declared range is out of scope — report it.
    • The assertion must name the package and all its competing ranges when it fails. A bare "ranges differ" reproduces the diagnosis problem one level up.

    Gates

    Derive from your diff. At minimum packages/cli's own test run and type-check, plus Lint. Run check-changeset-presence.mjs and follow its verdict — ⚠️ packages/cli is a released package, so a src/ change may owe a changeset even though this is a test file; let the gate decide rather than assuming. ⛔ No skip-changeset label (#4912/#3724).

    ⚠️ Restore idiom (objectstack#11648): git checkout HEAD -- <path>, prove with git diff HEAD.

    Draft PR, Fixes #4991, report as an issue comment with the <!-- os-dev-report --> marker (⚠️ sanitizer strips it — read back, repost as literal text). Report the per-package range counts you measured and whether the defect is live or latent.


    Generated by Claude Code

  4. added a commit that references this issue on Aug 24, 2026
    58ee056
  5. yinlianghui-tw commented on Aug 24, 2026

    @yinlianghui-tw
    Contributor

    <!-- os-dev-report -->

    {
      "issue": 4991,
      "status": "done",
      "branch": "claude/issue-4991-lucide-pin-range-consistency",
      "pr": "https://github.com/objectstack-ai/objectui/pull/6021",
      "premise_still_valid": true,
      "summary": "Extracted a shared soleInRepoRangeOf(name) helper in packages/cli/src/__tests__/app-generator.test.ts that asserts the repo's in-repo declarations for a dependency agree (naming the package and every competing range plus the manifests that declare each) before returning the sole range, reusing the precondition the anchor-table loop already had. Routed both prior arbitrary-member reads through it: the lucide-react pin at the old :1180 (Object.keys(...).sort()[0]) and the pre-fix reverse-verification's drift check at the old :652 (Object.keys(...)[0]). The anchor-table loop itself was also refactored to call the helper instead of duplicating the same assertion. No package's declared range was changed.",
      "tests": "All at final HEAD 58ee056dd. `pnpm exec vitest run packages/cli/src/__tests__/app-generator.test.ts` -> 41 passed (41). `pnpm --filter '@object-ui/cli' run type-check` -> tsc --noEmit clean, exit 0. `pnpm --filter '@object-ui/cli' run lint` -> 0 errors, 11 pre-existing warnings unrelated to this diff, exit 0. `node scripts/check-changeset-presence.mjs` -> pass, empty-frontmatter changeset added per the gate's own instruction. Reverse verification: mutated packages/plugin-tree/package.json's lucide-react to ^1.30.0 (a real 22-vs-1 split), confirmed on disk via git diff --stat; ran the suite and both the lucide-react pin and the anchor-table precondition test went red with `AssertionError: in-repo manifests disagree on lucide-react: {\"^1.31.0\":[...22 manifests],\"^1.30.0\":[\"packages/plugin-tree/package.json\"]} -- settle on one range first` -- the correct diagnosis, versus the old code's issue-documented wrong diagnosis (blaming the template, expected '^1.31.0' to be '^1.30.0'). Restored via `git checkout HEAD -- packages/plugin-tree/package.json`, proved with `git diff HEAD` (empty), re-ran the suite: 41/41 pass again.",
      "open_questions": [],
      "out_of_scope_findings": [],
      "measured_range_counts": {
        "lucide-react": 1,
        "@tailwindcss/postcss": 1,
        "@vitejs/plugin-react": 1,
        "postcss": 1
      },
      "defect_status": "latent — every DEPENDENCY_ANCHORS 'in-repo' name has exactly 1 declared range on main @ 53dc89db8 today; no live wrong-green existed. No split was manufactured to justify the change; the reverse-verification split above was created and restored on disk solely to prove the new assertion fires correctly."
    }

    Generated by Claude Code

  6. yinlianghui-tw commented on Aug 24, 2026

    @yinlianghui-tw
    Contributor
    {
      "issue": 4991,
      "status": "done",
      "branch": "claude/issue-4991-lucide-pin-range-consistency",
      "pr": "https://github.com/objectstack-ai/objectui/pull/6021",
      "premise_still_valid": true,
      "summary": "Extracted a shared soleInRepoRangeOf(name) helper in packages/cli/src/__tests__/app-generator.test.ts that asserts the repo's in-repo declarations for a dependency agree (naming the package and every competing range plus the manifests that declare each) before returning the sole range, reusing the precondition the anchor-table loop already had. Routed both prior arbitrary-member reads through it: the lucide-react pin at the old :1180 (Object.keys(...).sort()[0]) and the pre-fix reverse-verification's drift check at the old :652 (Object.keys(...)[0]). The anchor-table loop itself was also refactored to call the helper instead of duplicating the same assertion. No package's declared range was changed.",
      "tests": "All at final HEAD 58ee056dd. `pnpm exec vitest run packages/cli/src/__tests__/app-generator.test.ts` -> 41 passed (41). `pnpm --filter '@object-ui/cli' run type-check` -> tsc --noEmit clean, exit 0. `pnpm --filter '@object-ui/cli' run lint` -> 0 errors, 11 pre-existing warnings unrelated to this diff, exit 0. `node scripts/check-changeset-presence.mjs` -> pass, empty-frontmatter changeset added per the gate's own instruction. Reverse verification: mutated packages/plugin-tree/package.json's lucide-react to ^1.30.0 (a real 22-vs-1 split), confirmed on disk via git diff --stat; ran the suite and both the lucide-react pin and the anchor-table precondition test went red with AssertionError naming lucide-react and both competing ranges with their manifests -- the correct diagnosis, versus the old code's issue-documented wrong diagnosis (blaming the template). Restored via git checkout HEAD -- packages/plugin-tree/package.json, proved with git diff HEAD (empty), re-ran the suite: 41/41 pass again.",
      "open_questions": [],
      "out_of_scope_findings": [],
      "measured_range_counts": {
        "lucide-react": 1,
        "@tailwindcss/postcss": 1,
        "@vitejs/plugin-react": 1,
        "postcss": 1
      },
      "defect_status": "latent - every DEPENDENCY_ANCHORS 'in-repo' name has exactly 1 declared range on main @ 53dc89db8 today; no live wrong-green existed. No split was manufactured to justify the change; the reverse-verification split above was created and restored on disk solely to prove the new assertion fires correctly."
    }

    Generated by Claude Code

  7. yinlianghui-tw commented on Aug 24, 2026

    @yinlianghui-tw
    Contributor

    os-dev-report

    {
      "issue": 4991,
      "status": "done",
      "branch": "claude/issue-4991-lucide-pin-range-consistency",
      "pr": "https://github.com/objectstack-ai/objectui/pull/6021",
      "premise_still_valid": true,
      "summary": "Extracted a shared soleInRepoRangeOf(name) helper in packages/cli/src/__tests__/app-generator.test.ts that asserts the repo's in-repo declarations for a dependency agree (naming the package and every competing range plus the manifests that declare each) before returning the sole range, reusing the precondition the anchor-table loop already had. Routed both prior arbitrary-member reads through it: the lucide-react pin at the old :1180 (Object.keys(...).sort()[0]) and the pre-fix reverse-verification's drift check at the old :652 (Object.keys(...)[0]). The anchor-table loop itself was also refactored to call the helper instead of duplicating the same assertion. No package's declared range was changed.",
      "tests": "All at final HEAD 58ee056dd. `pnpm exec vitest run packages/cli/src/__tests__/app-generator.test.ts` -> 41 passed (41). `pnpm --filter '@object-ui/cli' run type-check` -> tsc --noEmit clean, exit 0. `pnpm --filter '@object-ui/cli' run lint` -> 0 errors, 11 pre-existing warnings unrelated to this diff, exit 0. `node scripts/check-changeset-presence.mjs` -> pass, empty-frontmatter changeset added per the gate's own instruction. Reverse verification: mutated packages/plugin-tree/package.json's lucide-react to ^1.30.0 (a real 22-vs-1 split), confirmed on disk via git diff --stat; ran the suite and both the lucide-react pin and the anchor-table precondition test went red with AssertionError naming lucide-react and both competing ranges with their manifests -- the correct diagnosis, versus the old code's issue-documented wrong diagnosis (blaming the template). Restored via git checkout HEAD -- packages/plugin-tree/package.json, proved with git diff HEAD (empty), re-ran the suite: 41/41 pass again.",
      "open_questions": [],
      "out_of_scope_findings": [],
      "measured_range_counts": {
        "lucide-react": 1,
        "@tailwindcss/postcss": 1,
        "@vitejs/plugin-react": 1,
        "postcss": 1
      },
      "defect_status": "latent - every DEPENDENCY_ANCHORS 'in-repo' name has exactly 1 declared range on main @ 53dc89db8 today; no live wrong-green existed. No split was manufactured to justify the change; the reverse-verification split above was created and restored on disk solely to prove the new assertion fires correctly."
    }

    Generated by Claude Code

  8. yinlianghui-tw commented on Aug 24, 2026

    @yinlianghui-tw
    Contributor

    ACCEPT — PR #6021. Landing on CI convergence.

    Reviewer of record: domain:devx @ objectui seat (#5748), round R9, PM session session_019b5UBNMtTzKbVtZZGvFuxe.

    Both spellings fixed — the half-fix I warned about did not happen

    The dispatch flagged that :652 used Object.keys(...)[0] while the card cited .sort()[0], and that hardening one and leaving the other is the failure this lane hit twice today (#5007's two citation sites, #4902's two directions). Both were routed through the new soleInRepoRangeOf(name) helper, and the anchor-table loop — which already had the precondition inline — was refactored to call it rather than keep a third copy. One assertion, three call sites, ⛔ no duplicated invariant left to drift.

    ⚠️ The reverse verification found something better than "the assertion fires"

    It mutated packages/plugin-tree/package.json to create a real 22-vs-1 split, confirmed the mutation on disk, and compared diagnoses:

    what the failure said
    old code blamed the template — expected '^1.31.0' to be '^1.30.0'
    new code in-repo manifests disagree on lucide-react: {"^1.31.0":[…22 manifests],"^1.30.0":["packages/plugin-tree/package.json"]} — settle on one range first

    ⚠️ So the old pin did not merely miss a split — it actively misdiagnosed it, pointing the reader at the generator template when the defect was a single package manifest. That is worse than a silent gap and is the real value of this card; the issue had documented that wrong diagnosis and it is now measured.

    Restored with git checkout HEAD -- <path> (the objectstack#11648 safe spelling), proved with an empty git diff HEAD, 41/41 green again.

    Latent, stated plainly

    lucide-react, @tailwindcss/postcss, @vitejs/plugin-react, postcss — 1 declared range each on main @ 53dc89db8. ⛔ No live wrong-green existed, and the report says so rather than implying one was fixed. ⛔ No split was manufactured to justify the work; the one created for ablation was restored and proved gone.

    Scope held

    ⛔ No package's declared range was changed — exactly as ruled. Empty-frontmatter changeset added per the gate's own instruction, not by assumption.

    Landing

    packages/cli/src/__tests__/** and .changeset/** are not governed surface. Maintainer's ruling, 2026-08-24, verbatim: 「授权 auto-merge(推荐)」 — every check green, ⛔ not the required subset.


    Generated by Claude Code

  9. added a commit that references this issue on Sep 1, 2026
    90592ee
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

domain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repopm:dispatched

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions