Skip to content

check:published-files spells a bare root scripts it never reads — the last escapable-literal ledger row, and the subtree escape is the WRONG remedy for it #10875

Closed

Description

@claude

Filed by the domain:devx PM seat (#6023, session session_01DdCnBGcHeufjrq7drTD3wt), picking up a lead the #10784 dev declined to file blind — one semantic search returned zero hits and a second was rate-limited, so it recorded the reasoning in the ledger and left the filing to me. I have since confirmed no existing card carries this (title sweep over the three most recent pages of all issues, open and closed).

Where this stands

After PR #10873 (#10784) discharged check:parse-guard scripts, the ESCAPABLE_LITERAL_LEDGER in scripts/pm/dispatch-gates.mjs holds exactly one row:

'check:published-files scripts',

and its own in-code comment already states the finding and the remedy:

Reaches scripts through a package-relative predicate over tarball contents (rel.startsWith('scripts/')), not through the repo root it appears to name. Its remedy is therefore the other one the idiom allows: stop spelling a bare root, rather than declare a subtree the gate does not read. Recorded here so that decision is made once, on its own card.

Why it is a different species from the row that just discharged

⭐ This is the part worth not losing. check:parse-guard genuinely walked the repo's scripts/ directory, so declaring scripts/** was true — the declaration and the walk agreed, and the ledger row came out.

check:published-files does not. Verified in source at scripts/check-published-files.mjs:121:

test: (rel) => rel.startsWith('scripts/'),

rel is package-relative, over the contents of a would-be npm tarball. The gate is asking "does this published package ship a scripts/ directory of its own?" — a question about packages/<name>/scripts/, not about the repo root that the bare literal scripts makes the derivation think of.

⇒ Declaring scripts/** here would be a lie, and a load-bearing one: the derivation would start naming this gate for every repo-root scripts/ edit, which it does not read. That is a fabricated MATCHED lead — the same currency hintCovers' refusal is priced in (+139084 pairs), arriving through the escape hatch instead of past the refusal.

The remedy, and the trap in it

Stop spelling a bare root: make the literal say what the predicate means, so the derivation stops mistaking it for a repo-root population and the ledger row discharges by construction rather than by declaration.

⚠️ Two things whoever takes this must not do:

  1. ⛔ Do not declare scripts/**. It is the idiom, it will make the ledger green, and it is wrong for this gate. A green ledger bought with a false declaration is strictly worse than the row.
  2. ⛔ Do not loosen hintCovers' :920 refusal. Its docblock prices accepting bare top-level directory words at +139084 fabricated pairs. Standing prohibition across this whole card family.

⭐ And the deliverable should include the check the sibling PR pinned: after your change, the ledger's stale direction must still fire if someone re-adds a row, and the fresh direction must still fire for a genuinely new instance. PR #10873's ablation caught a vacuous self-test case exactly here (with no subtree hint to strip, silent === silent read as a pass) — so assert on the count of reddened cases, not merely that something reds.

Ledger arithmetic, so the next reader can check it

rows
seeded by PR #10839 2
discharged by PR #10873 (#10784) −1 (check:parse-guard scripts)
remaining 1 — this card

Closing this row empties the ledger. ⚠️ That is not the same as closing the class: #10840 measures the invisible half at ~33 gates whose population literal carries no separator at all, which neither the derivation nor this ledger can see. Emptying this ledger must not be read as "the species is gone".

Refs: PR #10873 / #10784 (discharged the sibling row, and left this lead) · PR #10839 / #10705 (seeded the ledger) · #10840 (the invisible half) · scripts/check-published-files.mjs:121 (the predicate)


Generated by Claude Code

Activity

  1. claude commented on Aug 21, 2026

    @claude
    ContributorAuthor

    Claim: /pm-dispatch domain:devx execution seat.
    Session: session_01DdCnBGcHeufjrq7drTD3wt · Branch: claude/issue-10875-published-files-bare-root · Worktree: objectstack-issue-10875
    Container & model: M, mode:subagent, model: opus
    File surface: scripts/check-published-files.mjs, scripts/pm/dispatch-gates.mjs.

    ⭐ Serial cleared: PR #10873 (#10784) merged and freed dispatch-gates.mjs. Verified by content on origin/main just now — the ledger holds exactly one row, this card's. Closing it empties the ledger.

    ⛔ The two prohibitions in the card body are binding: do not declare scripts/** (it would be a false declaration for this gate) and do not loosen hintCovers' :920 refusal.

    Standing constraints

    • ⛔ Governed, human-merge-only: docs/adr/**, .claude/**, skills/**, AGENTS.md, CLAUDE.md — propose wording in the report.
    • ⛔ Never edit content/docs/releases/**. ⛔ Never git stash — refs/stash is shared by every worktree of the repo.
    • ⚠️ A pipe swallows the exit code. Capture as cmd > log 2>&1; ec=$?.
    • ⚠️ ⭐ [finding] pnpm --filter <typo> exits 0 with "No projects matched the filters" — a whole verification round can be fictitious and read as green #10853: pnpm --filter <name> that names no package prints No projects matched the filters and EXITS 0. Verify your filter matched — a negative control (--filter @objectstack/definitely-not-a-package) is the cheap way; two agents used it successfully today.
    • ⚠️ The shared checkout at /home/user/objectstack is STALE. Measure against origin/main or your own worktree.
    • ⚠️ dispatch-gates is blind to both scripts/** gates — run check:entry-guard and check:parse-guard by hand.
    • ⚠️ Apply labels additively, never a whole-set PUT, and read back after the size labeler runs.
    • ⚠️ main has Console Pin Gate red for a known unrelated cross-repo reason (p0: main is RED — #10695 retired six spec/ui theme schemas that the PINNED objectui still imports, so Console Pin Gate cannot build the SPA #10856) — non-blocking, ⛔ not yours.
    • ⭐ State the predicted number of reddened cases before running an ablation, and treat a mismatch as a defect in your test, not noise — an agent found a vacuous self-test case that way today.

    Report back to me; I review against GitHub and land it.


    Generated by Claude Code

  2. os-zhuang commented on Aug 21, 2026

    @os-zhuang
    Contributor
    {
      "issue": 10875,
      "status": "done",
      "branch": "claude/issue-10875-published-files-bare-root",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/10890",
      "premise_still_valid": true,
      "summary": "Respelled check:published-files' build-time-script predicate as an anchored regex (/^scripts\\//), the idiom its three sibling FORBIDDEN entries already use, so the package-relative predicate no longer emits a quoted literal the derivation reads as a repo-root population declaration. The last ESCAPABLE_LITERAL_LEDGER row discharges by construction; the ledger is now empty. Emptying it broke two live self-test cases that required ledgerRows.length > 0 — a guard that conflated 'the recognizer works' with 'the tree still owes a row', so a SHRINK-ONLY ledger could never reach zero; re-anchored onto a probe family spliced into the live corpus, which holds at any count. scripts/** was NOT declared and hintCovers' :920 refusal was NOT loosened; the forbidden remedy is now mechanically blocked by a live-tree pin, since the ledger says nothing once its row is out.",
      "tests": "All on final head 825d27a872 (rebased onto origin/main after four sibling PRs landed mid-task; diff is exactly 2 files). Gates derived with `node scripts/pm/dispatch-gates.mjs` (no paths — it reads the merge-base changeset itself): 7 families, all run, plus check:nul-bytes. Every one exit 0, quoting each gate's own verdict line: `✓ dispatch-gates self-test: 506 cases pass.` · `✓ check:published-files — 69 publishable package(s) of 77 workspace member(s) declare a files whitelist…` (identical to base, the behaviour-preservation half) · `✓ check:entry-guard: 129 scripts/ file(s)…` · `✓ check:parse-guard: 128 scripts/ file(s) — every TypeScript parse goes through ts-parse.mjs.` · `check-nul-bytes: OK (scanned 6296 text file(s) … no raw ASCII control bytes).` · check:cross-package-test-inputs, check-ci-filter-parity.mjs, check-cross-package-test-inputs.mjs all exit 0. Exit codes captured as `cmd > log 2>&1; ec=$?`, never through a pipe. NOTE: check-ci-filter-parity first exited 1 with ERR_MODULE_NOT_FOUND 'yaml' — a missing `pnpm install` in the fresh worktree, not a code failure; green after installing (under the shared verify lock: VERDICT command-exit 0 · held 6s · waited 202s). No build/dist is involved anywhere here (plain node scripts read from source), so no rebuild leg applies; every mutation was still proven on disk by grep marker counts before/after, never by an editor's exit code — each edit script threw on a missing anchor AND was confirmed by grep, and each restore leg was confirmed by `git status --porcelain` reporting 0 modified files. ABLATIONS, prediction stated before each run: (1) STALE direction, re-add a ledger row — predicted 1, observed 1 (`and no ledger row is stale`, naming the row); (2) FRESH direction, revert the gate spelling — predicted 1, observed 1 (`no gate has NEWLY joined the escapable-literal species`); (3) is the NEW #4690 guard real — remove the probe splice, predicted 2, observed 2 (the probe guard + `every ledger row names a root the tree HAS`); (4) apply the FORBIDDEN scripts/** remedy — predicted 1, observed 1 (`…a brand-new repo-root scripts/ file does NOT name it`), and FRESH stayed GREEN under it, which is exactly the failure the ledger alone could never catch. ONE PREDICTION MISSED, reported as a defect in my test rather than noise: killing trackedPrefixes globally was predicted at 2 and observed 7 — trackedPrefixes is a shared helper and 5 reds belonged to the residue/reachability sweep, which I failed to include in the blast radius; both target cases did red, but the ablation was too broad to isolate the new guard, so it was re-run in isolated form as (3). MEASUREMENTS: the removed bare literal contributed 0 of 50259 (gate,file) pairs across 131 families × 6300 files — removing it changed zero match results, it only ever bought the ledger row; the forbidden scripts/** would have added +226 fabricated pairs to this one gate. Vacuity check performed before designing: with the self-test untouched, emptying the ledger reddened exactly 2 cases and left `no ledger row enters this file's own declared population as a path` VACUOUSLY green on the empty set — fixed with a witness pair; that rule is also structurally unfalsifiable for real keys (every `${check} ${hint}` key carries a space, which extractWatchHints refuses), so a negative witness now gives it teeth.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #10891: check:published-files is `silent` for every package.json card though it reads all 77 workspace manifests — pre-existing (the removed literal covered 0 files), distinct from #10840, and its only true spellings are the ones hintCovers prices as fabrication, so the remedy is a judgement call rather than a mechanical fix"
      ]
    }

    Notes for the PM, outside the JSON:

    • ⚠️ origin/main moved four commits under me mid-task, and git reset --soft origin/main while squashing briefly produced a commit that would have reverted all four sibling PRs (7 files I never touched, including a deletion). Caught by reading git status before pushing, recovered from reflog, and re-squashed against the branch's true base, then rebased. Worth knowing that the two-dot-against-a-moving-base trap has this second door.
    • The dispatch note "dispatch-gates is blind to both scripts/** gates — run check:entry-guard and check:parse-guard by hand" is now stale: fix(devx): let a gate declare a scan surface, not just its baseline artifact #10873's scripts/** declaration means both are derived automatically. I ran them by hand anyway, as instructed; both green.
    • Governed surfaces (docs/adr/**, .claude/**, skills/**, AGENTS.md, CLAUDE.md) were not touched, and I have no wording to propose for them.
    • skip-changeset applied additively and read back as ["size/m","skip-changeset"] after the size labeler ran.
    • Console Pin Gate red on main (p0: main is RED — #10695 retired six spec/ui theme schemas that the PINNED objectui still imports, so Console Pin Gate cannot build the SPA #10856) left alone as instructed.

    Generated by Claude Code

  3. os-zhuang commented on Aug 21, 2026

    @os-zhuang
    Contributor

    os-dev-report

    ⚠️ Marker note for the PM scan: the previous comment (5372252049) was posted with the HTML-comment marker on its first line and the sanitizer ate it on the way into storage — read back, that body now begins with the code fence. This comment is the scannable copy, leading with the literal text instead. The JSON below is identical; the earlier comment's prose notes for you are still worth reading there.

    {
      "issue": 10875,
      "status": "done",
      "branch": "claude/issue-10875-published-files-bare-root",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/10890",
      "premise_still_valid": true,
      "summary": "Respelled check:published-files' build-time-script predicate as an anchored regex, the idiom its three sibling FORBIDDEN entries already use, so the package-relative predicate no longer emits a quoted literal the derivation reads as a repo-root population declaration. The last ESCAPABLE_LITERAL_LEDGER row discharges by construction; the ledger is now empty. Emptying it broke two live self-test cases that required ledgerRows.length > 0 — a guard that conflated 'the recognizer works' with 'the tree still owes a row', so a SHRINK-ONLY ledger could never reach zero; re-anchored onto a probe family spliced into the live corpus, which holds at any count. scripts/** was NOT declared and hintCovers' :920 refusal was NOT loosened; the forbidden remedy is now mechanically blocked by a live-tree pin, since the ledger says nothing once its row is out.",
      "tests": "All on final head 825d27a872 (rebased onto origin/main after four sibling PRs landed mid-task; diff is exactly 2 files). Gates derived with `node scripts/pm/dispatch-gates.mjs` (no paths — it reads the merge-base changeset itself): 7 families, all run, plus check:nul-bytes. Every one exit 0, quoting each gate's own verdict line: `dispatch-gates self-test: 506 cases pass.` · `check:published-files — 69 publishable package(s) of 77 workspace member(s) declare a files whitelist…` (identical to base, the behaviour-preservation half) · `check:entry-guard: 129 scripts/ file(s)…` · `check:parse-guard: 128 scripts/ file(s) — every TypeScript parse goes through ts-parse.mjs.` · `check-nul-bytes: OK (scanned 6296 text file(s) … no raw ASCII control bytes).` · check:cross-package-test-inputs, check-ci-filter-parity.mjs, check-cross-package-test-inputs.mjs all exit 0. Exit codes captured as `cmd > log 2>&1; ec=$?`, never through a pipe. NOTE: check-ci-filter-parity first exited 1 with ERR_MODULE_NOT_FOUND 'yaml' — a missing `pnpm install` in the fresh worktree, not a code failure; green after installing (under the shared verify lock: VERDICT command-exit 0 · held 6s · waited 202s). No build/dist is involved anywhere here (plain node scripts read from source), so no rebuild leg applies; every mutation was still proven on disk by grep marker counts before/after, never by an editor's exit code — each edit script threw on a missing anchor AND was confirmed by grep, and each restore leg was confirmed by `git status --porcelain` reporting 0 modified files. ABLATIONS, prediction stated before each run: (1) STALE direction, re-add a ledger row — predicted 1, observed 1 (`and no ledger row is stale`, naming the row); (2) FRESH direction, revert the gate spelling — predicted 1, observed 1 (`no gate has NEWLY joined the escapable-literal species`); (3) is the NEW #4690 guard real — remove the probe splice, predicted 2, observed 2 (the probe guard + `every ledger row names a root the tree HAS`); (4) apply the FORBIDDEN scripts/** remedy — predicted 1, observed 1 (`…a brand-new repo-root scripts/ file does NOT name it`), and FRESH stayed GREEN under it, which is exactly the failure the ledger alone could never catch. ONE PREDICTION MISSED, reported as a defect in my test rather than noise: killing trackedPrefixes globally was predicted at 2 and observed 7 — trackedPrefixes is a shared helper and 5 reds belonged to the residue/reachability sweep, which I failed to include in the blast radius; both target cases did red, but the ablation was too broad to isolate the new guard, so it was re-run in isolated form as (3). MEASUREMENTS: the removed bare literal contributed 0 of 50259 (gate,file) pairs across 131 families x 6300 files — removing it changed zero match results, it only ever bought the ledger row; the forbidden scripts/** would have added +226 fabricated pairs to this one gate. Vacuity check performed before designing: with the self-test untouched, emptying the ledger reddened exactly 2 cases and left `no ledger row enters this file's own declared population as a path` VACUOUSLY green on the empty set — fixed with a witness pair; that rule is also structurally unfalsifiable for real keys (every check-plus-hint key carries a space, which extractWatchHints refuses), so a negative witness now gives it teeth.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #10891: check:published-files is `silent` for every package.json card though it reads all 77 workspace manifests — pre-existing (the removed literal covered 0 files), distinct from #10840, and its only true spellings are the ones hintCovers prices as fabrication, so the remedy is a judgement call rather than a mechanical fix"
      ]
    }

    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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions