Skip to content

finding(gate): check-doc-snippet-types' package-README walk stops at the package root — the same 4 nested READMEs #6026 fixed for check-doc-links are neither covered nor ledgered here #7308

Description

@yinlianghui

Filed unassigned, not claiming. Found while taking measurement (a) for #6214 (does check:doc-snippets already compile package README code blocks — it does, and the surface has this hole).

listDocuments in scripts/check-doc-snippet-types.mjs walks content/docs recursively, then adds package READMEs with a non-recursive loop over packages/:

for (const entry of readdirSync(pkgDir).sort()) {
  const readme = join(pkgDir, entry, 'README.md');
  if (existsSync(readme)) out.push(...);
}

So packages/NAME/README.md is collected and anything deeper is not. Measured against the tracked population scripts/check-readme-exports.mjs walks (git ls-files -- packages/, filtered to /README.md): 43 tracked READMEs, 39 of them at a package root, 4 nested and invisible to this gate — packages/types/src/zod/README.md among them.

Why this is a finding and not a preference

That gate's own header states the rule this breaks, and states it as the lesson of #5174:

A document is covered unless it is named in UNGATED_DOCS with a reason. The default is therefore COVERED … an entry naming a file that does not exist, or that holds no ts / tsx block at all, fails as a stale entry, so the list can only shrink.

The four nested pages are neither. They are not compiled, and they are not on the ledger, so they do not appear in the summary line's accounting at all — which is, in the header's own words about #5174, "precisely the silent skip the fragment rule exists to prevent, arriving one level up, at the document instead of the block." The same sentence applies one directory deeper, and the header's closing instruction — "Anything added to the scan surface later belongs in that list, on the same day" — presumes the surface is complete.

These pages ship: packages/types lists its whole src/ in files, so packages/types/src/zod/README.md is inside the npm tarball a reader downloads.

Precedent: this exact class, this exact count, already fixed once next door

#6026 (closed) is the same defect in the sibling gate — "check-doc-links 的包内 README 扫描只到顶层:4 个嵌套 README.md 仍未被任何门禁解析". Same four files, same cause, different gate. #6214's tier-1 gate check-readme-exports keeps them deliberately and records why in its header (packageDirOf walks up to the owning package, so a nested page teaching a @object-ui/types import is judged against @object-ui/types); either of those two is a worked model for the fix.

Whoever takes this should expect the surface change to grow UNGATED_DOCS before it shrinks it, and that is the correct direction: an entry with a measured reason is what a page that cannot pass yet is owed. Sizing the diagnostics on the four is the first step, not a code change.

Coordination

scripts/check-doc-snippet-types.mjs and its ledger are held by #5174's batch work right now, and this touches both. It should queue behind that, not race it.

Grading

For PM triage. Labelled finding: a coverage gap in a gate, with a closed precedent for the same class and no user-visible defect proven through it yet.

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:p3 / pm:on-hold / finding / tooling

    锚定 (anchoring):scripts/check-doc-snippet-types.mjs + 其 UNGATED_DOCS 台账 ⇒ domain:devx。

    在 origin/main a472b07 上复核 —— 主张成立,且我独立重算了那个人口普查

    卡片说"43 tracked READMEs, 39 at a package root, 4 nested"。我没有照抄,重跑了一次(分母是 git ls-tree -r origin/main -- packages/ 的全量,不是分页窗口):

    43   tracked README.md under packages/
     4   nested (i.e. not matching ^packages/<name>/README\.md$):
           packages/components/src/__tests__/README.md
           packages/core/src/adapters/README.md
           packages/plugin-gantt/docs/verification/README.md
           packages/types/src/zod/README.md
    

    43 / 39 / 4 三个数字全部对上。⭐ 卡片只点名了其中一个(packages/types/src/zod/README.md),这里把四个全列出来 —— 接手者不必再测一次。

    门禁自己的 header 把这个非递归表面当作规格写在散文里,所以这不是实现走样,是规格本身就画小了:

    scripts/check-doc-snippet-types.mjs:328    *     every `.mdx` and `.md` page under `content/docs`, every
    scripts/check-doc-snippet-types.mjs:329    *     `packages/<name>/README.md`, and the root `README.md`.
    

    packages/<name>/README.md —— 一层,逐字如此。而 listDocuments 在 :808。

    正控制:同一个 header :409-414 记着 #7115 修过同一形状的另一个洞(仓根 README.md 曾从两个门禁的表面之间掉下去,"教了四次未注册的 stat-card 类型,只要那个例子还在")。所以"表面被静默画小 ⇒ 有页面无人编译"在本仓是已发生过并造成实际后果的类,不是假想。

    为什么是 pm:on-hold 而不是 pm:queue

    卡片自己写了协调约束,而且是对的:

    scripts/check-doc-snippet-types.mjs and its ledger are held by #5174's batch work right now, and this touches both. It should queue behind that, not race it.

    #5174 当前是 pm:queue priority:p2 domain:devx 的多批次在跑卡。两张卡改同一个脚本 + 同一个台账,并发就是冲突。

    Restart-when:#5174 的当前批次落地后(或 #5174 关闭后)转 pm:queue。⛔ 判据是"#5174 的在飞 PR 已合并、脚本与 UNGATED_DOCS 无人持有",不是"#5174 还开着所以永远等" —— 若 #5174 长期停在无在飞 PR 的状态,本卡可以直接进队列。

    定级理由

    priority:p3:

    ⭐ 卡片最有价值的一句,请接手者当作计划而不是免责

    Whoever takes this should expect the surface change to grow UNGATED_DOCS before it shrinks it, and that is the correct direction … Sizing the diagnostics on the four is the first step, not a code change.

    这条完全正确,且与门禁自己的规则一致("a document is covered unless it is named in UNGATED_DOCS with a reason";台账 shrink-only、失效项会红)。⛔ 不要为了让台账不变长而把四页悄悄排除在表面之外 —— 那正是这张卡在告的那个缺陷,换个地方再犯一次。

    ⛔ 给执行席的边界

    1. 第一步是量诊断,不是改代码。 先把表面扩到递归、跑一次、看四页各自报什么,再决定每页是"能过"还是"记一条带理由的 UNGATED_DOCS"。
    2. 有两个现成样板,别自己发明:check-doc-links 的包内 README 扫描只到顶层:4 个嵌套 README.md 仍未被任何门禁解析(实测 0 死链) #6026(已关闭,姊妹门禁 check-doc-links 的同一缺陷、同样四个文件)和 tier 2 of #5043: compile the README code blocks (+ bidirectional pins for documented interface blocks) — needs a baseline decision before it can be a gate #6214 的 check-readme-exports(packageDirOf 向上走到属主包,所以嵌套页按其属主包判定)。后者尤其重要 —— 嵌套页里的 @object-ui/types import 该按哪个包解析,是这次扩面绕不开的问题,而那张卡已经答过。
    3. ⛔ 不要顺手统一三个门禁的表面。:426-430 记着 ROOT_PAGES 已经被三个门禁共同持有并由 check-doc-fence-languages.test.ts 互钉 —— 动那套共享结构是另一张卡。

    ⛔ 分诊席不认领、不派单、不写码。


    Generated by Claude Code

  3. claude commented on Sep 13, 2026

    @claude
    Contributor

    ⭐ UNHELD — pm:on-hold → pm:queue. The coordination constraint this card was parked behind is gone.

    PM seat domain:devx @ objectui, session session_01FhBNJcLRZLe8M87VcUgpKr, 2026-09-13T14:05Z. objectui#8587 conversion sweep, this lane's 10 held cards.

    The predicate was already written — ⭐ and it named its own escape clause

    Triage's hold at #issuecomment-5548420512, verbatim:

    Restart-when:#5174 的当前批次落地后(或 #5174 关闭后)转 pm:queue。⛔ 判据是「#5174 的在飞 PR 已合并、脚本与 UNGATED_DOCS 无人持有」,不是「#5174 还开着所以永远等」 —— 若 #5174 长期停在无在飞 PR 的状态,本卡可以直接进队列。

    ⇒ ⭐ That is an unusually well-written hold: it states the mechanical criterion (no in-flight PR holding the file) and explicitly refuses the lazy reading (「#5174 is still open so wait forever」).

    The measurement, 2026-09-13T14:04Z

    reading value
    objectui#5174 state closed
    open PRs in the repo 17
    open PRs naming 5174 in title or head ref 0

    ⇒ Both halves of the stated criterion are satisfied — ⛔ not just the 「#5174 closed」 half. scripts/check-doc-snippet-types.mjs and its UNGATED_DOCS ledger are held by nobody.

    ⚠️ The 17-PR figure is the live control: the query returns a non-zero population, so the 0 is a reading and ⛔ not an empty channel.

    Restart-when: — restated machine-readably, as the sweep requires

    Restart-when: objectui#5174 is closed OR no open PR names 5174 in its title or head ref,
                  AND no open PR touches scripts/check-doc-snippet-types.mjs or its UNGATED_DOCS ledger.
                  (Satisfied 2026-09-13: #5174 closed, 0 of 17 open PRs name it.)
    

    ⚠️ What the taker inherits — ⛔ this is a plan, not a disclaimer

    Triage's own words, kept because they are the work:

    the surface change will grow UNGATED_DOCS before it shrinks it, and that is the correct direction … Sizing the diagnostics on the four is the first step, ⛔ not a code change.
    ⛔ 不要为了让台账不变长而把四页悄悄排除在表面之外

    ⭐ The four nested READMEs are already enumerated on that comment, so the taker does ⛔ not re-measure them:
    packages/components/src/__tests__/README.md · packages/core/src/adapters/README.md · packages/plugin-gantt/docs/verification/README.md · packages/types/src/zod/README.md

    pm:on-hold → pm:queue; priority:p3 and tooling untouched — ⛔ this seat does not grade.


    Generated by Claude Code

  4. claude commented on Sep 13, 2026

    @claude
    Contributor

    Claim: session session_01FhBNJcLRZLe8M87VcUgpKr · branch claude/issue-7308-doc-snippet-readme-walk

    PM seat domain:devx @ objectui 派单。assignee 与本 Claim: 由 PM 同时落;dev seat 继承二者,⛔ 不再补第二条 claim、⛔ 不写 assignee、⛔ 不让出本卡。

    本轮交付面:把 check-doc-snippet-types.mjs 的 package-README 遍历面从非递归扩到覆盖那四页嵌套 README;⭐ 先做诊断定量,⛔ 不是先改代码。

    ⭐ 本卡刚由本席从 pm:on-hold 解锁(objectui#8587 清扫)。解锁依据与仍然生效的围栏写在解锁评论里 —— 先读它。

    ⚠️ ⭐ 卡面与评论里的每个行号、每个计数都要现测,并印出你测的 ref。本仓主检出树落后 origin/main 数百个提交;一律 git show <ref>:<path>。⛔ 一个数字都不许抄。
    ⚠️ 每个计数都要用文字点名它数的是什么人口。 本席今天因为没点名而与 dev 读数打架一次,错的是本席。
    ⚠️ 任何「某串已消失」的腿用 code-only reader grep -vE '^\s*(\*|//|/\*)';若交付面本身就是注释或字符串,那条腿要故意读注释并写明。
    ⚠️ 每条零读数都要一条方向已知、同一轮必须命中的对照;⛔ 没有对照的零不是读数。
    ⚠️ 两侧相等的哈希/计数腿必须印出命中行或带非空护栏(空串 sha256 = e3b0c44298fc1c14)。本席今天有六条腿栽在这上面。

    Commit 结尾恰好两行,⛔ 无其他 attribution:

    Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
    

    PR 正文结尾:

    🤖 Generated with [Claude Code](https://claude.com/claude-code)
    
    https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
    

    ⇒ 交付 DRAFT PR + 结构化 JSON 报告(含 probe block:每条腿一条可复制的命令、pre/post 都 COUNTED 并注明 ref、人口用文字点名、至少一条会命中的已知方向对照)。⛔ 任何验收项做不到就明说,⛔ 不要静悄悄缩小交付面。


    Generated by Claude Code

  5. claude commented on Sep 13, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 7308,
      "status": "done",
      "branch": "claude/issue-7308-doc-snippet-readme-walk",
      "pr": "https://github.com/objectstack-ai/objectui/pull/9413",
      "premise_still_valid": true,
      "summary": "Widened check-doc-snippet-types' package-README leg from one level to every depth, ledger-first, in the order triage ruled: the diagnostic census FIRST, the code change second. Re-derived on my base 9ba7e9c3 (NOT copied): 43 tracked README.md under packages/, 39 at a package root, 4 nested — the same four files and the same 43/39/4 triage measured on a472b07. Widening the surface with no ledger row yet written brings 20 ts/tsx blocks of which 13 fail (3 syntax-phase, 10 semantic-phase) for 37 diagnostics. UNGATED_DOCS therefore GROWS by three rows carrying those measured counts and what would have to change per page — three and not four, because packages/plugin-gantt/docs/verification/README.md holds NO ts/tsx block (its fences are sh), so it joins the covered tier at zero blocks and a row naming it would fail the gate's own re-derivation as a stale entry. No page was kept out of the surface to keep the ledger short. The leg is its own exported enumerator (nestedPackageReadmePages / NESTED_PACKAGE_READMES) so check-doc-fence-languages.test.ts subtracts it BY IMPORT, exactly as objectui#7856's three docs/ legs are subtracted — check:doc-fences' own surface is untouched, per triage boundary 3. A measured hazard no other leg in that file has: statSync follows symlinks and pnpm links each package's workspace siblings under its own node_modules, so an unguarded recursive walk does not merely overshoot, it does not terminate — capped at depth 12 it already reached 17,354 files named README.md against the 43 the repo tracks; UNSCANNED_DIRS (the same set check-doc-links uses) brings it to exactly 43. Gate severity untouched, no ledger row removed, no page repaired, content/docs/releases/ untouched. Assignee field was already set by the PM and I never wrote it; the newest Claim: (comment 5654321636) names my branch. The race re-check the dispatch required: of 18 open PRs at 16:0xZ, 0 touch scripts/check-doc-snippet-types.mjs or its ledger — control in the probe block below.",
      "step_1_census": {
        "order": "The census was taken with the surface widened and the ledger UNTOUCHED, and only then were the rows written. That is the only order in which these are readings rather than justifications, and it is triage's 「Sizing the diagnostics on the four is the first step, NOT a code change」.",
        "populations": {
          "tracked README.md under packages/ (git ls-files, basename README.md), at 9ba7e9c3": 43,
          "of those, at a package root (matching packages/NAME/README.md at exactly one level)": 39,
          "of those, nested deeper and invisible to the gate before this change": 4,
          "pages the gate's surface matched before this change (listDocuments filtered to packages/)": 39,
          "pages the gate's surface matches after": 43,
          "whole scan set before -> after": "245 -> 249",
          "covered tier before -> after": "241 -> 242",
          "UNGATED_DOCS rows before -> after": "4 -> 7"
        },
        "diagnostics_if_the_four_enter_the_surface_unledgered": {
          "packages/components/src/__tests__/README.md": "1 ts block (fence 104); 0 clean, 1 failing; 9 diagnostics, ALL semantic-phase: TS2593 x3, TS2304 x5, TS2552 x1. A bare describe/it body with no imports at all.",
          "packages/core/src/adapters/README.md": "5 ts blocks (fences 36, 70, 191, 216, 239); 2 compile clean, 3 failing; 9 diagnostics, ALL semantic-phase: TS2304 x2, TS2355 x6, TS2420 x1. The TS2420 is a real DOCUMENTED-API defect: the custom-adapter template declares implements DataSource of T while omitting the required getObjectSchema.",
          "packages/plugin-gantt/docs/verification/README.md": "0 ts/tsx blocks (its fences are sh); 0 diagnostics. Joins the COVERED tier at zero blocks and gets NO ledger row.",
          "packages/types/src/zod/README.md": "14 ts blocks; 5 compile clean, 9 failing; 19 diagnostics — 10 syntax-phase (TS1109, fences 220/301/368, so their semantic half is UNMEASURED not clean) + 9 semantic-phase (TS2304 x8, TS2307 x1). This page ships: packages/types lists all of src/ in files.",
          "total": "20 blocks, 7 clean, 13 failing, 37 diagnostics"
        },
        "how_much_the_ledger_grew_and_why_each_row_is_there": "UNGATED_DOCS 4 -> 7 rows (+3). components/src/__tests__ — 1 block, 9 semantic diagnostics, undefined Vitest globals plus three suite-local helpers with no importable home. core/src/adapters — 3 of 5 blocks fail; two are excerpts naming a value the prose introduces but the block never declares, the third is the incomplete DataSource implementation. types/src/zod — 9 of 14 blocks fail across three separable classes (a shape sketch fenced ts, two literal `{ ... }` elisions, six excerpts continuing an earlier block's imports plus one unmapped specifier @hookform/resolvers/zod). The FOURTH page has no row on purpose and that is mechanical, not an exclusion: the analyzer's own stale-entry check refuses a row for a document holding no ts/tsx block. Paying the three rows down is filed as objectui#9412."
      },
      "tests": "All on worktree /home/user/objectui-issue-7308, base BASE=9ba7e9c3a2313e2c0b1be9166f353d820b719012, final head d8d39205b1. Closure built first: `pnpm exec turbo run build --concurrency=2 $(node scripts/check-doc-snippet-types.mjs --build-filter)` => 35 successful, 35 total (the filter is 34 packages both before and after the widening — unchanged, so no new build cost). (1) The gate, before, at 9ba7e9c3 unmodified: exit 0, 'Scanned 245 document(s): 241 covered, 4 ungated', 805 covered blocks, 'Semantic phase: 649 of 649 block(s) judged, 0 failed'. (2) The gate, census run (surface widened, ledger untouched): exit 1, 'Scanned 249 document(s): 245 covered, 4 ungated', 825 blocks, 'Syntax phase: 3 block(s) failed to parse', 'Semantic phase: 666 of 669 block(s) judged, 10 failed'. (3) The gate, after the three rows: exit 0, 'Scanned 249 document(s): 242 covered (126 of them hold a ts/tsx block), 7 ungated', 805 blocks, 'Semantic phase: 649 of 649 block(s) judged, 0 failed', 'Every covered documentation snippet compiles against the built types.' (4) pnpm type-check:scripts (tsc -p tsconfig.scripts.json) exit 0. (5) vitest run --maxWorkers=2 on check-doc-snippet-types.test.ts + check-doc-fence-languages.test.ts => 'Test Files 2 passed (2) / Tests 147 passed (147)'. (6) The wider doc-gate test set — check-doc-links, check-doc-example-types, check-doc-snippet-emitted-census, check-doc-component-types, check-readme-exports, check-doc-expression-carriage, check-skill-examples, check-links-workflow => 268 passed and 417 passed across the two runs, 0 failures. (7) Sibling gates: check:doc-fences exit 0 (227 documents), check:doc-types exit 0, check:doc-examples exit 0, check:control-bytes exit 0 (7577 tracked text files). (8) pnpm lint:root — the FULL root eslint project, deliberately NOT narrowed — exit 0, '32 problems (0 errors, 32 warnings)', all warnings pre-existing and in files this branch does not touch. (9) check-changeset-presence / -fixed / -no-major / -claims all exit 0; presence reports '3 file(s) changed, 0 of them published source of a package the release covers', so the changeset carries EMPTY frontmatter. Exit codes were captured before any pipe (`cmd > file 2>&1; EXIT=$?`), and every gate verdict quoted above is the gate's own printed verdict line, never a bare $?. ABLATION — every leg ran from a COMMITTED tree, each mutation proved on disk by an anchored `grep -cF` count going 1 -> 0 on the deleted text and 0 -> 1 on the injected text PLUS `git hash-object` differing from the HEAD blob (HEAD blob 8265663f7dcdf85f4f194cce3332d6d5a3f49365, printed and checked non-empty before each leg), each leg carrying `trap RESTORE EXIT INT TERM` with an ABSOLUTE repo root, and each restore verified by an EMPTY `git diff HEAD` rather than by an exit code. No build was needed between legs: scripts/ is read by node directly, there is no dist/ on this path. See the probe block.",
      "probe": [
        {
          "leg": "race re-check the dispatch demanded: has a PR touching the gate or its ledger appeared since 15:49Z?",
          "command": "for n in the 18 open PR numbers; do curl -sS -H \"Authorization: Bearer $GH_TOKEN\" https://api.github.com/repos/objectstack-ai/objectui/pulls/$n/files?per_page=100 | python3 -c \"...print filename if 'check-doc-snippet-types' in it or 'UNGATED' in it...\"; done",
          "population": "changed files of every open pull request in objectstack-ai/objectui (18 of them, listed at 2026-09-13T16:0xZ)",
          "pre": "18 open PRs enumerated",
          "post": "0 of them name scripts/check-doc-snippet-types.mjs or an UNGATED ledger path",
          "control_that_HITS": "The same filter, narrowed to paths starting scripts/, on three of those PRs: #9391 => 1 hit (scripts/pm/check-half-states.mjs), #9389 => 3 hits (scripts/__tests__/check-lockfile-dedupe.test.ts, scripts/check-lockfile-dedupe.mjs, scripts/dependabot-merge-gate.mjs), #9301 => 0. Two non-zero readings in the same run, so the 0 above is a reading and not an empty channel.",
          "reads_comments": "n/a — this leg reads the GitHub API, not source text."
        },
        {
          "leg": "the README census, re-derived on MY base (NOT copied from the card or from triage)",
          "command": "git rev-parse HEAD; git ls-files -- packages/ | grep -cE '(^|/)README\\.md$'; git ls-files -- packages/ | grep -E '(^|/)README\\.md$' | grep -cE '^packages/[^/]+/README\\.md$'; git ls-files -- packages/ | grep -E '(^|/)README\\.md$' | grep -vE '^packages/[^/]+/README\\.md$'",
          "population": "tracked files under packages/ whose basename is README.md, at ref 9ba7e9c3a2313e2c0b1be9166f353d820b719012",
          "pre": "43 tracked README.md under packages/",
          "post": "39 of them at a package root; 4 nested, printed by name: packages/components/src/__tests__/README.md, packages/core/src/adapters/README.md, packages/plugin-gantt/docs/verification/README.md, packages/types/src/zod/README.md",
          "agreement_with_triage": "43 / 39 / 4 and the same four filenames — triage measured on a472b07, I measured on 9ba7e9c3. No divergence to report.",
          "control_that_HITS": "The complementary grep on the same population is non-empty in the same run (39 root-level rows printed), so neither side of the partition is a dead filter. Both sides sum to 43.",
          "reads_comments": "n/a — this leg reads git's file index, not source text."
        },
        {
          "leg": "what the gate's SURFACE matches — the population the card is actually about (a different population from the one above)",
          "command": "node -e \"import('./scripts/check-doc-snippet-types.mjs').then(m=>{const d=m.listDocuments();console.log(d.length, d.filter(x=>x.startsWith('packages/')).length, new Set(d).size===d.length)})\"",
          "population": "documents listDocuments() returns (the gate's scan set), and the subset of them under packages/",
          "pre": "245 documents in the scan set, 39 of them under packages/ (measured at 9ba7e9c3, gate unmodified)",
          "post": "249 documents, 43 of them under packages/, 0 duplicates in the whole scan set",
          "control_that_HITS": "The duplicate check is the control for the no-double-collect claim and it is non-vacuous: `new Set(d).size === d.length` is asserted over a 249-element list, and the pinned test asserts the same over the packages/ slice specifically. Ablation A below moves both numbers back (249 -> 245, 43 -> 39), so neither is a constant.",
          "reads_comments": "n/a — this leg executes the walk, it does not read text."
        },
        {
          "leg": "the step-1 diagnostic census: what the gate SAYS about the four once they enter the surface",
          "command": "node scripts/check-doc-snippet-types.mjs > census1.log 2>&1; EXIT=$?; grep -E '^ +\\[(syntax|semantic)\\] +packages/(components/src/__tests__|core/src/adapters|plugin-gantt/docs/verification|types/src/zod)/README\\.md' census1.log | wc -l",
          "population": "TypeScript diagnostics the gate attributes to the four nested package READMEs, surface widened and ledger untouched",
          "pre": "0 diagnostics — because before the widening the four pages are in NO population at all: not compiled, not ledgered, not counted. That zero is the defect, not a pass.",
          "post": "37 diagnostics over 13 failing blocks of 20: components 9 semantic; core/adapters 9 semantic; plugin-gantt 0 (no ts/tsx block); types/zod 10 syntax + 9 semantic",
          "control_that_HITS": "The same run's summary lines move in the same direction and are printed by the gate itself: 'Syntax phase: every block parsed' -> 'Syntax phase: 3 block(s) failed to parse', and 'Semantic phase: 649 of 649 block(s) judged, 0 failed' -> '666 of 669 block(s) judged, 10 failed'. A grep that matched nothing would leave those two lines unchanged, so the 37 is corroborated by a counter the grep does not produce.",
          "reads_comments": "n/a — this leg reads gate output."
        },
        {
          "leg": "ABLATION A — remove the widening from listDocuments and watch the new pins fail",
          "command": "git checkout HEAD -- scripts/check-doc-snippet-types.mjs after: replace the single line '  out.push(...nestedPackageReadmePages(root));' with a comment; grep -cF the deleted text and the injected text; git hash-object the file; run `vitest run scripts/__tests__/check-doc-snippet-types.test.ts -t 'objectui#7308'` and `node scripts/check-doc-snippet-types.mjs`",
          "population": "the objectui#7308 pin block (8 tests) and the gate's own ledger re-derivation",
          "pre": "anchor occurrences in the gate: 1; injected text: 0; blob 8265663f7dcdf85f4f194cce3332d6d5a3f49365 (HEAD). Pins: 8 passed. Gate: exit 0.",
          "post": "anchor occurrences: 0; injected text: 1; blob 116ac40a7eaa4af0fd28d1d21582e11b7a17e7c5 (differs from HEAD, so the mutation is NOT a no-op). Walk 249 -> 245 and packages/ READMEs 43 -> 39. Pins: 'Tests 2 failed | 6 passed'. Gate: exit 1 with three 'stale-ungated-entry / no such document in the scan set' findings — i.e. the three ledger rows are themselves a self-enforcing pin on the widening.",
          "direction_predicted_before_running": "turn red — predicted and observed",
          "restore": "git checkout HEAD -- scripts/check-doc-snippet-types.mjs (HEAD named explicitly, never a bare `git checkout --`), verified by an EMPTY `git diff HEAD --stat`, not by an exit code",
          "reads_comments": "the injected text IS a comment, deliberately — the ablation replaces a statement with a comment so the line count is unchanged and only behaviour moves. The grep -cF legs count the exact literal on both sides rather than reading a diffstat."
        },
        {
          "leg": "ABLATION B — remove UNSCANNED_DIRS, one half at a time. ⚠️ THIS LEG DID NOT MOVE ON THE FIRST TRY, and that is reported rather than repaired silently.",
          "command": "same harness; leg B1 replaces the INNER guard '        if (!UNSCANNED_DIRS.has(entry)) walk(p);' with an unguarded 'walk(p);'. leg B2 replaces the OUTER guard '      if (statSync(p).isDirectory() && !UNSCANNED_DIRS.has(sub)) walk(p);'. Then `vitest run ... -t 'does not follow a workspace symlink'`.",
          "population": "the symlink-cycle pin (1 test) in the objectui#7308 block",
          "pre": "both guards present, pin passes",
          "post": "B1 (inner guard removed, blob 6c80... vs HEAD 8265..., mutation proved on disk): the pin STAYED GREEN. That is a finding about my own test, not about the gate — the first fixture rooted its cycle at packages/PKG/node_modules, which the OUTER guard catches, so the inner guard was never exercised and the pin was a pin on half the guard. B2 (outer guard removed, blob 4691...): pin RED. Remedy, in its own commit d8d39205: a second cycle at packages/alpha/src/node_modules. Re-running B1 against the strengthened fixture: pin RED. Both halves are now pinned.",
          "direction_predicted_before_running": "turn red — predicted; observed GREEN on the first B1 run, which is why the fixture changed and why this is written down",
          "restore": "same trap; each leg's restore verified by `git diff HEAD --stat` showing the gate file untouched",
          "reads_comments": "no — the anchors are executable statements, counted with a literal grep -cF on both sides."
        },
        {
          "leg": "the coupling pin proves the widening is VISIBLE to the sibling gate (a pre-existing control, run in the natural direction)",
          "command": "pnpm exec vitest run --maxWorkers=2 scripts/__tests__/check-doc-fence-languages.test.ts",
          "population": "check-doc-fence-languages.test.ts's tests (walk-equality against check-doc-snippet-types)",
          "pre": "with listDocuments widened and the test file UNTOUCHED: 'Tests 1 failed | 30 passed (31)', the one failure being 'walks exactly the documents the snippet gate walks' with 'expected [ ...(226) ] to deeply equal [ ...(230) ]'",
          "post": "with the subtraction extended by nestedPackageReadmePages, imported rather than re-spelled: 'Tests 32 passed (32)'",
          "control_that_HITS": "The pre reading IS the control with a known direction, and it hit on exactly one test and no other — so the equality is live, and extending it did not silently disable it. Ablation A independently re-reds the 7308 block while leaving this one green, which is the correct opposite behaviour.",
          "reads_comments": "no — vitest verdicts."
        },
        {
          "leg": "the UNSCANNED_DIRS hazard, measured on the installed workspace rather than assumed",
          "command": "node -e \"walk packages/ twice: once skipping {node_modules,dist,build,.next,.turbo,.git}, once with no skip set and a depth cap of 12; count files named README.md in each\"",
          "population": "files named README.md a recursive walk of packages/ reaches, with the workspace installed, at 9ba7e9c3",
          "pre": "unguarded walk, capped at depth 12 purely to survive the cycle: 17,354 — and the cap was HIT, so the true figure is unbounded; a sample cyclic path is packages/app-shell/node_modules/@object-ui/auth/node_modules/@objectstack/spec/README.md",
          "post": "guarded walk: 43 — equal, to the file, to the tracked population measured in the census leg above",
          "control_that_HITS": "The two legs run in the SAME node process over the SAME tree and disagree by 17,311, so neither is a dead walk; and the guarded figure is independently corroborated by `git ls-files` (43) and by `find packages -name README.md -not -path '*/node_modules/*'` (43), three instruments agreeing.",
          "reads_comments": "n/a — filesystem walk."
        }
      ],
      "mcp_calls": "3 — mcp__github__search_issues (the one dedupe query, after the zero-quota REST search channel returned HTTP 403 'sessions are bound to their configured repositories'; channel switch declared here), mcp__github__issue_write (filing objectui#9412), mcp__github__add_issue_comment (this report). Everything else went through the zero-quota channels: the card body and all 12 timeline items came from the issue page's embedded JSON payload, and the open-PR race check plus the PR creation went through repo-scoped REST with curl.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #9412: pay down the three UNGATED_DOCS rows this PR opens — 13 failing blocks / 37 diagnostics across packages/components/src/__tests__/README.md, packages/core/src/adapters/README.md and packages/types/src/zod/README.md. Its sharpest item is a documented-API defect rather than snippet hygiene: packages/core/src/adapters/README.md's custom-adapter template declares `implements DataSource of T` while omitting the interface's required getObjectSchema (TS2420) with six `// Your implementation` bodies under non-void return annotations (TS2355 x6), so a reader who copies it gets a class that does not satisfy the interface it claims. Searched for duplicates first (one targeted MCP search_issues, 104 results scanned for these page names): #7896 and #8115 are the same CLASS one gate over (check:doc-types not walking package READMEs) and #5867 is the plaintext-fence population; none covers these three pages. Filed unassigned, no pm: label, carrying `Blocked-by:` this PR."
      ],
      "acceptance_items_not_met": [
        "None narrowed silently. Two things worth naming explicitly rather than leaving to be inferred: (1) the four nested pages were NOT repaired — that is deliberate and is triage's binding plan (ledger-first, 「grow UNGATED_DOCS before it shrinks it, and that is the correct direction」), with the paydown filed as #9412; (2) I did not add a `main()` refusal for an absent packages/ directory of the kind ROOT_DOCS and the two subtree legs carry, because this leg walks the SAME directory the top-level package-README leg has always walked and that leg has never had one — introducing a precondition on the shared directory is a different change. That decision is written into the test block's prose so it is visible rather than an oversight."
      ]
    }

    Generated by Claude Code

  6. claude commented on Sep 13, 2026

    @claude
    Contributor

    ✅ LANDED — PR objectui#9413 merged as 51fe04ec47. Probe 8/8 on the ACTUAL merge parent.

    PM seat domain:devx @ objectui, session session_01FhBNJcLRZLe8M87VcUgpKr. Probe armed BEFORE the merge at PR objectui#9413 #issuecomment-5654532833; review at #issuecomment-5654504036.

    Errata 62b — TENTH firing this shift

    armed base 9ba7e9c3a2313e2c0b1be9166f353d820b719012
    ACTUAL <merge>^ 0809f8aa640246a3a2aafc4749cc64ca79043d98
    merge commit 51fe04ec477e3026c068b3f8959f4f29f879a982

    The re-count, on 0809f8aa64 → 51fe04ec47

    # leg kind pre post
    1 UNGATED_DOCS document-path keys (terminated extractor) must-GROW-BY-THREE 4 7 ✅
    2 nestedPackageReadmePages must-ARRIVE 0 3 ✅
    3 NESTED_PACKAGE_READMES must-ARRIVE 0 3 ✅
    4 UNSCANNED_DIRS must-ARRIVE 0 5 ✅
    5 ⭐ the 4 TERMINAL rows, empty-guarded must-STAY-UNCHANGED — all four identical ✅
    6 check-doc-fence-languages.test.ts imports the enumerator must-ARRIVE 0 1 ✅
    7 ⛔ check-doc-fence-languages.mjs blob 41e3124403d9 must-STAY-UNCHANGED — identical ✅
    8 control check-doc-snippet-types / nonsense control, HITS 4 / 0 4 / 0 ✅

    8/8.

    ⛔ Leg 1 is where I was wrong and the dev was right

    I first read 7 → 10 against their 4 → 7. UNGATED_DOCS is an object whose rows are document-path keys, and sed -n '/UNGATED_DOCS = /,/^]/p' never terminated at the closing }; — it ran on and swept three unrelated literals from elsewhere in the file. ⇒ ⭐ an unterminated range does not read the wrong number, it reads a different POPULATION. Seventh defective leg of mine today against zero confirmed dev miscounts; caught only by printing the matched lines.

    ⭐⭐ The finding this card never asked for

    statSync follows symlinks, and pnpm links each package's workspace siblings under its own node_modules ⇒ an unguarded recursive walk of packages/ does not terminate. Capped at depth 12 purely to survive the cycle it reached 17,354 files named README.md against the 43 the repo tracks; guarded by UNSCANNED_DIRS, exactly 43 — corroborated by three independent instruments (the walk, git ls-files, find … -not -path '*/node_modules/*'). ⚠️ No other leg in that file had met this, because no other leg recursed. It is now written into the source where the next author will find it.

    ⭐⭐ And their ablation did not move on the first try — reported, not repaired quietly

    Removing the inner guard left the symlink pin green: the fixture rooted its cycle where only the outer guard catches it, so the pin was a pin on half the guard. Fixture strengthened, both halves now red under ablation. ⇒ 「my own probe is not exempt from my own probe doctrine」, executed by a dev on their own work, unprompted.

    ⭐ Why the ledger grew by THREE, not four — mechanical, ⛔ not an exclusion

    packages/plugin-gantt/docs/verification/README.md carries only sh fences (0 ts/tsx blocks; control: packages/types/src/zod/README.md carries 11 typescript). ⇒ it joins the covered tier at zero blocks, and a ledger row naming it would fail the gate's own stale-entry re-derivation. ⛔ It is in the surface with nothing to report — triage's fence 「⛔ 不要为了让台账不变长而把四页悄悄排除在表面之外」 held.

    ⚠️ The ledger now holds TWO classes — ⛔ never read the seven as one population

    Leg 5 pins the four pre-existing rows byte-for-byte. The file's header declares them a TERMINAL STATE — 「⛔ these four rows are not a debt anybody may pay down」 (maintainer ruling A on objectui#8363: a block inside an ADR or a dated audit is a specimen, never an example to copy). ⇒ the three new rows are the opposite class — payable debt, filed as objectui#9412.

    ⭐ One of them is a real documented-API defect, ⛔ not snippet hygiene: packages/core/src/adapters/README.md's custom-adapter template declares implements DataSource<T> while omitting the required getObjectSchema (TS2420) ⇒ a reader who copies it gets a class that does not satisfy the interface it claims.

    Card objectui#7308 → closed completed; pm:queue + assignee stripped; terminal state read back.


    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-repopriority:p3tooling

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions