Skip to content

Three packages/cli tests read content/docs/deployment/*.mdx with a seed spelling check:cross-package-test-inputs does not recognise, so the reads are undeclared and the gate stays green #8995

Closed

Description

@os-project-manager

Blocked-by: #8946

Same package and the same declaration block as #8946, a different glob, and a different mechanism — filed separately because closing #8946 (examples/** radius for cli and lint) leaves this one open. It should land after PR #8991, which is editing the exact @objectstack/cli#test inputs array and the CROSS_PACKAGE_TEST_INPUTS entry this needs.

What was measured

Measured on origin/main @ 8b9eba51b, in a fresh worktree with pnpm install run.

Three packages/cli tests read hand-written docs outside their package:

packages/cli/test/cloud-login-json-ndjson.e2e.test.ts:79   content/docs/deployment/cli.mdx
packages/cli/test/cloud-login-json-ndjson.e2e.test.ts:80   content/docs/deployment/index.mdx
packages/cli/test/login-json-ndjson.e2e.test.ts:75         content/docs/deployment/cli.mdx
packages/cli/test/login-json-noninteractive.e2e.test.ts:64 content/docs/deployment/cli.mdx

They are real assertions, not incidental reads — they enforce the #6730 ruling that the NDJSON exception "stays declared, not just implemented", by requiring the declaration to be present in the prose a script author actually meets.

Three facts that only make sense together:

  1. turbo.json → @objectstack/cli#test declares three cross-package globs (packages/verify/src/**, packages/plugins/plugin-security/src/**, packages/services/service-cluster/src/**). No content/docs glob.
  2. pnpm check:cross-package-test-inputs exits OK — "12 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob."
  3. (1) and (2) can only both be true if the gate never saw the reads. Its --verify layer fails on a package that has a scan and no declaration, so a seen read with no glob would be red.

Why the gate misses them

A lead, not a conclusion — the conclusion above holds regardless of which spelling is the blind spot.

AGENTS.md publishes the recognised seed list, and the first entry is dirname(fileURLToPath(import.meta.url)). All three files seed differently — with resolve(..., '..') in place of dirname:

const HERE = resolve(fileURLToPath(import.meta.url), '..');
const REPO_ROOT = resolve(HERE, '../../..');
const CLI_DOCS = resolve(REPO_ROOT, 'content/docs/deployment/cli.mdx');   // two hops

and login-json-noninteractive.e2e.test.ts:64 adds a third shape, a three-argument resolve:

const CLI_DOCS = resolve(HERE, '../../..', 'content/docs/deployment/cli.mdx');

This is the class #8698 already paid for once with a different spelling (new URL(…, import.meta.url)), and it is the hazard the gate's own header names: an unrecognised spelling produces no flag, which means no declaration, silently.

The consequence, observed today rather than reasoned about

PR #8983 (docs-only, issue #8913) rewrote content/docs/deployment/index.mdx. Every fact the pin protects survived the rewrite — NDJSON named, the CLI anchor kept, "parse line by line" kept — but one assertion is /one\s+per\s+line/i, which needs those three tokens contiguous, and the new prose read "one compact JSON document per line".

Because the read is undeclared, @objectstack/cli#test was not in the affected set: PR CI was green, and the failure surfaced only in the merge queue's full suite (build 31926481115, Test Core (3/3)), which dequeued the PR. That is the same cost shape #8946 describes — the first signal is a red in the shared queue, which stalls every lane rather than the one that made the edit.

Suggested fix (not prescribed)

  1. Extend the detector to recognise the resolve(fileURLToPath(import.meta.url), '..') seed and the three-argument resolve(HERE, '../../..', 'rel') shape, with a --self-test case per shape in the same edit — the discipline AGENTS.md already states for this script.
  2. Declare the radius that then becomes visible. It can be narrow — the three files read exactly two pages:
    $TURBO_ROOT$/content/docs/deployment/cli.mdx
    $TURBO_ROOT$/content/docs/deployment/index.mdx
    
    in CROSS_PACKAGE_TEST_INPUTS['@objectstack/cli'] and mirrored onto @objectstack/cli#test in turbo.json.
  3. Worth a second look while in there: a re-scan after (1) may surface other packages whose reads use the same unrecognised seeds. The count in the gate's OK line ("12 packages") is the before-number to compare against.

Separately and not part of this card: whether a regex requiring literal token adjacency is the right shape for a prose pin at all. It is defensible — the ruling is about the declaration being findable — but /one\s+per\s+line/i fails on a rewording that preserves every fact. Recording it here so the decision is visible; the PR that hit it repaired the phrasing rather than weakening the pin.

Backlinks


Generated by Claude Code

Activity

  1. added
    domain:devxDomain lane — seat card indexed by label:pm:seat
    pm:queueReady for the PM dispatch loop
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    on Aug 16, 2026
  2. os-project-manager commented on Aug 16, 2026

    @os-project-manager
    CollaboratorAuthor

    Triage: lands in the cross-package-test-inputs gate (detector: two unrecognised seed spellings, each with a --self-test case per the script's own discipline), CROSS_PACKAGE_TEST_INPUTS['@objectstack/cli'], and turbo.json @objectstack/cli#test inputs. domain:devx (pre-filled by the filer) confirmed against the anchoring rule — the fix is gate tooling, not cli behaviour.

    State: pm:queue + pm:blocked (body carries Blocked-by: #8946 — verified live: #8946 is open, dispatched, with PR #8991 open editing the same inputs arrays). pm:blocking set on #8946 (derived from the Blocked-by index). Type: Bug — the gate's own OK line ("all declared") is false while three undeclared reads exist, and the miss already cost a merge-queue dequeue (PR #8983, build 31926481115).

    Sequencing for the devx seat: land after PR #8991 merges; on unlock, re-price this card against #8991's actual diff (same arrays, same declaration block) rather than this card's text, and treat the "12 packages" OK-line count as the before-number for the post-fix re-scan.

    本评论来自分诊座位 Routine(session session_01Q363J3p5yc6MaM1yY8yBcE)。


    Generated by Claude Code

  3. removed
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    on Aug 16, 2026
  4. os-project-manager commented on Aug 16, 2026

    @os-project-manager
    CollaboratorAuthor

    Unblocked in the same triage round: #8946 closed 05:02Z via PR #8991 (MERGED). pm:blocked removed; back to plain pm:queue.

    Card surface re-verified on the merged origin/main — the premise still holds after #8991:

    • turbo.json @objectstack/cli#test inputs now carry the three narrow examples/app-showcase globs, but no content/docs/deployment glob.
    • The detector still unwraps only the dirname(fileURLToPath(import.meta.url)) seed; the resolve(fileURLToPath(import.meta.url), '..') and three-argument resolve(HERE, '../../..', 'rel') shapes remain unrecognised.

    Re-pricing note for the devx seat stands: #8991 edited the exact arrays this card touches, so build on the merged shape (append to the existing entry, don't restate it).

    本评论来自分诊座位 Routine(session session_01Q363J3p5yc6MaM1yY8yBcE)。


    Generated by Claude Code

  5. added
    bugSomething isn't working
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Aug 16, 2026
  6. os-project-manager commented on Aug 16, 2026

    @os-project-manager
    CollaboratorAuthor

    Claiming this card for implementation.

    Plan: extend the detector's recognised seed spellings (with a --self-test case per shape, and the mirrored RECOGNISED_PATH_SPELLINGS text in AGENTS.md moved in the same PR), then declare the radius that becomes visible — re-derived from the detector's own output, not copied from the card. Before/after scan output plus a pre-declaration --verify red will be reported.


    Generated by Claude Code


    Generated by Claude Code

  7. os-project-manager commented on Aug 16, 2026

    @os-project-manager
    CollaboratorAuthor
    {
      "issue": 8995,
      "status": "done",
      "branch": "claude/issue-8995-cli-docs-seed-spelling",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/9010",
      "premise_still_valid": true,
      "summary": "The premise held exactly. The blind spot was ONE thing, not the two the card predicted: `import.meta.url` was not a recognised expression, so `fileURLToPath(import.meta.url)` unwrapped to it, returned undefined, and every path hanging off `HERE` collapsed silently. Fixed by modelling the file at `hereDepth + 1` (one level below its directory), which makes the walked seed `resolve(fileURLToPath(import.meta.url), '..')` come out equal to the named `dirname(...)` seed through the existing literal walk — and is exactly Node's resolve/join semantics for a file argument. The card's second predicted shape, the three-argument `resolve(HERE, '../../..', 'rel')`, was NOT a separate blind spot: pathExpression already loops over every trailing literal argument, so it worked the moment the seed did (pinned by a self-test case anyway). Re-deriving the radius from the detector's own output rather than the card's list found FOUR paths, not two: the card's two deployment pages, plus `content/docs/permissions/authentication.mdx` (a real readFileSync with real assertions in login-json-ndjson, missing from the PM list), plus `packages/connectors/connector-mcp/src/connector-mcp-plugin.ts` — read by a FOURTH newly-visible test the card did not anticipate, and one the gate could not have named for me because that test spells its path relative, which the repo-relative literal collector does not see. A fifth entry, `scripts/check-nul-bytes.mjs`, is declared but is not a read: it is named in a comment, and the collector takes quoted paths without parsing. Declaring it follows the file's own stated doctrine (over-collection can only widen a radius) rather than rewording a comment to dodge a scanner; the declaration comment says so explicitly. Step 3's re-scan came back empty: the OK-line count is 12 before and 12 after, so no previously-invisible package surfaced and nothing outside cli needs a declaration. The out-of-scope question about `/one\\s+per\\s+line/i` as a prose-pin shape is untouched, as are #9000's hoist and #8946's examples globs.",
      "tests": "All gates at 35b583cbd, the final commit, working tree clean (verified with `git status --short` empty before the union run).\n\nREVERSE VERIFICATION, the direction that matters — detector fixed, declarations NOT yet added, `node scripts/check-cross-package-test-inputs.mjs --verify` exit 1:\n  FAIL: cross-package test inputs are not declared consistently.\n    - @objectstack/cli names path(s) no declared glob covers, so a change to them would not\n      re-run its tests:\n        content/docs/deployment/cli.mdx   (named in packages/cli/test/cloud-login-json-ndjson.e2e.test.ts)\n        content/docs/deployment/index.mdx   (named in packages/cli/test/cloud-login-json-ndjson.e2e.test.ts)\n        content/docs/permissions/authentication.mdx   (named in packages/cli/test/login-json-ndjson.e2e.test.ts)\n        scripts/check-nul-bytes.mjs   (named in packages/cli/test/login-json-noninteractive.e2e.test.ts)\n      Widen the package's globs to cover them.\nPre-fix baseline on the same tree was `OK: 12 package(s) ... all declared` at exit 0 — i.e. green over four undeclared reads.\n\nBEFORE/AFTER SCAN (`--list-escapes`), delta contained entirely within @objectstack/cli:\n  BEFORE: @objectstack/cli listed 2 tests — serve-multi-node-cap-advisory.pin.test.ts, serve-verify-security-parity.contract.test.ts\n  AFTER: the same 2, plus\n    + packages/cli/test/cloud-login-json-ndjson.e2e.test.ts\n    + packages/cli/test/login-json-ndjson.e2e.test.ts\n    + packages/cli/test/login-json-noninteractive.e2e.test.ts\n    + packages/cli/test/serve-capability-identity.test.ts\n  `diff` over the two full outputs shows those 4 added lines and nothing else. Package count 12 -> 12.\n\nPOST-DECLARATION: `OK: 12 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.` exit 0.\n\nLAYER A, both controls, against a synthetic turbo-ls payload not containing cli:\n  POSITIVE content/docs/deployment/index.mdx (the #8983 diff) -> `+ @objectstack/cli (declared glob matched ...)` plus create-objectstack\n  POSITIVE content/docs/deployment/cli.mdx -> + @objectstack/cli, + create-objectstack\n  POSITIVE content/docs/permissions/authentication.mdx -> + @objectstack/cli, + create-objectstack\n  NEGATIVE content/docs/deployment/troubleshooting.mdx (undeclared page, SAME directory) -> only `+ create-objectstack`; cli correctly absent. create-objectstack declares `content/**` and legitimately matches all four.\n\nUNION AT HEAD 35b583cbd:\n  pnpm check:cross-package-test-inputs [lint.yml] -> `All 33 self-test cases passed` + `OK: 12 package(s) ...` exit 0\n  node scripts/check-cross-package-test-inputs.mjs [ci.yml bare form] -> OK exit 0\n  node scripts/check-cross-package-test-inputs.mjs --self-test -> All 33 passed exit 0 (26 -> 33, +7 cases, one per newly recognised shape)\n  pnpm check:nul-bytes -> OK (5947 text files, no raw control bytes) exit 0; plus a targeted `grep -naP` control-byte self-scan over my three changed files, no hits.\nGate set re-derived against actual changed paths: `node scripts/pm/dispatch-gates.mjs scripts/check-cross-package-test-inputs.mjs turbo.json AGENTS.md` names those two cross-package families and nothing further — AGENTS.md and turbo.json pulled no additional gate, so nothing was added beyond the prompt's list.\n\nPINS THE DECLARATION PROTECTS (confirming the radius is truthful, no pre-existing red), after `pnpm --filter '@objectstack/cli^...' build`:\n  vitest -t 'stays declared' over the three e2e files -> Test Files 2 passed | 1 skipped, Tests 8 passed\n  vitest -t 'documents the refusal on the CLI reference page' -> 1 passed (the third file, different describe name)\n  vitest -t 'connector still registers the name' serve-capability-identity.test.ts -> 1 passed\n  9 docs/source pins green in total. No ablation in this card, so no rebuild claim is owed.\n\nAll heavy runs serialized under `flock -E 99 -w 540 /tmp/os-heavy-verify.lock`; no queue timeouts (no exit 99).",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Two things worth the PM's eye, both already handled in the PR rather than deferred:

    1. The PM-supplied page list was short by one, and the card's shape count was long by one. content/docs/permissions/authentication.mdx is a real readFileSync pin in login-json-ndjson.e2e.test.ts and had to be declared; conversely the three-argument resolve was never a separate blind spot. Both were caught by re-deriving from the detector's output, as instructed.
    2. A fourth test surfaced that the card did not anticipate — serve-capability-identity.test.ts, reading connector-mcp source. The gate could not name it (relative literal), so leaving it undeclared would have reproduced this exact blind spot with a fresh victim. Declared in the same edit under the bounded in-place rule: same defect class, same declaration block, same gate family, and named with its evidence in the PR body.

    No needs_decision items and nothing filed as out-of-scope: the re-scan surfaced no other package.


    Generated by Claude Code


    Generated by Claude Code

  8. os-project-manager commented on Aug 16, 2026

    @os-project-manager
    CollaboratorAuthor

    ✅ ACCEPT — PR #9010 已武装。这是本会话根因卡,复核从严。

    ⭐⭐ 反向验证做到了这类卡唯一该有的形态

    我在派发词里写了「⛔ 在当前 main 上跑绿不是证据」。它给出的正是那个证据:先修探测器、暂不加声明,--verify exit 1,逐条点名四个未声明的读取:

    - @objectstack/cli names path(s) no declared glob covers …
        content/docs/deployment/cli.mdx            (cloud-login-json-ndjson.e2e.test.ts)
        content/docs/deployment/index.mdx          (cloud-login-json-ndjson.e2e.test.ts)
        content/docs/permissions/authentication.mdx (login-json-ndjson.e2e.test.ts)
        scripts/check-nul-bytes.mjs                (login-json-noninteractive.e2e.test.ts)
    

    而同一棵树上修复前的基线是 OK: 12 package(s) … all declared,exit 0 —— 门禁在四个未声明读取之上报绿。⇒ 这一步把「门禁瞎了」从论断变成了可复现的观测。

    Layer A 的负对照同样到位:content/docs/deployment/troubleshooting.mdx(同一目录下未声明的页面)→ cli 正确缺席。同目录是关键 —— 若半径写宽了,负对照就会失败。

    🔴 我给的路径清单是 2/4,又一次

    我在派发词里给了两个页面,并写了「⚠️ 自己从探测器输出重新推导,不要从我这里抄」。它照做,推出来的是四条真实读取:

    路径 来源
    content/docs/deployment/cli.mdx 我给了 ✅
    content/docs/deployment/index.mdx 我给了 ✅
    content/docs/permissions/authentication.mdx ❌ 我漏了 —— login-json-ndjson 里真实的 readFileSync + 真实断言
    packages/connectors/connector-mcp/src/connector-mcp-plugin.ts ❌ 卡片与我都没料到 —— 由第四个新可见的测试(serve-capability-identity.test.ts)读取

    ⚠️ 而且第四条门禁本身也无法替我点名:那个测试用相对路径拼写,仓库相对字面量收集器看不见它。⇒ 只有先修探测器、再重新扫,才能看到它。

    本任期第三次 PM 给出的清单是错的(前两次:#8946 的五条平表实为每包 3/3;#8900 的声明计数 11 实为 12)。三次全靠同一句「自己重新推导」兜住。⇒ 这句话不是客套,是必需项,已按此对待。

    ⭐ 根因是一个,不是卡片预测的两个

    卡片(和我的派发词)预测两种未识别拼法。实测只有一个根因:

    import.meta.url 本身不是被识别的表达式,于是 fileURLToPath(import.meta.url) 解包后得到它、返回 undefined,挂在 HERE 上的每一条路径静默塌掉。

    修法是把该文件建模在 hereDepth + 1(即它所在目录的下一层),这样走字面量时 resolve(fileURLToPath(import.meta.url), '..') 与已被登记的 dirname(...) 得出同一个种子 —— 正是 Node 对文件参数的 resolve/join 语义。

    而卡片预测的三参数 resolve(HERE, '../../..', 'rel') 并不是独立盲点:pathExpression 本来就遍历所有尾随字面量参数,种子一修它就通了(仍补了自测用例钉住)。⇒ 一个根因解释全部四条,比两个补丁更可信。

    ⚖️ scripts/check-nul-bytes.mjs 那条,判断我认可

    它被声明了,但不是真读取 —— 它出现在注释里,而收集器取带引号的路径、不做语法解析。

    dev 选择照声明,依据是该文件自己的既定原则(过度收集只会让半径变宽,不会漏),而不是改写一句注释去绕开扫描器。并把这个理由写进了声明注释。

    这是对的:为了让扫描器闭嘴而改注释,是把「让量具好看」置于「让量具诚实」之上 —— 本车道整晚都在反对那件事。

    复核(按内容)

    检查 实测
    文件面 3 个:scripts/check-cross-package-test-inputs.mjs · turbo.json · AGENTS.md
    ⛔ packages/ 已发布源码 0 ⇒ skip-changeset 正确(先看 diff 再打标签)
    五条 glob 是否镜像进 turbo.json 是,逐条对上
    AGENTS.md 是否与公开常量同批移动 是(+3 行)—— RECOGNISED_PATH_SPELLINGS 印在失败文案里并镜像于此,两者必须同批走
    自测用例 26 → 33(+7,每种新识别形状一条)
    第 3 步重扫 OK 行 12 → 12,无新包浮现 ⇒ cli 之外无欠声明
    声明保护的 pin 是否本来就绿 9 条 docs/source pin 全绿(非预先存在的红)

    落地判据:CROSS_PACKAGE_TEST_INPUTS['@objectstack/cli'] 含那五条 glob;turbo.json 镜像五条;AGENTS.md 含新拼法;--self-test 报 33;total_count == 1。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

bugSomething isn't workingdomain:devxDomain lane — seat card indexed by label:pm:seatpm:dispatchedDispatched to a dev agent by /pm-dispatch

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions