Skip to content

finding(devx): upstream-port-pin.json covers only scripts/, so the verbatim cross-repo ports under .claude/hooks/** have no drift gate — the same shape #6642 measured at ~212 KB #7263

Closed

Description

@yinlianghui

Observed while landing the ported self-test matrix on #6451. Not a defect in anything that ships — an undrilled-drift gap. Filed rather than fixed: registering a new file in the pin is a scope decision about which surfaces the ledger governs, and .claude/** is governed surface with its own merge rules, so it is not a bounded in-place edit for a matrix-only PR.

The gap

scripts/check-upstream-port-parity.mjs exists because a verbatim cross-repo port drifted silently and went on reporting confidently — #6642 measured scripts/pm/check-half-states.mjs at 4,637 diff lines and 458 missing predicate cases before the gate landed. Its header states the harm direction plainly: "A drifted copy does not fail; it reports."

The ledger it verifies (scripts/upstream-port-pin.json) currently holds exactly two entries, both under scripts/:

$ node scripts/check-upstream-port-parity.mjs
✓ scripts/pm/check-half-states.mjs: byte-identical to objectstack-ai/objectstack@bf10debd5:... modulo 11 declared divergence(s).
✓ scripts/invoked-as.mjs: byte-identical to objectstack-ai/objectstack@bf10debd5:... modulo 9 declared divergence(s).
✓ check-upstream-port-parity: 2 ported file(s) match ... modulo their declared divergences.

Meanwhile .claude/hooks/ is this repo's densest concentration of verbatim ports from the same upstream, and the history is a list of drifts each caught by a human noticing, one at a time, never by a gate: #5459 (quote-escape fix not ported), #5712 ("second drift item… reads a shell COMMENT as a command"), #5789 (the fix family), #6042 (the backslash fail-open that objectstack closed in #11131). Every one of those is the failure mode check-upstream-port-parity was built to make impossible, in a directory the ledger does not look at.

Why now

The matrix landing on #6451 is a fresh instance with the pin's exact shape already established, and measured rather than assumed:

  • upstream blob: objectstack-ai/objectstack:.claude/hooks/guard-main-checkout.selftest.sh @ d63c8a2
  • ported byte-for-byte, then exactly 4 declared divergences, all comment/echo-label text: one added PORTED from… header paragraph, and two hole-reference re-pointings (#11809 to #7259, #11810 to #7260) that each carry the sibling number as a cross-ref
  • no executable line differs, which is why the sibling file ran here unmodified and returned 87 passed, 0 failed

So this one could be registered today with a one-sentence why per divergence, which the pin's self-test requires. It would then be the first entry that makes the ledger's promise reach the surface where the drift has actually kept happening.

The decision this needs (not asserted here)

  1. Does the ledger's scope extend to .claude/** at all, given that surface's human-merge rule and that a --resync rewrites the ported file in place? A --resync touching governed surface is the part worth thinking about before, not after.
  2. If yes, do the four existing hook ports get registered too, or only new ones from here on? The three already-drifted hooks would each need a divergence set written down first, which is real work and is the actual value.

Filed unassigned; no domain:* or type label set beyond finding.

Activity

  1. added
    domain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repo
    on Sep 5, 2026
  2. os-zhuang commented on Sep 5, 2026

    @os-zhuang
    Contributor

    分诊 · domain:devx / priority:p2 / pm:queue

    Anchor read, not guessed. 台账与关卡是 scripts/upstream-port-pin.json / scripts/check-upstream-port-parity.mjs ⇒ domain:devx。⚠️ 但被登记的对象在 .claude/hooks/**(治理面),所以一次 --resync 会改写治理面文件 —— 这是本卡真正的决策点,见下。

    ⭐ 复核 objectui origin/main a472b07(2026-09-05T01:04:19Z):upstream-port-pin.json 的结构与卡面所述一致 —— upstream.repo / upstream.ref,files[] 每项带 ported / upstreamPath / upstreamSha256 / divergences[](每条 id / why / upstream / ported),首项即 scripts/pm/check-half-states.mjs。⇒ 登记一个新文件需要为每条分歧写一句 why —— 这是成本所在,也是价值所在。

    定级 p2

    ⭐ 卡面把「为什么这不是可有可无」写得最好,引的是关卡自己的头注:

    「一份漂移的副本不会失败;它会报告。」(A drifted copy does not fail; it reports.)

    ⇒ 而这道关卡之所以存在,是因为 #6642 实测 scripts/pm/check-half-states.mjs 漂了 4,637 个 diff 行、缺 458 个谓词用例才被发现。

    ⭐ 而 .claude/hooks/ 是本仓同源逐字移植最密集的目录,它的历史是一串「每次靠人注意到」的漂移,从来没有一次是关卡抓到的:

    ⇒ ⭐ 四次,每一次都是 check-upstream-port-parity 被造出来就是为了让其不可能发生的那种失败,而它们全部发生在台账不看的那个目录里。 这就是 p2:不是「可能会漂」,是已经漂了四次。

    ⛔ 不是 p1:今天没有已知的活漂移(#6451 的移植测得 0 条可执行行差异,兄弟文件原样跑出 87 passed, 0 failed),而且四次历史漂移都已各自修好。

    ⭐ 时机是对的 —— 有一个形状已备好的现成条目

    卡面把它测出来了,而不是估计:

    • 上游 blob:objectstack-ai/objectstack:.claude/hooks/guard-main-checkout.selftest.sh @ d63c8a2
    • 逐字节移植,然后恰好 4 条已声明分歧,全是注释/echo 文案:一段新增的 PORTED from… 头,以及两处洞引用重指(#11809→#7259、#11810→#7260,各自带上兄弟编号作交叉引用)
    • 没有一行可执行行不同

    ⇒ ⭐ 这一条今天就可以登记,每条分歧一句 why 即可 —— 而它将是第一条让台账的承诺抵达那个真正在漂的面的条目。

    ⛔ 两个必须先答的决策 —— 卡面列出,本席点明哪个更硬

    1. ⭐ 台账的范围是否延伸到 .claude/**? ⚠️ 关键不在「能不能」,而在卡面点破的那一句:「一次 --resync 会就地改写被移植的文件」 —— 而 .claude/** 是人工合并的治理面。⇒ 一个能自动改写治理面文件的工具,是一个需要事先想清楚的东西,不是事后。 这一条是本卡真正的门槛。
    2. 若是,四个既有的 hook 移植也一并登记,还是只登记从现在起的新条目? ⚠️ 三个已漂过的钩子各自需要先写下一份分歧集合,那是真实的工作量,而那也正是真正的价值所在 —— 因为写下分歧集合的过程,就是把「这里为什么不同」从人的记忆变成台账。

    ⇒ ⛔ 本席不裁。⭐ 但建议裁决者注意一条低成本的中间答案:先只登记 selftest 那一条(4 条纯文案分歧,今天就能写),把 --resync 对治理面的写入能力单独关掉或要求人工确认 —— 这样第一条条目立刻生效,而第 1 个决策可以在有实例之后再答。⛔ 这是观察,不是裁决。

    ⛔ 范围围栏采纳:#6451 是「只加矩阵」的 PR,而在台账里登记一个新文件是一次关于「账本治理哪些面」的范围决策,且 .claude/** 有自己的合并规则 ⇒ 不是一次有界的就地编辑。 立卡而不是顺手加,是对的。

    ⛔ 本席不认领、不派发、不代裁。


    Generated by Claude Code

  3. claude commented on Sep 6, 2026

    @claude
    Contributor

    Claim: PM loop round R44
    Session: session_013uAaxiwgYDybsTNV9xwa1M
    Branch: claude/issue-7263-port-pin-hooks-and-governed-resync-guard
    Worktree: objectui-issue-7263
    Domain: domain:devx
    File surface: scripts/upstream-port-pin.json (one new entry: .claude/hooks/guard-main-checkout.selftest.sh with its divergences and why lines) + scripts/check-upstream-port-parity.mjs (--resync refuses to write under governed paths unless an explicit flag is passed; the check itself unchanged) + its tests; ⛔ no edit to any file under .claude/** (stop on breach; explain in the report)
    Container & model: M (a ledger entry with four written divergences + a refusal semantics change in the gate's --resync; judgment in the divergence prose), mode:subagent, model: opus — objectstack's node scripts/pm/dispatch-gates.mjs --tier REFUSES --repo objectstack-ai/objectui (measured this round: gate families are a property of the tree it runs in), so the tier is the PM's per-card judgment under the script's declared policy floor sonnet · default opus · ceiling fable; no path-derived mandate applies to this surface.
    Clause-②: no
    Serial constraints cleared: ⚠️ PR #7749 (draft, other seat) edits .claude/hooks/guard-main-checkout*.sh — the file this card REGISTERS but does not edit; registration pins the upstream sha and declared divergences, so if #7749 lands first the divergence set must be re-derived — the dev reads #7749's diff and states which; no open PR at 04:04Z touches scripts/upstream-port-pin.json or scripts/check-upstream-port-parity.mjs


    Generated by Claude Code

  4. claude commented on Sep 6, 2026

    @claude
    Contributor

    Claim: os-dev (dispatched by PM round R44)
    Session: session_013uAaxiwgYDybsTNV9xwa1M
    Branch: claude/issue-7263-port-pin-hooks-and-governed-resync-guard
    Worktree: /home/user/objectui-issue-7263 (base 5e33b7548)
    File surface: scripts/upstream-port-pin.json (one new entry) + scripts/check-upstream-port-parity.mjs (--resync refuses governed paths without an explicit flag) + its tests. No edit to any file under .claude/**.
    Inherited the PM claim 5556784871; this comment names the branch on the card by its owner.

    Generated by Claude Code, session session_013uAaxiwgYDybsTNV9xwa1M.


    Generated by Claude Code

  5. claude commented on Sep 6, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 7263,
      "status": "done",
      "branch": "claude/issue-7263-port-pin-hooks-and-governed-resync-guard",
      "pr": "https://github.com/objectstack-ai/objectui/pull/7954",
      "premise_still_valid": false,
      "summary": "Both ruled halves landed on scripts/ only; no file under .claude/** was edited. (1) --resync now refuses any ported path on the governed surface unless --rewrite-governed-file is passed, naming the path, the surface and the flag; the governed set is read from governedPathsIn in check-governed-queue-guard.mjs rather than re-listed, and the CHECK path is untouched (two self-test rows pin that a drifted governed port still reds with no flag). (2) .claude/hooks/guard-main-checkout.selftest.sh is registered against the blob the pin already names, with 14 declared divergences and a one-sentence why each. PREMISE FALSIFIED, in the card's own readiness measurement: #7263 said 'exactly 4 declared divergences, all comment/echo-label text, no executable line differs'. The pinned blob at bf10debd5 and the card's d63c8a2 blob are BYTE-IDENTICAL for this file, so the pin's ref is not the cause; PR #7686 landed on THIS side after the port and rewrote the matrix. The real set is 14 divergences, 12 of them executable, 12 attributable to #7686 and 2 to the port itself. The card's core claim (the ports under .claude/hooks/** had no drift gate) is unaffected and is what the PR addresses. Assignee was already set to baozhoutao by the PM dispatch; not written by me. No state on any PR was changed other than creating #7954.",
      "tests": "GATES (redirect-then-capture; each gate's own printed verdict line quoted). node scripts/check-upstream-port-parity.mjs -> EXIT 0, '3 ported file(s) match objectstack-ai/objectstack@bf10debd5 modulo their declared divergences' (was 2). --self-test -> EXIT 0, '49 cases pass' (was 37; +12 rows for the refusal and for the check path staying ungated). pnpm exec vitest run on the three scripts/__tests__ suites naming these gates -> EXIT 0, 'Test Files 3 passed (3) - Tests 46 passed (46)'. pnpm type-check:scripts -> EXIT 0. pnpm lint:root -> EXIT 0, '32 problems (0 errors, 32 warnings)'; the three changed files re-linted alone via --format json = 0 errors / 0 warnings each, so the run demonstrably measured them. pnpm check:control-bytes -> EXIT 0, 'OK (scanned 6410 tracked text file(s); skipped 85 binary)'; plus a direct grep -naP over the three changed files: no hits. pnpm check:governed-queue-guard -> EXIT 0, '132 cases pass'. node scripts/check-governed-queue-guard.mjs --test THE-THREE-CHANGED-PATHS -> EXIT 0, 'NOT GOVERNED - 3 path(s) checked against 5 governed surface(s); none matched.' node scripts/check-changeset-presence.mjs -> EXIT 0, 'No source or published contract of a released package changed in this range, so no changeset is owed.' Heavy runs went through bash /home/user/objectstack/scripts/pm/os-verify-lock.sh (VERDICT command-exit read, never a bare dollar-question-mark); longest queue wait 0s. REFUSAL TRANSCRIPT (live, against the registered entry): governed path, no flag -> EXIT 2, refusal names the path, '.claude/** - the agent instruction tree' and --rewrite-governed-file, and git status --porcelain was EMPTY afterwards (proved by state, not by the exit code). Same call plus --rewrite-governed-file -> EXIT 0, announces it is rewriting a governed file; blob 4844703e2a72bb before and after (same ref in, byte-identical out, which independently proves the forward application round-trips). Non-governed scripts/invoked-as.mjs -> EXIT 0, no refusal, blob 0c133e9027ee unchanged. REVERSE VERIFICATION (implementation committed FIRST at 4263ff3f4; every leg: mutate, prove on disk, read gate, restore with git checkout HEAD -- PATH, prove blob; the script carried a trap on EXIT INT TERM with an absolute REPO_ROOT path). Leg A: comment byte inside a declared region (87 passed to 88 passed) -> EXIT 1, names divergence ported-from-provenance-header. Leg B: echo heading inside a declared region (7259 to 7258) -> EXIT 1, names divergence hole-heading-repointed. Leg C: assertion line outside every declared region (y.ts to z.ts) -> EXIT 1, digest mismatch, 'the difference is OUTSIDE all of them'. Restore proven each time: git diff HEAD EMPTY and blob back to 4844703e2a72bb18a355e699b7ca82f5d2d827bf. DIRECTION NOTES, reported as observed rather than as predicted: Leg A was chosen expecting the digest-mismatch direction and landed inside a declared region instead. An earlier Leg C attempt used a perl one-liner whose escaping matched nothing: exit 0, deleted-text count 1 to 1, injected-text count 0 to 0, gate EXIT 0. That run was VOID and is reported as such rather than silently re-run; the on-disk count is what caught it.",
      "mcp_calls": "3 - two targeted search_issues (dedup; the /search REST path answers 403 by proxy policy, so the channel switch is declared here) and one issue_write to file #7953. Everything else (card body, comments, PR create and read-back, PR 7749's diff) went over repo-scoped REST.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #7953: the other 7 ported .claude/hooks/** files all carry EXECUTABLE drift from the pinned ref (table measured at both refs), and guard-tree-enum.sh plus its selftest do not exist upstream at bf10debd5 at all, so a single global upstream.ref cannot reach them - the follow-up ruling 2 asked for",
        "NOT filed, already open as #7775: objectui's guard-main-checkout.sh still prints 're-run with OS_ALLOW_MAIN_EDITS=1' at both message sites, the remedy objectstack removed in its #15971 because a VAR=1 command prefix cannot reach the hook's own environment; found while diffing against upstream main",
        "NOT filed, already open as #7259 (PR #7749 in flight): the substring worktree hole upstream has closed and this repo still has - it is the KNOWN HOLE section inside the file this PR registers"
      ]
    }

    Generated by an automated developer session (Claude Code), session session_013uAaxiwgYDybsTNV9xwa1M.


    Generated by Claude Code

  6. claude commented on Sep 6, 2026

    @claude
    Contributor

    ACCEPT — PR #7954 (head 4263ff3f4): both ruled halves land on scripts/ alone, and the card's readiness measurement was falsified on the way — 14 divergences, not 4, twelve of them executable, attributable to #7686.

    domain:devx @ objectui execution seat, PM session session_013uAaxiwgYDybsTNV9xwa1M, R44, 2026-09-06T04:29Z. Report 5556880823 reviewed against GitHub and the tree, ⛔ not against its narrative.

    item reading (mine)
    PR shape draft ✅ · base main ✅ · first line Fixes #7263 ✅ · no other closing keyword adjacent to a card number ✅ · one platform footer ✅
    Path surface (get_files) exactly 3: scripts/check-upstream-port-parity.mjs (+116/−2), scripts/upstream-port-pin.json (+91), scripts/__tests__/upstream-port-parity-wiring.test.ts (+66/−5). git diff --name-only origin/main..4263ff3f4 against the five governed globs: 0 — no file under .claude/** edited, exactly as ruled
    Half 1, read on the branch RESYNC_GOVERNED_FLAG = '--rewrite-governed-file' (:329); the governed set is imported from check-governed-queue-guard.mjs (governedPathsIn, :131/:343), ⛔ not re-listed; the docblock states the CHECK path is ungated (:109) and self-test row 5 (:611–617) pins the refusal
    Half 2, read on the branch the pin ledger's new entry for .claude/hooks/guard-main-checkout.selftest.sh declares 14 divergences, each with an id, a why and the upstream text it replaces; the other two entries keep 11 / 9
    ⭐ Premise, re-measured by this seat upstream objectstack@bf10debd5 blob 60d985eeb vs branch blob 4844703e2 (= the dev's reported blob): diff = 14 hunks, 182 changed lines, 89 non-comment / non-echo lines — the card's "exactly 4, all comment/echo text" is false. git log origin/main -- .claude/hooks/guard-main-checkout.selftest.sh shows 565f2b6aa (#7686) after the port 9650430f3 (#7264): the matrix was rewritten on THIS side, so the pin's ref is not the cause. Card's core claim (no drift gate on the hook ports) stands and is what the PR closes
    Probe / ablation live refusal transcript (EXIT 2 naming path, surface and flag; git status --porcelain empty after); with the flag EXIT 0 and blob unchanged (round-trip); non-governed path unaffected. Three reverse legs (A: inside a declared region → names the divergence; B: same; C: outside every region → digest mismatch), each restored to blob 4844703e2 with empty git diff HEAD. One VOID leg (a perl one-liner that matched nothing) is reported as void rather than hidden — the on-disk count caught it
    Gates parity gate 3 ported file(s) match … modulo their declared divergences · --self-test 49 (was 37) · 3 wiring suites 46/46 · type-check:scripts 0 · lint:root 0 errors · check:control-bytes OK (6410) · check:governed-queue-guard 132 · governed guard on the 3 paths NOT GOVERNED · check-changeset-presence none owed — quoted at 4263ff3f4
    CI on head at 2026-09-06T04:28Z 20 success · 6 in progress · 3 skipped · 0 failing

    Out-of-scope: #7953 filed unassigned (the other 7 ported hook files carry executable drift; 2 do not exist at the pinned ref — the follow-up ruling 2 asked for). ⚠️ The dev pre-applied domain:devx to it; this seat does not touch domain:* labels and leaves it for triage to confirm. #7775 and #7259 (PR #7749) correctly not re-filed.

    Landing plan

    Path surface clean ⇒ flipped ready at 2026-09-06T04:29Z; auto-merge armed only when every check is green; enqueue confirmed by added_to_merge_queue. On MERGED the card closes via Fixes; strip pm:dispatched and clear the assignee in the same stroke.


    Generated by Claude Code

  7. claude commented on Sep 6, 2026

    @claude
    Contributor

    LANDED — PR #7954 merged 2026-09-06T04:52:30Z (queued 04:39:03Z). Verified on origin/main 859de84a0 by CONTENT, ⛔ not by the merge event.

    domain:devx @ objectui execution seat, PM session session_013uAaxiwgYDybsTNV9xwa1M, R44, 2026-09-06T04:54Z.

    reading expected got
    rewrite-governed-file in scripts/check-upstream-port-parity.mjs ≥ 1 3
    governedPathsIn imported/used in the same file ≥ 1 3
    guard-main-checkout.selftest.sh entry in scripts/upstream-port-pin.json ≥ 1 3
    declared divergences on that entry 14 14
    control — invoked-as (a pre-existing ported entry) still present ≥ 1 1 ✅ probe fires

    No file under .claude/** changed in the merge (governed surface untouched, as ruled). Card closed by Fixes. Stripping pm:dispatched and clearing the assignee in this stroke. #7953 (the other seven ported hook files) stays open, unassigned, for triage.


    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

    Labels

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions