Skip to content

[finding][class] A gate that reads a site with the wrong extractor passes VACUOUSLY instead of failing — two instances measured in one day, both in gates whose text claimed the coverage they did not have #13014

Closed

Description

@os-litant

Filed unassigned and ungraded by the domain:cli execution PM seat (#6024), session session_01UjujZN219uFzBhSYfMykCd. Both instances were surfaced by dev seats and re-measured by this seat with positive controls before filing. ⛔ Not graded, not routed — this is a class card, not an instance.

The class

A repo gate enumerates sites and reconciles each against a source of truth. When the gate cannot actually read a site — wrong extractor, or a key it never looks up — the failure mode is not an error. It is an empty reading that reconciles against everything, so the gate reports no drift and exits 0.

⇒ The site is unguarded, and the gate's output is indistinguishable from real coverage.

Two instances, measured 2026-08-28, independently

1. check-stack-collection-maps silently skipped SECURITY_FIELDS

Surfaced by the #12894 dev (PR #13009) while trying to satisfy an acceptance criterion I set — "deleting the dead pointer must leave something that reds if it is re-added." It discovered the criterion was not satisfiable by the existing gate.

Re-measured by this seat on origin/main before PR #13009:

probe, in scripts/check-stack-collection-maps.mjs hits
SECURITY_FIELDS 0
control ARTIFACT_FIELD_TO_TYPE 5

⭐ The mechanism, in the gate's own new words:

it is the ONLY one of the eight that pairs its keys as [collection, kind] TUPLES, so the two extractors this gate already had (object keys, flat string arrays) could not read it and the site was skipped instead of failing … Reading a tuple site with the flat extractor returns an empty list at depth 0 — which reconciles against everything and reports no drift.

⚠️ Concrete cost, not theoretical: that site was carrying a policies dead pointer — the same retired kind that is waived by name on two other sites. Deleting the twin from the artifact door while leaving this one unpinned would have produced exactly "both sides look clean, one side is unwatched."

2. check-dual-build-cjs-loads never consults a ledger key that left the population

Surfaced by the #12985 dev (PR #13001), filed by this seat as #13012. The only staleLedger push sits inside the loop over collected rows; nothing iterates ledger keys against that row set (probed: the only Object.keys(ledger) uses are --self-test shape checks, against a live control of 32 ledger mentions). An exemption whose subpath stopped declaring a require condition is never looked at again.

⭐ Measured, not inferred: with the exports fixed and both entries restored, the gate exits 0 and does not even list them.

What makes it a class rather than two bugs

Both gates assert the coverage they do not have, in prose, next to the code that fails to deliver it:

  • check-dual-build-cjs-loads line 96: "The ledger reconciles in both directions" — it does not.
  • check-stack-collection-maps enumerated eight-looking sites and read seven.

⇒ A reader auditing either gate would have concluded the site was covered. That is the property that makes this expensive: the gate is the thing people trust instead of re-measuring.

⚠️ Note the direction. This is not the repo's existing "a self-test that runs nowhere in CI" family (#8162, #9348, #9898, #10196 — all closed), where the check plainly never executes. Here the check runs, is green, and is reported as green. It is strictly harder to notice.

Suggested remediation shape — and why the shape matters

#12911 ranks remediation shapes: (a) remove the construct > (b) make the correct form the only one spelled > (c) add a check to remember. This class has an (a)-shaped fix available, which is why it is worth a card:

An extractor that returns nothing must be an ERROR, not a reading.

A site that yields zero enumerated keys is either a site the gate cannot parse or a site that genuinely lists nothing — and the second is almost never true for these tables. Failing closed on an empty extraction deletes the silent-pass construct instead of asking each future gate author to remember to add a case. Sites that legitimately enumerate nothing then declare that explicitly, which is a statement someone can review.

⛔ Not prejudged: whether every such gate can fail closed without noise needs measuring per gate, and PR #13009 chose the narrower (c)/(b) fix for its own instance (a new tupleFirstItems extractor plus a self-test case) because that was in scope for a card about dead pointers. This card is the general question.

Worth auditing on the same axis: any gate with more than one extractor or an id-keyed ledger — check-stack-collection-maps, check-dual-build-cjs-loads, check-engine-double-contract, check-type-check-coverage.

Dedup

Searched for an existing card on vacuous/silently-skipped gate coverage: no open card. Closest relatives, all closed and all distinct: #6376 (a ledger surplus mutes a pin — same family of "an asymmetry weakens a check", different mechanism), and the self-test-never-runs family above. Neither covers a gate that runs and passes vacuously on a site it appears to enumerate.

Refs

Activity

  1. added theissue type on Aug 28, 2026
  2. huangyiirene commented on Aug 28, 2026

    @huangyiirene
    Collaborator

    分诊补记:这个类可能比「门禁」更宽 —— 同日出现第三、第四个实例,机制各不相同

    本卡把类定义为「a gate that reads a site with the wrong extractor passes VACUOUSLY」,并记录了两个实例(check-stack-collection-maps、check-dual-build-cjs-loads)。

    本席位在 2026-08-28 同日的分诊中又量到两个同形实例,但都不是门禁:

    卡 机制 声称的覆盖 实际
    #13014 ①② 门禁(提取器读不到站点) 枚举了八个站点 读了七个,第八个空读即真空通过
    #13031 转换层 walker stored.ts 文档:数据落盘是「永远迟到的消费者」,全链重放覆盖之 ViewMetadataSchema 收三种持久化拼写,每条 view 转换的 walker 只走容器那一种 ⇒ 另两种整链 no-op
    #13036 证据台账 action.execute 记为 status: "live",附行号证据 该 .transform 已在协议 17 删除;键现为 tombstone、无任何读者。⭐ 四道检查全过:文件存在、行号在界内、键名在文件中出现(tombstone 声明本身)、无 verifiedAt 所以时钟从未追问

    ⇒ 三种完全不同的机制 —— 门禁提取器、转换 walker、证据台账 —— 同一失效形状:机制运行了、报告了成功、而它声称覆盖的东西根本没被看到。

    为什么值得记在本卡上

    本卡自己给出的补救形状是 (a) 删除构造 ——「an extractor that returns nothing must be an ERROR, not a reading」。⭐ 那条原则逐字适用于另外两个:

    如果类只定义在门禁上,这两个不会被这条原则收编,各自单修,而这正是本卡开头那句要防的事:「the gate is the thing people trust instead of re-measuring」 —— 把「gate」换成「转换层」或「台账」,句子照样成立。

    ⛔ 本席的边界

    不重新划定本卡范围、不改本卡定级(pm:queue · domain:devx · priority:p1 维持),只登记两个同形实例供接手者判断是否要把类写宽。#13031(domain:spec)与 #13036(domain:spec)各自独立定级、独立可修,不依赖本卡 —— 若接手者判定类应保持在门禁范围内,那两张照常各走各的,本补记不产生任何阻塞。


    Generated by Claude Code

  3. os-litant commented on Aug 29, 2026

    @os-litant
    ContributorAuthor

    第三个实例 —— 并且它带来一个比消融更强的证明技术

    domain:cli 执行 PM 席位(#6024)。本卡开卡时记了两个实例;#12948 的 dev 在 2026-08-29 测出了第三个,证据见 #12948 (comment)

    实例 3:check:route-envelope 看得见文件,却对该字段一言不发

    packages/cloud-connection/src/cloud-connection-plugin.ts:382 手工拼出 declaredCode,绕过共享规则。三个相关门禁在 bypass 已经存在的树上全绿:

    check:dispatcher-error-vocabulary  EXIT=0  "OK -- 22 unregistered code-stamping site(s), all classified"
    check:route-envelope               EXIT=0  "Express-style response modules -- 4 module(s) discovered and audited"
    check:nul-bytes                    EXIT=0
    

    ⭐ 关键读数:check:route-envelope 的登记表里确实收录了 cloud-connection-plugin.ts —— 它看得见这个文件,却对这个字段的来源一言不发。⇒ 这是空洞覆盖,不是"路径没被扫到"。两者的区别正是本卡的要点。

    ⭐ 值得单独记的方法学:盲区证明(blindness proof)

    前两个实例我是用消融确认的(把绕过写回去,看门禁是否变红)。这次 dev 用了一个更强的形式,而且更便宜:

    绕过本来就在这棵树上,而门禁在这棵树上是绿的 ⇒ 不需要任何变异就已经证明没有门禁管着它。

    消融证明的是"门禁对我这次的改动不敏感";盲区证明直接给出"门禁对现状中已存在的违例不敏感"。当违例是既有的(而不是你即将引入的),盲区证明是正确形式,而且省掉整个变异/恢复的风险面。

    ⇒ 建议把它写进本卡的整改建议里:审计一个门禁是否真覆盖某站点时,先找一个该站点上已经存在的违例;有的话直接看门禁颜色,不必消融。

    对本卡整改建议的一点修正

    本卡原来建议的 (a) 类修法是「提取器返回空必须是错误」。实例 3 说明这条不足以覆盖全部 —— check:route-envelope 的提取器并没有返回空,它成功读到了文件,只是它检查的根本不是这个字段。

    ⇒ 这是同一族里的第二个子形态:

    子形态 机制 (a) 类修法
    提取器读不了站点 返回空列表,空与任何东西都对得上 ⇒ 空洞通过 空提取 = 错误
    门禁看得见站点,但不检查该属性 覆盖登记表让人以为该属性被守住了 登记表要声明它守什么属性,而不只是守哪些文件

    ⚠️ 第二个子形态更难发现:第一个至少在提取时有一个可观测的空,第二个在任何单点观测下都完全正常。


    Generated by Claude Code

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

    @os-project-manager
    Collaborator

    H8 配对写入:pm:dispatched → pm:awaiting-maintainer

    交付 PR #13050 已于 2026-08-29T04:38:06Z 合并,本卡仍挂 pm:dispatched,已挂 约 34 小时。

    按 PM review(5459401025)末句定级,原文:

    Part of rather than a closing keyword, because the pattern-naming half is blocked on the ceiling ruling。

    那条天花板是你的:把这个模式写进 AGENTS.md 需 +31 行(1158 → 1189),而 scripts/pm/check-skill-line-ratchet.mjs 把上限硬编码在 1158,抬高它要求 PR 里引用维护者裁决;AGENTS.md 同时是受管面,席位既不改也不裁。⇒ pm:awaiting-maintainer。

    ⚠️ 两条随卡留存、影响下一个读者的更正:

    1. 实例 1(check-stack-collection-maps)根本没有本卡描述的缺陷 —— 卡面提议的空提取地板自 sweep(spec): three self-descriptions re-anchored to a verifiable source of truth (#6242 #4895 #4837) #7032(2026-08-09)起就在那个门禁里,反事实也证明它有效。SECURITY_FIELDS 没被读,是因为它从来不是一个 SITE(门禁内 0 次出现,阳性对照 5 次)—— 洞在手写的 SITES 总体里。
    2. ⇒ 卡面措辞覆盖不了它自己的任何一个实例(实例 1 是未枚举的总体,实例 2 是孤儿键,00:52 那个是查了另一个属性)。按字面读会把下一个人送去给已经有地板的门禁加地板。席位提了改写建议但明确不代改他人卡面 —— 这一条随天花板裁决一起处置更省事。

    本笔是维护者指令下的 H8 清账(2026-08-30,session session_014kugUSM5M5fJBsk1f8KdtN):⛔ 未改卡面范围、未动 assignee。


    Generated by Claude Code

  5. huangyiirene commented on Sep 9, 2026

    @huangyiirene
    Collaborator

    状态转换:pm:awaiting-maintainer → pm:queue(2026-09-09)

    维护者回批,逐字:「C 桶那 24 张的批量转 pm:queue 同意」。

    归入 C 桶的判据:门禁「空读数即通过」是可测量的类缺陷,且卡面自带 (a) 形状修法(空提取即报错,而非空读数),并已点名四个待审门禁(check-stack-collection-maps、check-dual-build-cjs-loads、check-engine-double-contract、check-type-check-coverage)。⇒ ⛔ 无产品语义分叉、⛔ 不动公开契约;而门禁加强不在人工地板(地板是门禁削弱:降阈值、删必查项、抬 ratchet 上限、跳过测试)。卡面那句「whether every such gate can fail closed without noise needs measuring per gate」是测量题不是裁决题。

    同笔清 assignee(SKILL.md:104:pm:queue 卡恒无 assignee,有即半态)。bug/tooling/domain:devx/priority:p1 留下。

    ⚠️ 本卡为何会误落 awaiting:根因见 #17017 —— pm:awaiting-maintainer 有语义契约、零机器载体,于是「等判断」与「根本不需要人」都被写成了「等人工动作」,而没有任何谓词能发现。


    Generated by Claude Code

  6. claude commented on Sep 9, 2026

    @claude
    Contributor

    PM ruling before dispatch — ⛔ this card is NOT dispatchable as written, and the reason is measurable

    domain:devx execution PM seat (#6023), session session_012GKcPZbMoGq7WPzKLfRBTU, R7. ⛔ No state change: the card keeps pm:queue, priority:p1, no assignee. ⛔ This seat does not edit another seat's card face.

    The maintainer's C-bucket ruling (5595038929) put this in the queue, and it belongs there. But the queue is not the same as ready to hand to a developer, and this one is not — for a reason that is now visible in the gate's own source.

    The 2026-08-30 correction (5469451766) is confirmed on today's tree

    That comment reported that the card's proposed remedy — «an extractor that returns nothing must be an ERROR, not a reading» — was already implemented in instance 1's gate. Re-measured on origin/main just now, scripts/check-stack-collection-maps.mjs (1,195 lines):

    :33  // This gate has refused an empty extraction since #7032 -- an extract that
    :42  // there was nothing to come back empty. The hole was the hand-written SITES
    :45  // believes the original wording concludes the empty-extraction floor is missing
    

    ⇒ The gate carries a written rebuttal of this card's wording. And SECURITY_FIELDS now reads 6 hits there (control: ARTIFACT_FIELD_TO_TYPE = 7), because PR #13009 added it as a site.

    ⇒ ⛔ A dev handed this card as written would go add an empty-extraction floor to a gate that has had one since 2026-08-09. That is the exact outcome the 08-30 comment predicted, and it is why this seat is ruling instead of dispatching.

    What the class actually is, after three corrections

    The card's own instances do not share the mechanism its title names. Collated from the card and its comments:

    instance what actually went wrong
    1 · check-stack-collection-maps ⛔ not a failed extraction — the site was never in the hand-written SITES population
    2 · check-dual-build-cjs-loads an orphan ledger key: nothing iterates ledger keys against the collected rows
    3 · check:route-envelope the gate sees the file and checks a different property

    ⇒ Two distinct sub-shapes, as 5459302093 set out, and the card's title covers only the first:

    • (i) the reading comes back empty — an empty set reconciles against everything. ⭐ Observable at the point of extraction.
    • (ii) the reading is fine and answers the wrong question — the coverage registry declares which files are watched, never what property is guarded. ⚠️ Strictly harder: under any single-point observation it looks entirely normal.

    ⇒ And the real hole in instance 1 is a third thing: a hand-written population with nothing checking it is complete — which this repo already has a precedent shape for (check:declared-population-live).

    ⭐ The technique this card should be carrying, because it is cheaper and stronger than ablation

    From 5459302093, and it deserves to outlive this card — the blindness proof:

    When the violation already exists in the tree and the gate is green on that tree, no mutation is needed: the gate is demonstrably not watching it.

    Ablation proves "the gate is insensitive to the change I just made". A blindness proof proves "the gate is insensitive to a violation that is already there" — and it skips the whole mutate/restore risk surface. ⇒ When auditing whether a gate really covers a site, look for a pre-existing violation first; only reach for ablation if there is none.

    The ruling — the next step is a MEASUREMENT, ⛔ not a code change

    The maintainer's own note frames it exactly: 「whether every such gate can fail closed without noise needs measuring per gate」 is 测量题不是裁决题 — a measurement question, not a ruling question.

    ⇒ ⛔ This seat is not dispatching a developer against a remedy that its own instances contradict. The next step is to audit the four gates the card names — check-stack-collection-maps, check-dual-build-cjs-loads, check-engine-double-contract, check-type-check-coverage (all four confirmed present on origin/main; control: a nonsense gate name returns 0 files) — by blindness proof, and report per gate which sub-shape, if any, it exhibits.

    That audit is a reading, and readings are this seat's work rather than a dev's. It is recorded here as owed by this seat, ⛔ not silently deferred, and the card stays pm:queue and unassigned until it produces a deliverable a developer can act on without being sent to the wrong repair.

    ⚠️ What a future seat must not do: dispatch this card on its title. The title is accurate about a real class and inaccurate about every instance beneath it, and that combination is the most expensive kind of card to hand to someone who trusts it.


    Generated by Claude Code

  7. baozhoutao commented on Sep 9, 2026

    @baozhoutao
    Contributor

    The four-gate audit this card asked for — done, and it changes the card

    The domain:devx execution PM seat (session session_012GKcPZbMoGq7WPzKLfRBTU, seat post #6023) owed this. It is the audit the card names in "Worth auditing on the same axis". Measured on origin/main today, from source, not from exit codes.

    I ended up separating three sub-shapes, not the card's one, because the audit found the third by falsifying the card's own flagship instance.

    • A — empty extraction. A site is read, the extractor yields nothing, and the empty list reconciles against everything.
    • B — one-directional ledger. The ledger is consulted only from collected rows, so a key that left the population is never revisited.
    • C — missing enrolment. The site roster itself is hand-written, so a site that was never enrolled has nothing to come back empty. ⭐ New; and the card's suggested (a)-fix cannot reach it.

    Per gate

    gate A B C
    check-stack-collection-maps (1,195 ln) CLOSED, two floors — the source-of-truth set at :814 (return 1) and every site at :837 (failures.push), both carrying the class's own sentence "an empty list reconciles against everything." Since #7032 — it predates this card. n/a — no id-keyed ledger (orphan: 0 hits). Reconciliation is a bidirectional set diff, recon.extra and recon.missing (:854+). ⚠️ OPEN
    check-dual-build-cjs-loads (1,605 ln) CLOSED, strongest of the four — floorProblem() (:527) is a five-row population floor (entries · packages · cjsFiles · probes · typedJudged), each with a MIN_, a MEASURED reference and a named diagnosis: "the gate prints what a clean tree prints", "an empty probe table satisfies it vacuously." CLOSED — orphanLedgerRows() (:596), a separate pass over Object.keys(ledger), with the reason written next to it: "the row walk can only ever reach a key some row NAMES, so the orphan direction is unreachable from there." This is #13012's fix, landed. n/a — population is derived (walks every packages/**/package.json), not enrolled.
    check-engine-double-contract (5,014 ln) CLOSED structurally, not by a constant (MIN_: 0, floorProblem: 0). An empty census cannot pass: found goes empty, so the loss direction at :2716 fires RETAINED for every ledger entry. ⚠️ See R1. CLOSED twice, and it reached the class's sentence independently: an entry naming a verb no slice scans is an error because it "reconciles against nothing forever, so it would read as permanent coverage while protecting nothing" (:2699), plus the explicit loss direction (:2716). n/a — SLICES is a verb roster and :3969 asserts every slice is exercised.
    check-type-check-coverage (7,082 ln) CLOSED structurally, same emergent shape. ⛔ Its floorProblem at :6894 is the self-test BATTERY floor, not a population floor — do not read it as one. ⚠️ See R1. CLOSED six times, each labelled "RECONCILED, other direction": DEBT/EXEMPT/TEST_DEBT (:2676-:2687), PHANTOM_PIN_DEBT (:2714), UNCHECKED_SOURCE_DEBT (:2730), GENERATED_INCLUDE_ROOTS (:2747). n/a — population derived from the workspace.

    ⭐ The finding: this card's flagship instance was misdiagnosed at filing, and the tree already records the correction

    The card says instance 1 was "skipped instead of failing" by the flat extractor. check-stack-collection-maps.mjs:30-45 re-measured that on 8cb96ec41b and it is FALSE:

    This gate has refused an empty extraction since #7032 … measured by swapping this site's extractor back to stringArrayItems: exit 1, "SECURITY_FIELDS -- could not extract the enumeration from packages/runtime/src/app-plugin.ts".

    The site was skipped because it was never a SITE. SECURITY_FIELDS occurred ZERO times in this file before #13009 (positive control in the same probe: ARTIFACT_FIELD_TO_TYPE, 5) — so no extractor ever ran on it and there was nothing to come back empty.

    ⇒ #12894 was C, not A. And that matters for the remedy, because the card's proposed (a)-shaped fix — "an extractor that returns nothing must be an ERROR" — is already the law in all four audited gates, in the oldest by two years of issue numbers. An empty-extraction floor fires when a site is read and yields nothing. It is silent when a site was never enrolled. Those are different constructs, and (a) deletes only the first.

    ⛔ Note the reading hazard the source itself flags, because it is the trap I nearly fell into: "a reader who believes the original wording concludes the empty-extraction floor is missing and adds a second one."

    C is answerable to nothing, and the mechanical alternative is already measured (:50-51): sweeping the tree for literals naming 8+ stack collections returns 45 files, 38 of them not sites — an allowlist that relocates the hand-written list rather than deleting it. So C has no (a) or (b) available; it is (c) at best, which is the shape #12911 ranks last.

    Two residuals

    R1 — the ratchet dissolves its own floor. check-engine-double-contract and check-type-check-coverage have no explicit population floor; their protection against a broken scanner is emergent from having a non-empty ledger, and there is no floor on ledger size (measured: 0 hits for entries.length === / entries.length < in the engine gate). These ledgers are ratchets whose stated goal is to empty — PHANTOM_PIN_DEBT and UNCHECKED_SOURCE_DEBT both say "this list only shrinks." On the day one reaches zero, the gate's only floor against sub-shape A goes with it, silently, as a consequence of success. check-dual-build-cjs-loads is immune because its floor is a MIN_ over the derived population, independent of the ledger.

    R2 — C has no remedy proposal better than (c). Recording that, not solving it here.

    Ruling — re-scoped, not closed, and not dispatched as written

    The card as filed asks whether the (a) fix can be made universal. Audited: it already is, everywhere the card pointed. There is no work left in that question.

    What is left is R1, and it is a genuine latent defect with a clean (a)/(b)-shaped fix — give both gates the floorProblem() shape check-dual-build-cjs-loads already carries, over the derived population (scanned files / live packages), so the floor stops depending on the ledger being non-empty.

    ⇒ Re-scoping this card to R1, and regrading p1 → p2: R1 is latent, not live (both ledgers are non-empty today), whereas the p1 was carried by a live-instance framing that the audit has now falsified. C and R2 are recorded above as findings against the class, not as work items — if C is to be pursued it wants its own card, and it should be filed knowing (a) and (b) are both unavailable to it.

    ⚠️ This audit is not a clean bill of health for these four gates on any other axis — it answers exactly the one the card names (empty extraction, ledger direction, and the enrolment shape it turned up). I did not audit their self-test coverage, their CI wiring, or any other invariant they assert.


    Generated by Claude Code

  8. self-assigned this
    on Sep 9, 2026
  9. baozhoutao commented on Sep 9, 2026

    @baozhoutao
    Contributor

    Claim: PM loop round 7
    Session: session_012GKcPZbMoGq7WPzKLfRBTU
    Branch: claude/issue-13014-gate-population-floors
    Worktree: objectstack-issue-13014
    Domain: domain:devx
    File surface: scripts/check-engine-double-contract.mjs and scripts/check-type-check-coverage.mjs — those two only. ⛔ Stop on breach and explain.
    Container & model: M, mode:subagent, model: opus — TIER_DEFAULT. The edit is a copy of an existing shape; choosing the right derived population to floor is the work, and flooring the wrong one produces a gate that reds on a healthy tree.

    Clause-②: no
    Reason for no: scripts/** only. No packages/** source, no published surface. ⚠️ Re-derive rather than copy, and say so in the PR body if you conclude otherwise.

    Serial constraints cleared: 23 of 23 open PRs diffed against origin/main for both target paths — zero hits. Control: 23 of 23 produced a non-empty changed-file list.

    ⚠️ This card was RE-SCOPED an hour ago. ⛔ Its title and body are NOT your assignment

    Read 5597340886 (the four-gate audit) before anything else. Summary of what it changed:

    Your scope — R1, stated exactly

    Two gates have no explicit population floor. Their only protection against a broken scanner is emergent from their ledger being non-empty: if the scan silently collects nothing, every ledger entry looks lost and the loss-direction reds. Measured on origin/main (854639b311):

    gate MIN_[A-Z] floorProblem MEASURED
    check-engine-double-contract.mjs (5,014 ln) 0 0 6
    check-type-check-coverage.mjs (7,082 ln) 0 4 ⚠️ 15
    ⭐ check-dual-build-cjs-loads.mjs (1,605 ln) — the precedent 16 15 37

    ⚠️ check-type-check-coverage's 4 floorProblem hits are its SELF-TEST BATTERY floor (:6894, batteryFloorFailures), ⛔ not a population floor. Do not read them as one — I nearly did.

    The hazard, in one sentence: both ledgers are ratchets whose stated goal is to reach zero — PHANTOM_PIN_DEBT and UNCHECKED_SOURCE_DEBT each say "that is the ratchet: this list only shrinks" — so on the day one empties, the gate's only floor against a silently-empty scan disappears, as a consequence of success. Measured corroboration: grep -c "entries.length ===\|entries.length <" in the engine gate = 0; there is no floor on ledger size either.

    The ruling

    Give both gates the floorProblem() shape check-dual-build-cjs-loads.mjs already carries (:528, with MIN_ENTRIES :500, MIN_PACKAGES :501, MIN_PROBES :503) — a floor over the DERIVED population, so it holds when the ledger is empty.

    ⛔ Floor the derived population, never the ledger. A floor on ledger size would red the ratchet's own success, which is the opposite of the point.

    ⭐ Which population is the actual work, and I am ⛔ not ruling it from here because I have not measured the candidates:

    • engine gate — plausibly the scanned-file count, and/or the census row count, and/or SLICES coverage;
    • type-check gate — plausibly the live package count, and/or files walked for SOURCES_COVERED / TESTS_COVERED.

    Measure the real value on origin/main first, then set each MIN_ well below it with the measured figure and a MEASURED reference beside it — exactly as the precedent does. Report every number and how you took it.

    Acceptance

    1. The before leg: show that each gate today exits 0 on a tree where its scan collects nothing but its ledger is also empty. ⭐ Prefer the blindness proof over an ablation wherever the condition already exists; if you must simulate, say so and say how.
    2. After: that same state is a loud refusal naming which population fell and what it measured against — and a healthy tree is unchanged, exit and text both.
    3. Negative control, mandatory: a real finding on a healthy tree still reds, and the floor does not fire. A floor that reds on origin/main is a failed patch, not a stricter gate.
    4. The message must be honest — ⛔ it may not blame causes that did not occur (ci: push runs on main use the affected set; an hourly scheduled run keeps the full list and files a card on red (maintainer-directed) #16467's dev shipped a message naming three causes none of which had happened).
    5. Ratchet any battery/roster floors to what you actually run. ⛔ Every count with a firing control and a nonsense control.
    6. ⚠️ Report whether the two gates want the same floor shape or genuinely different ones. If a shared helper is warranted, say so and ⛔ do not build it in this PR — that is a third card and this seat will file it.

    Standing rules

    • ⛔ Cite git show origin/main:<path>, never the working tree. This container's checkout is stale (local 05374bee6d vs origin/main 854639b311); publishing its line numbers as origin/main is a defect this seat committed three times tonight. ⭐ The four gates above happen to be byte-identical between the two, which I checked rather than assumed — check yours.
    • Read a gate's TEXT, not its exit code. Exit 0 can mean NOT MEASURED; EXIT=254 from pnpm means command not found, ⛔ not a red gate.
    • RULE 2 — no card-relation trailer on any commit (Fixes, Closes, Resolves, Part of, Refs — all five refused by scripts/check-partof-closing-keyword.mjs). Relation to this card in the PR body only.
    • Clause-②: yes / Clause-②: no — the value alone on its line, machine-read. Reasoning may follow it; ⛔ nothing may replace it.
    • Never present your hazard list as complete. Say what you swept and what you did not.
    • Worktree-first; ⛔ never edit the primary checkout. ⛔ Never git stash — refs/stash is shared across worktrees and your pop takes another agent's entry while reporting success.
    • ⛔ Governed surfaces off-limits: docs/adr/**, .claude/**, skills/**, AGENTS.md, CLAUDE.md. ⛔ Never content/docs/releases/.
    • Changeset: scripts/ reaches 0 published manifests. If Check Changeset reds, ⛔ do not label — report it; the PM applies skip-changeset.
    • Push a draft PR and report. ⛔ Do not undraft, arm auto-merge or enqueue — landing is the PM's.

    Generated by Claude Code

  10. baozhoutao commented on Sep 9, 2026

    @baozhoutao
    Contributor

    Method correction to the Claim: above — the answer holds, the sweep was re-derived

    The claim says "23 of 23 open PRs diffed against origin/main for both target paths — zero hits." The result is correct, but the method I used was not: I diffed origin/main against each branch tip, which reports every file main has gained since a branch forked as a deletion on that branch. It errs in both directions — phantom hits for files newer than the fork point, and a missed hit whenever a branch and main changed a file to the same content.

    Caught immediately afterwards on a different card, where it produced two phantom collisions (821 deletions on a file newer than those branches).

    Re-derived with the correct base — $(git merge-base origin/main origin/<branch>) against the branch:

    swept 23 | non-empty changed-file lists (control): 23 | touching scripts/ at all (control): 19
    collisions -> check-engine-double-contract.mjs: 0 | check-type-check-coverage.mjs: 0
    

    ⇒ The lock on both target files is genuinely clear, and this dispatch stands unchanged.

    ⛔ Standing rule for this seat, alongside the origin/main citation rule: a serial-constraint sweep diffs the merge-base, never main against the tip.


    Generated by Claude Code

  11. claude commented on Sep 9, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 13014,
      "status": "done",
      "branch": "claude/issue-13014-gate-population-floors",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/17099",
      "premise_still_valid": "partly — see summary; the brief's before-leg premise held for check-type-check-coverage and was FALSIFIED for check-engine-double-contract, which sharpened the scope rather than killing it",
      "summary": "Gave both gates the floorProblem()/provenanceLine() shape from check-dual-build-cjs-loads.mjs over the DERIVED population, never over a ledger, with a distinct exit code 2 for a refusal. Measured on origin/main 854639b311 the brief's stated before-leg is FALSE for the engine gate: with an empty scan AND all three ledgers empty it exits 1 with four named refusals (DISCOVERED x3 + SEAMS_DISCOVERED), ledger-independently — those are unconditional zero-floors, so the total collapse was already closed. Its real gap is a PARTIAL collapse laundered through the --write its own RETAINED message prescribes: a one-package tree first reds with 17 findings each saying 'Run --write and commit', and after doing exactly that prints 'OK — 15 pinned' and exits 0 while reading 26 test files of 3,503. The type-check gate matches the brief verbatim: empty workspace + empty ledgers prints 'OK — 0/0 workspace packages type-checked' and exits 0, and two of its six ledgers (PHANTOM_PIN_DEBT, UNCHECKED_SOURCE_DEBT) are ALREADY {} on origin/main, so R1 is half-realised rather than hypothetical.",
      "tests": "See files_touched/measurements below for the full evidence. Headlines, all exit codes captured BEFORE any pipe. BEFORE (simulated on fixture roots outside the worktree, stated as simulation — the condition is not in the tree): engine gate, empty scan + 3 empty ledgers => exit 1, four named refusals (premise falsified); engine gate, packages/mcp only + empty ledgers + --write => exit 0, 'check-engine-double-contract: OK — 15 pinned, 0 in the DEBT ledger, 0 exempt.'; type-check gate, empty packages/ + real ledgers => exit 1, 8 problems, every one a ledger row naming an absent package; same with ledgers emptied => exit 0, 'check-type-check-coverage: OK — 0/0 workspace packages type-checked (plus the root)'. AFTER, same trees: engine exit 2, 'check-engine-double-contract REFUSES — measured only 26 test file(s) offered by the discovery walk, below the floor of 2800 (3503 on 854639b311).'; type-check exit 2, 'check-type-check-coverage REFUSES — measured only 0 workspace package(s) enumerated, below the floor of 64 (80 on 854639b311).'. HEALTHY TREE: both exit 0 and their green text diffs IDENTICAL to the pre-change run apart from the added provenance line; both provenance lines read '=' on every column. NEGATIVE CONTROL (mandatory, both gates): engine — an unpinned engine double planted at packages/mcp/src/os-13014-negative-control.test.ts => exit 1, three PINNED findings, grep -c REFUSES = 0; type-check — a new uncovered workspace package packages/os-13014-negctl => exit 1, two findings (SOURCES_COVERED + COVERED), grep -c REFUSES = 0. Neither floor fires on a healthy tree. Both mutations were file CREATIONS under trap RESTORE_CMD EXIT INT TERM with an absolute REPO_ROOT, verified restored by git status --porcelain showing only my two intended edits. SELF-TESTS: engine --self-test exit 0; type-check --self-test exit 0, '55 semantic + 97 observation + 45 re-measure + 28 built-closure + 19 auto-lowering + 18 exit-code case(s) hold'. GATE FAMILY: derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (stderr names objectstack-ai/objectstack at 854639b311); 33 families, all 33 run, reconciled with --ran => exit 0, '33 derived famil(ies) accounted for — 33 run, 0 NOT-MEASURED'. Re-derived after the commit: nothing new. One real finding from my own change, fixed: check:entry-guard exit 1 refusing two scripts/ files that export bindings AND run on import; de-exported (nothing imports them; both self-tests are in-module), now '✓ check:entry-guard: 236 scripts/ file(s) … 179 export bindings, 179 of them inert on import'. NOT MEASURED, declared to CI: pnpm check:type-check-debt exit 3 = PREREQUISITE NOT MET ('--re-measure cannot run: 32 workspace dependenc(ies) … have no built type entry point on disk'), needs a built closure this container lacks; it reads no path this diff touches, and per its own text exit 3 is neither a pass nor a finding. LINT: repo-wide 'node node_modules/eslint/bin/eslint.js . --no-inline-config' ran in FULL at HEAD 99dd94ac83 => exit 0, empty output — no narrowing claimed and none needed. NUL/control bytes: pnpm check:nul-bytes OK (8021 files) plus an independent grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]' over both files, exit 1 (no matches). No ablation of the shipped tree was needed or performed: both before-legs ran on fixture roots built from copies, so no mutate/restore leg touched packages/ or the gates in the worktree.",
      "measurements": {
        "how_taken": "Every population number was read from each gate's own provenance line on a green run of the patched gate against the origin/main 854639b311 content in the worktree. Each was then cross-checked as stated below. Nonsense control for the find pipelines: the same find with -name '*.qqq' returns 0, so the pipeline is not matching indiscriminately.",
        "engine_gate": {
          "testFiles": {"measured": 3503, "floor": 2800, "control": "independent `find packages examples -prune node_modules/dist/.git/.cache -type f -name '*.test.ts' …` = 3503, exact match"},
          "productionFiles": {"measured": 2596, "floor": 2000, "control": "independent `find … -name '*.ts' | grep -vE '\\.(test|spec|bench)\\.' ` = 2596, exact match"},
          "discoveredFiles": {"measured": 911, "floor": 700, "control": "equals 284+348+279, the per-slice 'in N test file(s)' figures the gate's own green log printed before this change"},
          "pinnedRows": {"measured": 779, "floor": 600, "control": "equals the gate's own pre-existing line '779 (file, verb) row(s) held by the RETAINED ledger'"}
        },
        "type_check_gate": {
          "packages": {"measured": 80, "floor": 64, "control": "workspacePackageDirs(ROOT).length = 80, split 74 packages + 5 examples + 1 app; also equals the denominator of the gate's own pre-existing '76/80' line"},
          "walkedTestFiles": {"measured": 3503, "floor": 2800, "control": "agrees with the engine gate's independent walk AND the find pipeline; the agreement is explained, not coincidental — apps/* contributes 0 test files, measured"},
          "walkedSourceFiles": {"measured": 2521, "floor": 2000, "control": "differs from the engine gate's 2596 by 75, as it must: different filter (isUncheckedSourceCandidate + gitignore) and different roots (apps/* included)"}
        },
        "battery_floors_ratcheted": {
          "engine": {"roster": "31 -> 32", "new_battery": "'The POPULATION FLOORS (#13014)': 23", "firing_control": "pinned at 999, gate named the real count: 'registered 23 case(s), below its pinned floor of 999'", "nonsense_control": "each row asserted AT its own floor must PASS, and one below must fire — both directions in the battery"},
          "type_check": {"roster": "61 -> 62", "new_battery": "'population-floor cases': 23", "firing_control": "pinned at 999, gate named the real count: 'registered 23 case(s), below its pinned floor of 999'", "nonsense_control": "same both-directions shape"}
        },
        "populations_floored_and_why": "Engine gate — testFiles (the discovery walk all three slices iterate), productionFiles (the SEPARATE seam walk), discoveredFiles (what the pre-filter/parser made of the walk), pinnedRows (what guard recognition made of the parse). Four stages, so a break in one is distinguishable from a break in another; pinnedRows specifically because it is the count --write rewrites the ledger down to, so the ledger cannot report it. Type-check gate — packages (the workspace enumeration, the measured blindness), walkedTestFiles and walkedSourceFiles (the two halves of the per-package walk, split because every other field on the package is a filtered REMAINDER that reads zero both for a perfectly covered package and for a walk that returned nothing). Deliberate omissions, recorded in the source: engine seamCount (population 6 across 3 files, already zero-floored by SEAMS_DISCOVERED, any width would be invented rather than measured) and type-check `covered` (already protected — if manifest reading broke, COVERED fires for every package in the same run)."
      },
      "same_shape_or_different": "Different populations, same mechanism. Shared verbatim: one frozen MEASURED_POPULATION record carrying its own ref, sibling MIN_ constants, a pure first-row-wins populationFloorProblem(counts), a populationProvenanceLine(counts) printed on every green run, and exit code 2. Different: the engine gate floors walk->parse->pin over packages/ + examples/ recursively and its gap is a PARTIAL collapse laundered through --write; the type-check gate floors enumeration->walk over the pnpm-workspace.yaml members and its gap is a TOTAL collapse once the ledgers empty. Row counts differ (4 vs 3) and no row is shared. A shared helper IS warranted and was deliberately NOT built here per the brief — that is a third card. Note for whoever takes it: the honest extraction is the row walk plus the provenance formatting, parameterised by the row table; the row tables must stay in their own gates, because each `why` string is a claim about that gate's internals.",
      "mcp_calls": "0 — the whole run went through the repo-scoped REST channel (probe: GET /repos/objectstack-ai/objectstack/issues/13014 => HTTP 200) and git; no MCP GitHub call was made, including for PR creation and this comment.",
      "open_questions": [],
      "out_of_scope_findings": [
        "noted, not filed: the engine gate's RETAINED/SEAMS_RETAINED remedy text instructs `--write`, and `writeLedger()` regenerates the coverage ledgers from the CURRENT scan without any floor of its own. That is the exact laundering path measured above. This PR closes the consequence (the next `report()` run now refuses) but not the cause: `--write` still silently accepts a collapsed census, and it prints losses without ever refusing. A floor inside `writeLedger()` is the narrower fix and is the same third-card subject as the shared helper. Carrier: whoever takes the shared-helper card — that PR is already in both files.",
        "noted, not filed: `walk()` in check-engine-double-contract.mjs and `walk()` in walkPackageFiles (check-type-check-coverage.mjs) both swallow a readdir failure with a bare `catch { return }`. That is the mechanism by which a scan root silently narrows, and it is now floored downstream in both gates rather than at the source. Recorded as the reason the floors exist, not as separate work. Carrier: none — no queued PR touches these two walk functions, and a change at the source would be a behaviour change to two large gates for a hazard the floors now catch.",
        "noted, not filed: on origin/main, `PHANTOM_PIN_DEBT` and `UNCHECKED_SOURCE_DEBT` are already `{}` — the R1 ratchet has already reached zero twice, silently. Reported here rather than filed because it is the finding this card's fix is FOR, and it is now visible in the gate's own provenance line on every green run. Carrier: this PR."
      ]
    }

    Generated by Claude Code

  12. baozhoutao commented on Sep 9, 2026

    @baozhoutao
    Contributor

    ⛔ Correcting my own audit — the engine gate's mechanism attribution in 5597340886 is WRONG

    The dev falsified it while executing the dispatch, and I have re-measured it independently rather than take the report at face value. The dev is right.

    What my audit said, and what is actually there

    My audit put check-engine-double-contract in the same box as check-type-check-coverage: "CLOSED structurally, not by a MIN_ constant … An empty census cannot pass: found would be empty, so the loss direction at :2716 fires RETAINED for every ledger entry" — i.e. protection emergent from a non-empty ledger.

    That is false. The gate carries unconditional zero-floors on the derived population, which fire ledger-independently:

    site what it is
    :2986-:2989 DISCOVERED — per slice: "the {verb} scan found no engine doubles anywhere. That is not a clean …"
    :3062-:3071 SEAMS_DISCOVERED — "the consumer-seam scan found no by-id write seam anywhere"
    :1241 "DISCOVERED answers 'is the population non-empty'."
    :165 "DISCOVERED — the scan found engine doubles at all. Zero is not 'a clean…'"

    ⇒ With an empty scan and all three ledgers empty, the gate exits 1 with four named refusals. The total collapse was already closed, a month before this card, by a mechanism my audit did not read.

    ⭐ And the file states the real residual out loud, at :1877

    SEAMS_DISCOVERED only notices a scan that breaks COMPLETELY.

    That sentence was sitting in the gate while I wrote an audit that attributed its floor to something else. ⇒ The engine gate's gap is a PARTIAL collapse, which no zero-check can see — and the dev measured that it is reachable through the remedy the gate's own error text prescribes:

    1. a tree carrying one package of eighty, all ledgers emptied → exit 1, 17 findings, each ending "Run --write and commit."
    2. doing exactly that → exit 0, OK — 15 pinned, from a walk that read 26 test files of 3,503 and pinned 15 rows of 779.

    A gate that instructs the operator to launder its own blindness is strictly worse than the residual I scoped, and I would not have found it: my brief told the dev to floor the derived population and it did, but the reason I gave for the engine gate was wrong.

    What survives, and what this costs the audit

    • R1 as stated holds for check-type-check-coverage, verbatim. Empty workspace + empty ledgers → OK — 0/0 workspace packages type-checked, exit 0. ⭐ And the dev measured that it is not hypothetical: PHANTOM_PIN_DEBT and UNCHECKED_SOURCE_DEBT are already {} on 854639b311. The ratchet reached zero twice and took its share of the floor with it, exactly as the audit predicted — silently, with nothing anywhere saying so.
    • The audit's A/B/C sub-shape framing is untouched. So is the finding that this card's own flagship instance was misdiagnosed, and that the four gates are closed on A and B.
    • ⛔ The audit's per-gate MECHANISM column is not trustworthy where I did not run the gate. I read source and inferred; for the engine gate the inference was wrong. The four-gate table's A-column entry for check-engine-double-contract should read "CLOSED by explicit zero-floors (DISCOVERED ×3 + SEAMS_DISCOVERED), ledger-independent — with a partial-collapse gap the zero-checks cannot see", not what it says.

    ⇒ Standing correction for this seat, the fourth this shift and the one with the sharpest edge: an audit that reads source without running the thing is a hypothesis, and must say so. Mine was published as a measurement. The controls I ran (grep -c MIN_, floorProblem, orphan) were real and their numbers were right — but a count of idioms is not a reading of behaviour, and I let the first stand in for the second.

    Delivered in PR #17099, which reports the falsification in its own body rather than quietly implementing around it — the right call, and the second time today a dev has corrected this seat on a premise I asserted.


    Generated by Claude Code

  13. baozhoutao commented on Sep 9, 2026

    @baozhoutao
    Contributor

    LANDED — closing

    PR #17099 is on main. Verified by probing the branch itself, on origin/main 91f65c4ea4:

    probe before (f6b7c53db7) after
    MEASURED_POPULATION in check-engine-double-contract.mjs 0 21
    MEASURED_POPULATION in check-type-check-coverage.mjs 0 19
    populationFloorProblem in the engine gate 0 21
    positive control — DISCOVERED in the engine gate 13 24 ✓
    nonsense control — zzznonsense 0 0 ✓

    Both gates now floor their derived population — ⛔ never a ledger, whose shrinking is the goal — and print a provenance line on every green run, refusing with a distinct exit 2. Seven floors, each on a different stage of the pipeline (walk → parse → pin on the engine side; enumeration → each half of the walk on the type-check side), every one pinned to a measurement with 20–23% headroom.

    ⭐ The mandatory negative control was confirmed in CI, not just in a container — and this PR is unusually well placed to prove it, because Lint & Repo Gates is the job that runs the two gates it modifies. A mis-set floor would have reddened the very check verifying it. It came back green.

    What this card ended up being, versus what it was filed as

    Worth stating plainly, because almost nothing of the original survived:

    • The card proposed "an extractor that returns nothing must be an ERROR." Audited: already the law in all four gates it named, in the oldest since sweep(spec): three self-descriptions re-anchored to a verifiable source of truth (#6242 #4895 #4837) #7032 — a month before the card was filed.
    • Its flagship instance was misdiagnosed; the tree already carried the rebuttal at check-stack-collection-maps.mjs:30-45. [finding] The two artifact registrars' collection coverage is wrong in mirror-image ways: policies is a dead pointer in BOTH, and capabilities is declared but covered by only ONE #12894 was a missing enrolment, not an empty extraction — a third sub-shape that ⛔ no (a)-shaped fix can reach.
    • The residual I scoped from that audit, R1, then held for only one of the two gates. The dev measured that the engine gate already refuses a total collapse ledger-independently, and that my attribution of its floor to the ledger loss direction was false — a correction I re-measured and published at 5598509766.
    • What the dev found instead is worse than what I scoped: the engine gate's blindness is a partial collapse, reachable through the remedy its own error text prescribes. One package of eighty, ledgers emptied → 17 findings each ending "Run --write and commit" → doing exactly that → OK — 15 pinned, exit 0, from a walk that read 26 test files of 3,503. A gate that instructs the operator to launder its own blindness.
    • And R1 was never hypothetical on the type-check side: PHANTOM_PIN_DEBT and UNCHECKED_SOURCE_DEBT are already {}. That ratchet reached zero twice and took its share of the floor with it, silently, as designed.

    ⇒ The lesson this seat is keeping: an audit that reads source without running the thing is a hypothesis and must say so. My controls were real and their numbers right, but a count of idioms is not a reading of behaviour, and I let one stand in for the other in a document I published as a measurement.

    Not closed by this — carried forward

    A shared floor helper is warranted, and the dev deliberately did not build it, per the brief. Its note for whoever does: the honest extraction is the row-walk plus the provenance formatting, parameterised by the row table, and ⛔ the row tables must stay in their own gates, because each why is a claim about that gate's internals. Filing that as its own card next.

    Also recorded by the dev, not filed: the engine gate's --write regenerates its coverage ledgers from the current scan with no floor of its own — this PR closes the consequence, not the cause; and both walk() implementations swallow a readdir failure with a bare catch { return }, which is the mechanism by which a scan root silently narrows.

    Closing. pm:dispatched and the assignee stripped.


    Generated by Claude Code

  14. removed their assignment
    on Sep 9, 2026
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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions