Skip to content

Burn down the 35 scripts/ modules that run on import — the KNOWN_IMPORT_UNSAFE ledger from PR #10665 #10667

Description

@claude

Parent: #10631. Split out from PR #10665, which landed the rule and left this half deliberately undone.

PR #10665 taught check:entry-guard a second rule — a scripts/** file that exports a binding
must have every top-level statement that runs something behind the guard — and recorded the files
that already violate it in a ⛔ SHRINK-ONLY KNOWN_IMPORT_UNSAFE list so the gate is green on
the tree as it stands. Nothing new can join the list; these 35 are the standing debt.

The burn-down was kept out of that PR because its claim declared scripts/check-entry-guard.mjs,
and 35 gate scripts is the file surface other seats work in. It is safe to do in any number of
sittings: each file is independent, and the gate itself tells you when a line is ready to go.

Why each one matters

Measured by importing every exporting scripts/ file in a child process for its exports alone:
of the 39 unguarded exporters, 8 ended the importer mid-import and five of those exit 0, and 14
more ran the whole tool loudly. Three further leaks live in files that do carry a guard and leak
conditionally on the importer's argv (qa/qa-rollup.mjs, objectui-range.mjs) or on an env var
(ts-parse.mjs) — invisible to any clean-argv probe. Full table in PR #10665.

The remedy is mechanical, and needs no re-indentation

Five files here already use the inverted idiom, which wraps a trailing dispatch chain with a 4-line
insert and zero re-indentation of the existing branches:

import { isEntrypoint } from './invoked-as.mjs';   // '../invoked-as.mjs' from a subdir

const invokedDirectly = isEntrypoint(import.meta.url);

if (!invokedDirectly) {
  // imported as a module — expose the exports and do nothing else
} else if (process.argv.includes('--self-test')) {
  ...

The plain form if (isEntrypoint(import.meta.url)) { ... } is equally accepted, as is storing the
predicate in a const under any name.

Roughly five of the 35 have their whole gate body at top level rather than a trailing dispatch
chain (check-error-status-conformance.mjs, checklist-select.mjs,
check-query-options-erasure-ratchet.mjs, check-release-page-status.mjs,
pm/check-governed-prose.mjs) and need a real main() extraction rather than a wrap. Those are
worth their own sitting.

⚠️ pm/check-governed-prose.mjs additionally mutates process.argv at module scope to filter
--self-test out before importing a sibling. That workaround is a scar from exactly this class and
looks stale now that pm/check-governed-merges.mjs carries the guard — worth checking rather than
preserving.

Per file: verify, then delete its line

For each one: add the guard, then node scripts/check-entry-guard.mjs — it fails with the file
named as a STALE KNOWN_IMPORT_UNSAFE entry, which is the signal to delete that line. Confirm
with an import probe (the importer must survive silently) and by running the file as a CLI
unchanged. Demonstrated end to end on check-nul-bytes.mjs in PR #10665.

The 35

  • scripts/ablation-dist-preflight.mjs
  • scripts/check-changeset-no-major.mjs
  • scripts/check-dispatcher-error-vocabulary.mjs
  • scripts/check-driver-memory-census.mjs
  • scripts/check-empty-changeset.mjs
  • scripts/check-engine-split-ratio.mjs
  • scripts/check-error-code-casing.mjs
  • scripts/check-error-status-conformance.mjs
  • scripts/check-examples-live-imports.mjs
  • scripts/check-filter-alias-parity.mjs
  • scripts/check-nul-bytes.mjs
  • scripts/check-org-identifier.mjs
  • scripts/check-query-options-erasure-ratchet.mjs
  • scripts/check-quick-reference-counts.mjs
  • scripts/check-ratchet-remedy-authority.mjs
  • scripts/check-release-page-status.mjs
  • scripts/check-required-contexts.mjs
  • scripts/check-route-envelope.mjs
  • scripts/check-runtime-services-index.mjs
  • scripts/check-shard-attestation.mjs
  • scripts/check-spec-parsed-alias.mjs
  • scripts/check-startup-registry-verdict.mjs
  • scripts/check-tenant-chokepoint.mjs
  • scripts/check-test-completeness.mjs
  • scripts/check-workflow-status-functions.mjs
  • scripts/checklist-select.mjs
  • scripts/docs-audit/check-audit-scope.mjs
  • scripts/measure-test-shard-timings.mjs
  • scripts/objectui-range.mjs
  • scripts/pm/check-governed-prose.mjs
  • scripts/pm/check-label-desc-cap.mjs
  • scripts/pm/check-skill-id-lint.mjs
  • scripts/pm/check-skill-line-ratchet.mjs
  • scripts/qa/qa-rollup.mjs
  • scripts/ts-parse.mjs

node scripts/check-entry-guard.mjs --list prints this live, with the offending line in each file.


Generated by Claude Code

Activity

  1. claude commented on Aug 21, 2026

    @claude
    ContributorAuthor

    Claim — PM dispatch (devx seat), sitting 1 of N

    • Session: c970724d-303c-5614-9d20-a3f92205cfad
    • Branch: claude/issue-10667-entry-guard-burndown-1

    Ledger re-read on origin/main = dd8172ee22 (fetched 2026-08-21T08:38:37Z), not adopted
    from this card: KNOWN_IMPORT_UNSAFE still holds 35 entries, and the list matches the
    card's exactly.

    One correction worth recording: the card says the remedy was "demonstrated end to end on
    check-nul-bytes.mjs in PR #10665"
    , which reads as if that file were already cleared.
    It is still on the ledger at the current tip — #10665 demonstrated the method on it,
    not the removal. So this is 35 to burn down, not 34.

    Scope of this sitting: the mechanical wrap only. The agent is told to stop and leave a
    file on the ledger, rather than force it, whenever the remedy is more than the 4-line
    isEntrypoint insert. That deliberately excludes:

    • the five the card flags as needing a real main() extraction —
      check-error-status-conformance.mjs, checklist-select.mjs,
      check-query-options-erasure-ratchet.mjs, check-release-page-status.mjs,
      pm/check-governed-prose.mjs (which additionally mutates process.argv at module scope;
      the card is right that this wants checking rather than preserving);
    • the three that leak conditionally on the importer's argv or env — qa/qa-rollup.mjs,
      objectui-range.mjs, ts-parse.mjs. These are invisible to a clean-argv probe, so the
      usual import probe cannot confirm them, and they need their own sitting with a probe built
      for that. ts-parse.mjs is additionally live in lint.yml:189.

    The ledger is ⛔ SHRINK-ONLY, so a partial burn-down is safe by construction and the gate
    itself names each line that has become stale.

    No collision with this seat's four in-flight PRs — checked each against the ledger:
    check-cross-package-test-inputs.mjs (#10566), check-platform-checklist.mjs (#10682),
    pm/dispatch-gates.mjs (#10664) and docs-audit/affected-docs.mjs (#10674) are none of
    them on it.


    Generated by Claude Code

  2. claude commented on Aug 21, 2026

    @claude
    ContributorAuthor
    {
      "issue": 10667,
      "status": "done",
      "branch": "claude/issue-10667-entry-guard-burndown-1",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/10704",
      "premise_still_valid": true,
      "summary": "Sitting 1 of N: KNOWN_IMPORT_UNSAFE burned down from 35 to 10 — 25 scripts/ files guarded, one commit per file, ledger line deleted only after the gate named that file STALE. 18 took the inverted 4-line insert with zero re-indentation; 7 took the plain if (isEntrypoint(import.meta.url)) { ... } wrap. Two exceeded the strict 4-line insert and are named rather than smuggled: check-tenant-chokepoint.mjs has TWO top-level runners (the --self-test branch took the inverted insert; the separate trailing main(); became `if (invokedDirectly) main();` — nothing moved, and folding it into an else was avoided deliberately because that would make control flow depend on the process.exit(0) inside the self-test branch), and docs-audit/check-audit-scope.mjs dispatches from a mid-file top-level try/catch, so the plain wrap re-indents 11 lines by two spaces (the zero-re-indent alternative is `} else try {`, valid JS but too ugly to survive review). ⚠️ CORRECTION TO THE BRIEF, and the reason this is 25 and not 27: TWO of the briefed 27 candidates sit inside the #9465 epic's DECLARED FILE TERRITORY — scripts/check-changeset-no-major.mjs and scripts/check-empty-changeset.mjs are named explicitly in that epic's territory list, alongside .changeset/** and the workflows the brief already fenced. The brief fenced 'changeset scripts' but did not cross-reference which of the 35 they were. check-changeset-no-major.mjs was implemented and verified fully green before I read the territory list, then git reset --hard'd off the branch; it is back on the ledger untouched and the gate is green. A future sitting must not take those two until #9465 lands or the epic PM hands them over. The other 8 left on the ledger are exactly the 8 the brief excluded.",
      "tests": "HEAD = e1153da230 (final commit; the gate union below was re-derived and run AT that sha, working tree clean). PER FILE, never batched, all four steps: (1) MUTATION IS REAL — every edit printed its isEntrypoint anchor count read back off disk (0->2, and 1->3 for check-examples-live-imports.mjs whose prose already mentioned the helper) plus the byte delta; the tool refuses with 'VOID MUTATION' rather than exiting 0 on a no-op. Edits were spliced at offsets from a real TypeScript parse (ts.createSourceFile) of each file, not by regex — several of these files carry prose and help text with `import` at column 0. (2) IMPORT PROBE, BOTH SIDES — a child process imports the file for its exports alone with clean argv (target arrives by env, so nothing looks like a flag). BEFORE: all 25 leaked, e.g. check-skill-id-lint printed '✓ check-skill-id-lint: 17 file(s) clean' INSIDE the importer; check-tenant-chokepoint printed its full 20-binding verdict. AFTER: every one of the 25 emits only the probe sentinel line (PROBE_EXPORTS=...), empty stderr, exit 0. (3) CLI UNCHANGED — each file run twice before and twice after (default path AND --self-test); stdout, stderr and exit code compared byte-for-byte with cmp on all four legs: IDENTICAL for all 25, 100/100 comparisons. (4) GATE — node scripts/check-entry-guard.mjs run after each edit and required to name that file as STALE before its ledger line was deleted, then re-run green; ledger printed at every step 35->34->...->10. GATE UNION: node scripts/pm/dispatch-gates.mjs with NO paths (26-path change set derived from merge base dd8172ee2, three-dot). All 24 named families exit=0, quoting their own verdict lines, not a bare $? (exit codes captured by redirecting to a file first): check:entry-guard -> '✓ check:entry-guard: 123 scripts/ file(s) ... 81 export bindings, 71 of them inert on import (10 known-unsafe, ⛔ SHRINK-ONLY).' ; check:parse-guard -> '✓ check:parse-guard: 122 scripts/ file(s) — every TypeScript parse goes through ts-parse.mjs.' ; check:nul-bytes -> 'check-nul-bytes: OK (scanned 6215 text file(s) ... no raw ASCII control bytes).' ; plus check:cross-package-test-inputs, check:dispatcher-error-vocabulary, check:docs-audit-scope, check:driver-memory-census, check:error-code-casing, check:examples-live-imports, check:filter-alias-parity, check:org-identifier, check:pm-label-desc-cap, check:pm-skill-id-lint, check:pm-skill-ratchet, check:quick-reference-counts, check:ratchet-remedy-authority, check:required-contexts, check:route-envelope, check:runtime-services-index, check:shard-attestation, check:spec-parsed-alias, check:startup-registry-verdict, check:tenant-chokepoint, check:workflow-status-functions — all exit=0. node scripts/docs-audit/check-affected-docs.mjs -> exit 0. ESLint over the 26 changed files with --no-inline-config -> exit 0, no output. GATES I RAN BY REASONING, NOT BECAUSE THE TOOL NAMED THEM (the derivation cannot see import edges and this diff is entirely import edges): check:parse-guard, which the derivation marks 'unreachable BY CONSTRUCTION' because its declared population is the bare literal 'scripts' (refused as too generic) — yet CI runs it on every PR and it parses every file I touched; green. I also swept the INBOUND edge myself: grepped for from/import()/require() of each of the 25 basenames across scripts/, packages/, tools/, examples/ — ZERO importers today, so no consumer could have depended on the import side effect this removes. The one NEW edge is 25 scripts importing scripts/invoked-as.mjs, which is itself guarded and inert on import; pm/dispatch-gates.mjs and pm/check-governed-merges.mjs already reach it as '../invoked-as.mjs', so the subdirectory spelling has precedent. NO ABLATION WAS RUN and none is owed: steps 2 and 3 are the before/after measurement on the real file, which is a stronger demonstration than a mutated copy. No build was involved anywhere — these are plain .mjs run against source, so there is no dist/ that could serve a stale tree. ONE PRE-EXISTING RED, NOT MINE: node scripts/check-engine-split-ratio.mjs exits 2 in this container with a shallow-clone refusal (it prints its own 'fetch --unshallow' remedy). Exit 2 and byte-identical output BEFORE and AFTER my edit, and its --self-test is green on both sides — an environment condition the script is designed to refuse on, not a regression. Also environment-shaped: check-test-completeness.mjs with no args prints usage and exits 1 both before and after (CI passes it a turbo log).",
      "open_questions": [
        {
          "question": "Who takes scripts/check-changeset-no-major.mjs and scripts/check-empty-changeset.mjs? Both are on the #10667 checklist and both sit inside the #9465 epic's declared file territory, so no burn-down sitting can touch them without crossing the fence.",
          "options": [
            "A — leave both on the ledger until #9465 lands, then dispatch them as a short follow-up sitting; the ledger is SHRINK-ONLY so nothing rots meanwhile",
            "B — hand the two guards to the #9465 epic seat to carry inside its own PR, since it is already rewriting both files for the v3 migration",
            "C — ask the epic PM for a narrow release of just these two files to a burn-down sitting"
          ],
          "recommendation": "B, because the epic is already restructuring both files (the pre/RC exemption logic in check-changeset-no-major.mjs is one of the things v3 is meant to delete), so a separate PR touching the same tails would conflict for no gain — and the guard is a 4-line insert the epic seat can add while it has the file open. A is the safe fallback if the epic seat declines; C costs a round trip to reach the same place as A or B."
        },
        {
          "question": "Was exceeding the strict 4-line-insert bound on the two shaped files (check-tenant-chokepoint.mjs, docs-audit/check-audit-scope.mjs) the right call? Both are outside the brief's eight exclusions and neither needs a main() extraction, but neither is a pure 4-line insert either.",
          "options": [
            "A — keep both; each is one commit and fully verified byte-identical on both CLI paths",
            "B — drop the two commits and put both files back on the SHRINK-ONLY ledger for a sitting with a wider bound"
          ],
          "recommendation": "A, because the card documents `if (isEntrypoint(import.meta.url)) { ... }` as an equally accepted idiom — zero re-indentation is a property of the inverted form, not a requirement of the rule — and neither file needed the main() extraction that defines the brief's real exclusion. Each is its own commit, so B costs exactly two `git revert`s if you disagree."
        }
      ],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  3. claude commented on Aug 21, 2026

    @claude
    ContributorAuthor

    Sitting 1 landed as PR #10704: KNOWN_IMPORT_UNSAFE 35 → 10. Verified on the PR head, not taken from the report.

    25 files guarded — 18 by the inverted 4-line insert, 7 by the plain if (isEntrypoint(import.meta.url)) { ... } wrap. Each one verified in four steps before its ledger line was deleted: anchor count read back off disk, import probe on both sides (all 25 leaked before; only the probe sentinel after), CLI output cmp'd byte-for-byte across default and --self-test (100/100 identical), and the gate required to name the file STALE before the line went.

    ⛔ Constraint for future sittings — two files are inside the #9465 fence

    scripts/check-changeset-no-major.mjs and scripts/check-empty-changeset.mjs are named in the #9465 epic's declared file territory. My dispatch brief fenced "changeset scripts" without cross-referencing which of the 35 they were, and the dev discovered it only after implementing one of them and verifying it green — then correctly reset that work off the branch. Both are untouched by PR #10704 and remain on the ledger.

    No sitting may take those two until #9465 lands or the epic seat hands them over. I have posted the 4-line insert to #9465 as an offer they can take while those files are already open, or decline with no reply needed.

    Remaining 10 on the ledger

    The two above, plus the eight the sitting-1 brief deliberately excluded:

    • needing a real main() extraction — check-error-status-conformance.mjs, checklist-select.mjs, check-query-options-erasure-ratchet.mjs, check-release-page-status.mjs, pm/check-governed-prose.mjs (which additionally mutates process.argv at module scope — worth checking rather than preserving);
    • leaking conditionally on the importer's argv or env, so a clean-argv probe cannot see them — qa/qa-rollup.mjs, objectui-range.mjs, ts-parse.mjs (also live at lint.yml:189). These need a probe built for that, not the one sitting 1 used.

    Two files exceeded the strict 4-line bound and were kept after review — check-tenant-chokepoint.mjs (two top-level runners; folding the trailing main(); into an else would make control flow depend on the process.exit(0) inside the self-test branch) and docs-audit/check-audit-scope.mjs (mid-file top-level try/catch, so the wrap re-indents 11 lines). Each is its own commit.

    Card stays open for sitting 2.


    Generated by Claude Code

  4. removed their assignment
    on Aug 23, 2026
  5. os-zhuang commented on Aug 23, 2026

    @os-zhuang
    Contributor

    清 assignee — #11196 维护者裁决 2026-08-23「A:本席清 agent 残留」,skills 席 session 757f0dc0-5dae-51d7-9053-c15c57ebfe3b。证据:sitting 1 已以 PR #10704 合并收束(35→10),卡按 burndown 设计回到 pm:queue 等下一个 sitting——assignee 是 sitting-1 认领(devx 席 session c970724d)的残留。标签未动;sitting 2 的认领席重新 assign + 声明即可。


    Generated by Claude Code

  6. 17 remaining items

  7. huangyiirene commented on Sep 2, 2026

    @huangyiirene
    Collaborator

    Unlock scan (R+89, triage seat, session session_019kDRpB7D2XzVzkaLp57T5D): the recorded blocker #9465 is CLOSED (completed). Card face on origin/main (a39b02a6): scripts/check-entry-guard.mjs still carries the KNOWN_IMPORT_UNSAFE set (line 460) — a quick shape-count of quoted entries in that block reads 2, against the 35 the 2026-08-21 sitting-1 claim measured. ⚠️ Treat that count as a lead, not a reading: the block's entry shape may not be one-quoted-string-per-line. The dispatch re-reads the ledger at its base; if it really is down to a handful, this is one short sitting away from closing.

    Released → pm:queue · priority:p3 · domain:devx · type Task (multi-sitting burn-down; each file independent, the gate itself says when a line may go).


    Generated by Claude Code

  8. added theissue type on Sep 2, 2026
  9. claude commented on Sep 2, 2026

    @claude
    ContributorAuthor

    Claim: session session_01WLJQhde67SeTccsmnBVarV (domain:devx execution seat, seat post #6023) — R1 wave 8, sitting 4 (the final sitting: the ledger holds exactly the two entries the #9465 fence held back).

    • Branch: claude/issue-10667-entry-guard-ledger-sitting-4
    • Worktree: ../objectstack-10667 (dedicated per-task worktree, off origin/main)
    • Domain: domain:devx per the unlock scan (comment 5503053245): scripts/ gate debt; check:entry-guard is the arbiter.
    • File surface: scripts/check-changeset-no-major.mjs, scripts/check-empty-changeset.mjs (guard their run-on-import top-level statements behind isEntrypoint(import.meta.url), zero behaviour change when invoked directly), and scripts/check-entry-guard.mjs ONLY to remove the two now-stale KNOWN_IMPORT_UNSAFE entries (shrink-only ledger; the gate fails STALE until they go). ⛔ No other script, no .changeset/** content, no .github/**. Sitting-3's ts-parse.mjs lesson applies: a guard that would arm a census on the one run with nothing to count is an inversion, not a burn-down — read what each top-level statement does before guarding it.
    • Premise re-verified on origin/main 7085f9053: scripts/check-entry-guard.mjs:460-463 KNOWN_IMPORT_UNSAFE = exactly { scripts/check-changeset-no-major.mjs, scripts/check-empty-changeset.mjs }; the recorded blocker Migrate the release toolchain to @changesets/cli v3 — one atomic PR carrying the bump, the pre-mode restructure, and the gates that model v2's semantics #9465 (changesets v3 epic, whose file territory fenced both scripts) is CLOSED (completed). No open PR (27 open at 13:5xZ) touches any of the three files. Premise holds; this sitting can close the card (Fixes #10667 — not Part of — if the ledger reaches zero and check:entry-guard is green).
    • Container & model: subagent lane (S/M mechanical with a known inversion trap), opus.
    • Clause ②: not reached — import-safety of two gate scripts; their CLI behaviour is unchanged.
    • Serial constraints: none. Changeset: scripts/**-only ⇒ skip-changeset label on the PR.

    Generated by Claude Code

  10. claude commented on Sep 2, 2026

    @claude
    ContributorAuthor

    os-dev-report

    {
      "issue": 10667,
      "status": "needs_decision",
      "branch": "claude/issue-10667-entry-guard-ledger-sitting-4",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/14593",
      "premise_still_valid": true,
      "summary": "Sitting 4 implemented in full: KNOWN_IMPORT_UNSAFE burned 2 to 0, and check:entry-guard is green with an empty set (199 scripts/ files, 150 export bindings, 150 of them inert on import, 0 known-unsafe). Both targets had exactly one offending top-level statement each (a trailing --self-test / --list / main dispatch chain), so both took the inverted 4-line insert with zero re-indentation; check-entry-guard.mjs is touched only to delete the two ledger lines, and its SHRINK-ONLY doc comment is untouched because that comment is what keeps an empty set empty. The sitting-3 ts-parse.mjs inversion check was applied and CLEARS: nothing in the repo imports either file -- every reference in .github/**, package.json and scripts/** spawns them as a child process, and a repo-wide grep for an import of either module returns nothing -- so neither guard silences a census. Measured leaks, both directions: importing check-changeset-no-major.mjs ran the whole gate inside the importer and then main()'s trailing process.exit(exitCode) ENDED the importer mid-import carrying status 0, with the probe SENTINEL never printed -- the silent-success shape sitting 2 flagged as the worst in this class; importing check-empty-changeset.mjs wrote the gate's verdict to the importer's stdout. After: both probes return their exports silently at exit 0 with no foreign output. ⚠️ WHY needs_decision AND NOT done: emptying the ledger turns one case in scripts/pm/dispatch-gates.mjs RED, and that file is outside this card's declared three-file surface. The card is one PM decision from closed; the PR is deliberately 'Part of', not 'Fixes', so the merge cannot close the card while that decision is open.",
      "tests": "Union derived LIVE at the final commit 54b433adb with a clean working tree: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands -- provenance line names objectstack-ai/objectstack at 54b433adb, change set 3 paths vs merge base 7085f9053 (committed 3, working tree 0, untracked 0). 18 families (16 by path + 2 by change KIND, the convention block hitting because this diff edits GATE SCRIPTS -- the block a matched-only harvest would have dropped). Exit codes captured by redirect BEFORE any pipe, never through tail/head. 16 RAN-PASS (exit 0): pnpm check:agent-test-spelling; pnpm check:bash32-floor; pnpm check:changeset-gate-self-tests; pnpm check:cli-command-ids; pnpm check:cross-package-test-inputs; pnpm check:entry-guard ('✓ check:entry-guard: 199 scripts/ file(s) -- every entry guard goes through invoked-as.mjs; 150 export bindings, 150 of them inert on import (0 known-unsafe, SHRINK-ONLY)'); pnpm check:parse-guard; pnpm check:pnpm-filter-targets; pnpm check:ratchet-remedy-authority; pnpm check:watch-hint-literal; node scripts/check-changeset-no-major.mjs; node scripts/check-ci-filter-parity.mjs; node scripts/check-cross-package-test-inputs.mjs; node scripts/check-empty-changeset.mjs; node scripts/check-shard-attestation.mjs; node scripts/pm/bare-root-worklist.mjs --self-test (the ledger-row obligation the convention block names -- green, no row owed). 1 NOT MEASURED: node scripts/check-test-completeness.mjs exit 3, its own PREREQUISITE NOT MET branch ('this gate grades a saved turbo run test log, and no log was named ... the local reading for this gate is NOT MEASURED ... It is not a red'); the family names it with no argument and CI, which tees the log and passes the path, never reaches that branch. 1 RAN-FAIL: pnpm check:pm-dispatch-gates exit 1, run through scripts/pm/os-verify-lock.sh (VERDICT command-exit 1, held the lock 850s, waited 247s) -- '✗ dispatch-gates self-test: 1 of 1240 case(s) failed', the single case being the one in open_questions. check-entry-guard's OWN self-test is green at 54 cases, including the :800 scan-surface assertions which hold on an empty set. Control-character self-scan over all three edited files: clean. BEHAVIOUR PROOF, zero change when invoked directly: each script run the way pr-automation.yml / cut-rc.yml / package.json run it, same tree, before and after, captured to files and compared with cmp -- default, --self-test and --list all byte-IDENTICAL for both scripts, exit 0 throughout (116 and 118 self-test assertions respectively). MUTATION PROOF: every edit re-read off disk after writing -- anchor counts (isEntrypoint 0 -> 3 in each script: import, prose, call) and byte deltas (+1048, +926), and the ledger deletion verified by reading back 0 occurrences of each deleted literal plus 'new Set([])' present and 'SHRINK-ONLY' still present. LEDGER DISCIPLINE: the two entries were deleted ONLY after the gate named them STALE ('❌ check:entry-guard -- 2 stale KNOWN_IMPORT_UNSAFE entry/entries'), never in the same breath as the guard.",
      "mcp_calls": "0 -- all GitHub reads and writes went through REST with $GITHUB_TOKEN and the CA bundle, per the dispatch; no MCP GitHub call was made.",
      "open_questions": [
        {
          "question": "Emptying KNOWN_IMPORT_UNSAFE breaks one pin in scripts/pm/dispatch-gates.mjs (the #10784 ablation at :12093, 'strip the subtree declaration and check:entry-guard goes back to silent'). It strips the subtree hint and asserts the residual verdict is exactly 'silent'. Measured through the tool's own exported discoverFamilies/classifyEntry on this tree: hints NOW are [scripts/**] only, verdict matched; after the strip the hints are [] and the verdict is 'undetermined'. Simulating the PRE-CHANGE tree, the strip leaves [scripts/check-changeset-no-major.mjs, scripts/check-empty-changeset.mjs] and the verdict is 'silent'. So the ablation was passing only because the ledger still held two path literals, and this card's whole purpose removes them. Unreachable in sittings 1-3: only the sitting that empties the set can hit it. May a fourth file be edited to close the card, and with which remedy?",
          "options": [
            "A -- widen the pin's expected verdict at dispatch-gates.mjs:12093 from exactly 'silent' to not-matched (verdict !== 'matched', or 'silent' or 'undetermined'), keeping the existing undeclared.hints.length < entry.hints.length guard that stops it passing vacuously. One line, in a fourth file outside this card's declared surface. No open PR holds that file (checked against all 27 open PRs).",
            "B -- keep one path literal in check-entry-guard.mjs so the pin's precondition survives. ⛔ Rejected as harmful: it re-creates the exact #10784 blind spot the pin exists to prevent, and dispatch-gates.mjs itself warns that declaring a root the gate does not really read is the costlier error.",
            "C -- land this PR as-is with a red check:pm-dispatch-gates and fix the pin in a separate card. Keeps the file surface honest but leaves main red on merge, so it is only viable if the pin card lands first or together.",
            "D -- leave one entry on the ledger so the pin stays green. ⛔ Rejected: it keeps real debt alive to satisfy a miscalibrated assertion, and the ledger is SHRINK-ONLY by design."
          ],
          "recommendation": "A, applied in THIS PR if the PM authorizes the fourth file -- otherwise A as an immediate follow-up that merges with or before this one. The ablation's intent is fully intact under A: it exists to prove the subtree declaration is the load-bearing half, and both 'silent' and 'undetermined' mean not-matched, which is all that proof needs. What is now wrong is only the spelling of the expected verdict, which was incidentally exact while the roster was non-empty and became wrong the moment the roster emptied -- a pin calibrated against a debt ledger that was always going to reach zero. I did not take it myself because the dispatch fences this card to three files with an explicit non-negotiable, and re-spelling a governance-grade pin in the PM's own dispatch tooling is a judgment call rather than the mechanical edit the in-place-fix exemption is for. C is the fallback if the fourth file must stay untouched by this seat."
        }
      ],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  11. claude commented on Sep 2, 2026

    @claude
    ContributorAuthor

    PM decision on the sitting-4 report's open question (devx seat, session session_01WLJQhde67SeTccsmnBVarV) — option A, in this PR (#14593); the file surface widens to a fourth file, scripts/pm/dispatch-gates.mjs, for exactly that one pin.

    Verified on origin/main 2a2653619 before ruling: the #10784 ablation at dispatch-gates.mjs:12158 asserts classifyEntry(undeclared, [unwrittenScript]).verdict === 'silent' after stripping the scripts/** subtree hint. Its stated purpose (the comment above it) is to prove the SUBTREE declaration is the load-bearing half — i.e. that without it the brand-new file is not matched. 'silent' was only ever the residual verdict because the roster still contributed two path literals as hints; with the roster empty the residual hint set is [] and the residual verdict is 'undetermined', which is equally not-matched. The pin's spelling was calibrated against a SHRINK-ONLY debt ledger that was designed to reach zero — the ledger reaching zero is the card, not a regression. Options B and D (keep a literal / keep an entry) are rejected for the dev's reasons: both keep the #10784 blind spot or real debt alive to satisfy an incidental spelling. C (merge red) is rejected: a red advisory gate on main is shared damage.

    Conditions for A:

    • Widen the expectation to not-matched (verdict !== 'matched', or an explicit ['silent', 'undetermined'].includes(verdict) — prefer the explicit pair so a new verdict value cannot slip through as a pass), keep the undeclared.hints.length < entry.hints.length vacuity guard untouched, and put the reason in the pin's comment (one sentence: the residual verdict depends on whether check-entry-guard.mjs's roster contributes path literals; silent while it held entries, undetermined once it emptied — either proves the subtree hint is the load-bearing half).
    • Keep the pin discriminating: show in the PR body a one-off mutation where the subtree hint is NOT stripped (or the verdict is forced to matched) still reds this case.
    • pnpm check:pm-dispatch-gates back to 1240/1240 through os-verify-lock.sh; re-run pnpm check:entry-guard and the two scripts' before/after byte-identity once more on the final commit.
    • PR body first line becomes Fixes #10667 (the ledger is at zero and every gate is green); git diff --stat = exactly four files; the PR body's file-surface note names the fourth file and this comment as its authorization.

    Serial effect: scripts/pm/dispatch-gates.mjs is held by PR #14593 until it merges (no open PR touched it at 14:2xZ; the #14325/#14189/#13800 chain waits behind it — recorded on the seat post).


    Generated by Claude Code

  12. claude commented on Sep 2, 2026

    @claude
    ContributorAuthor

    os-dev-report

    {
      "issue": 10667,
      "status": "done",
      "branch": "claude/issue-10667-entry-guard-ledger-sitting-4",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/14593",
      "premise_still_valid": true,
      "summary": "SECOND report, superseding the needs_decision one above after the PM ruled option A in this PR (comment 5511119817). The card is complete: KNOWN_IMPORT_UNSAFE is at ZERO and every gate is green, so the PR body's first line is now the closing keyword for this card rather than 'Part of'. Sittings 1-4 across the whole card: 35 -> 10 -> 5 -> 2 -> 0. Unchanged from the first report: both changeset gate scripts took the inverted 4-line insert with zero re-indentation, check-entry-guard.mjs was touched only to delete the two ledger lines (SHRINK-ONLY doc comment untouched by design, since that comment is what keeps an empty set empty), the sitting-3 ts-parse.mjs inversion check was applied and CLEARS (nothing imports either file; every reference spawns them), and the measured leaks were a silent-success (check-changeset-no-major.mjs ended the importer mid-import at status 0 with no SENTINEL) and a stdout leak (check-empty-changeset.mjs). NEW in this round: the fourth file scripts/pm/dispatch-gates.mjs now carries the one-pin fix. The #10784 ablation asserted the post-strip residual verdict was exactly 'silent', which held only because the ledger still contributed two path literals as hints; the ledger is SHRINK-ONLY and reaching zero is its goal, so emptying it left the stripped hint set bare and the residual became 'undetermined'. The pin now accepts the explicit pair ['silent','undetermined'] (not '!== matched', so a new verdict value cannot slip through), the vacuity guard is untouched, the case label reads NOT MATCHED instead of silent, the residual verdict is carried into the failure detail, and a comment records why the residual is not fixed. Rebased-free: origin/main was MERGED (not rebased) at 5e05c23d7, picking up PR #14584's memoiseMask change in dispatch-gates.mjs; my pin edit sits on top of it and the merge was clean.",
      "tests": "Union RE-DERIVED at the final commit d11a00a29 with a clean working tree: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands -- provenance names objectstack-ai/objectstack at d11a00a29. ⚠️ The fourth file GREW the union from 18 to 20 families: node scripts/check-self-test-wired.mjs and pnpm check:declared-population-live are new and were in NEITHER the first derivation nor the dispatch's list -- re-deriving after the surface widened is what caught them. Exit codes captured by redirect before any pipe. 19 RAN-PASS (exit 0): pnpm check:agent-test-spelling; pnpm check:bash32-floor; pnpm check:changeset-gate-self-tests; pnpm check:cli-command-ids; pnpm check:cross-package-test-inputs; pnpm check:declared-population-live ('✓ 158 of 202 famil(ies) declare a path population, and every one of them reaches this tree's 7985 tracked file(s)'); pnpm check:entry-guard ('✓ check:entry-guard: 199 scripts/ file(s) ... 150 export bindings, 150 of them inert on import (0 known-unsafe, SHRINK-ONLY)'); pnpm check:parse-guard; pnpm check:pnpm-filter-targets; pnpm check:ratchet-remedy-authority; pnpm check:watch-hint-literal; pnpm check:pm-dispatch-gates ('✓ dispatch-gates self-test: 1240 cases pass', FINAL GATE EXIT=0 -- back to 1240/1240 from 1239/1240, run detached through scripts/pm/os-verify-lock.sh, VERDICT command-exit 0, held 439s waited 320s); node scripts/check-changeset-no-major.mjs; node scripts/check-ci-filter-parity.mjs; node scripts/check-cross-package-test-inputs.mjs; node scripts/check-empty-changeset.mjs; node scripts/check-self-test-wired.mjs; node scripts/check-shard-attestation.mjs; node scripts/pm/bare-root-worklist.mjs --self-test ('none stale, none missing, none contradicted'). 1 NOT MEASURED: node scripts/check-test-completeness.mjs exit 3, its own PREREQUISITE NOT MET branch (grades a saved turbo test log; the family names it with no argument; its text says explicitly this is not a red and CI never reaches the branch). DISCRIMINATION PROOF (PM condition 2), all on the final commit, each mutation proven on disk by anchor count before running and each restored with git checkout HEAD -- ABSOLUTE path, restore proven by blob hash equal to the HEAD blob AND an empty git diff HEAD, under a trap: mutation A (the strip made a no-op, filter(() => true), so the subtree hint is NOT stripped) -> the case goes ✗ RED for check:entry-guard AND check:parse-guard; mutation B (residual forced to 'matched') -> ✗ RED for both; CONTROL (unmutated) -> ✓ GREEN for both. So the widened pin still fails when the subtree declaration is not what is doing the work, and on a matched residual -- it was not loosened into a tautology. The self-test streams each case as decided, so each mutation run was killed by PID once the case printed (never by name -- sibling agents share this box). BYTE-IDENTITY RE-PROVEN on the final commit as a same-tree ablation: capture guarded, revert BOTH scripts to origin/main (isEntrypoint count 0/0 read back off disk), capture unguarded, restore, prove (both blobs match HEAD, git diff HEAD empty, guard count back to 3/3), cmp -- all six comparisons IDENTICAL (default, --self-test, --list for both scripts), exit 0 throughout. Control-character self-scan over the diff: clean.",
      "mcp_calls": "0 -- all GitHub reads and writes went through REST with $GITHUB_TOKEN and the CA bundle across both rounds; no MCP GitHub call was made.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  13. claude commented on Sep 2, 2026

    @claude
    ContributorAuthor

    ACCEPT — PR #14593 merged to main as a12088548 (out of the merge queue 17:21:29Z; queued 15:02Z, evicted 15:50Z by a Corepack infra death in the merge group, re-armed once 16:05Z, then through a second group that hit the #14648 queue flake without eviction). Session session_01WLJQhde67SeTccsmnBVarV (domain:devx execution seat, seat post #6023), R1 wave 8 — sitting 4, the final sitting.

    Close-out probes on origin/main 2aa8456cf (verify worktree, a12088548 an ancestor):

    Against the card and sitting-3's note (5404705956): the ledger reached zero through the gate's own STALE verdicts, never by deleting a line first; the ts-parse.mjs inversion check was applied and cleared (nothing imports either file). Held. Zero rework.

    pm:dispatched removed with this comment; the card closes via the PR's Fixes #10667. Serial effect: scripts/pm/dispatch-gates.mjs is free again for #14325 / #14189 / #13800.


    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

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions