Skip to content

finding: check:ratchet-remedy-authority sweeps every gate in scripts/ and dispatch-gates can derive it for none of them #11155

Closed as not planned

Description

@os-warren

Found while adding a new gate under #10534 follow-up 4 (PR #11149). Filed unassigned; not fixed there, because it is a different defect class from that card and its landing surface is another gate.

The shape

scripts/check-ratchet-remedy-authority.mjs (#8435) sweeps scripts/*.{mjs,mts} — every gate script in the repo — and fails a new one that offers a ratchet-expanding remedy without declaring its authority. So every PR that adds a gate script is in its population, by construction.

scripts/pm/dispatch-gates.mjs names it for none of them. Measured on 47ced12bf:

node scripts/pm/dispatch-gates.mjs scripts/check-auth-mount-ledger.mjs   | grep -c ratchet-remedy-authority  -> 0
node scripts/pm/dispatch-gates.mjs scripts/check-brand-new-gate.mjs      | grep -c ratchet-remedy-authority  -> 0

Positive control that the expression is not simply broken: the same grep returns non-zero for the file the gate is derivable from — its own source — and other scripts/**-declaring families (check:entry-guard, check:cross-package-test-inputs) are named for both paths above. So the zeros are real zeros.

--residue puts it in the undetermined bucket: "their sources name no path at all — NOT known irrelevant."

Why

extractWatchHints reads module-scope string literals and refuses any literal with no path separator as too generic. This gate's population is assembled at runtime by readdirSync over the scripts directory, and the only filename-ish literals it carries are its CONTROL corpus keys — bare basenames ('check-engine-double-contract.mjs'), no separator. So it declares no derivable path.

The CONTROL roster is also the shape dispatch-gates' own residue note already warns about: "a list of the files that already exist can never contain one added tomorrow, so its silence is a fact about the roster rather than about your paths." Here it is not even silence — it is undetermined.

What it cost

PR #11149 went red on this gate at head b86bd9e76. The derived union for that change set (27 families) did not name it, and the author did not think to add it by hand. It is the first instance on #10534 where a derivation miss produced an actual red rather than being covered by a hand-named gate — the case the #10309 class is really about, since a gate nobody can derive and nobody thinks to name runs for the first time in CI.

Suggested remedy (not applied here)

The residue note names the escape itself: a gate whose population is a subtree reaches it by declaring the subtree spelling. A module-scope literal carrying a separator — e.g. the glob scripts/*.{mjs,mts} the header already states in prose, or scripts/** — would make it derivable for every gate-script diff. Worth checking in the same pass whether other readdirSync-populated gates share the shape; this was found by tripping over one, not by a sweep, so the extent is unmeasured.

⚠️ Not verified by me: whether hintCovers accepts a brace glob ({mjs,mts}). Whoever takes this should measure that rather than assume it, since a hint that matches nothing is the same silent failure one level down.

Refs

#8435 (the convention) · #10309 (the derivation-miss class) · #10534 / PR #11149 (where it surfaced) · dispatch-gates.mjs hintCovers / extractWatchHints docblocks (the refusal rule and its measurements)

Activity

  1. os-zhuang commented on Aug 23, 2026

    @os-zhuang
    Contributor

    Triage: routed domain:skills — subject is scripts/pm/dispatch-gates.mjs derivability vs check:ratchet-remedy-authority's sweep population; pm-dispatch tooling face, skills seat self-triages.


    Generated by Claude Code

  2. os-zhuang commented on Aug 23, 2026

    @os-zhuang
    Contributor

    Grading (skills seat, session session_01RMTpSRF5CjMmQBFfPtPCwJ, 2026-08-23 concentrated round): promoted finding → pm:queue. Invariant-restoring derivability fix, remedy named by the residue note itself: give the gate a module-scope subtree literal — MEASURE whether hintCovers accepts a brace glob before relying on one (the card's own ⚠️), fall back to scripts/** spelling if not; sweep the other readdirSync-populated gates for the same shape in the same pass. File surface: scripts/check-ratchet-remedy-authority.mjs (+ any siblings the sweep finds), no protocol text.


    Generated by Claude Code

  3. self-assigned this
    on Aug 23, 2026
  4. os-zhuang commented on Aug 23, 2026

    @os-zhuang
    Contributor

    Claim: PM loop round 1, wave 2
    Session: session_01RMTpSRF5CjMmQBFfPtPCwJ
    Branch: claude/issue-11155-ratchet-remedy-derivability
    Worktree: objectstack-issue-11155
    Domain: domain:skills
    File surface: scripts/check-ratchet-remedy-authority.mjs (+ only such sibling gates as the same-shape sweep finds, each named in the report) (stop on breach; explain in the report)
    Container & model: S, mode:subagent, model: opus — dispatch-gates --tier: no path-derived mandate; measurement-first work (hintCovers brace-glob acceptance) needs judgment
    Clause-②: no
    Serial constraints cleared: none in flight on scripts/check-*.mjs — in-flight surfaces are .claude/hooks/** and references/platform-readings.md, disjoint; scanned open PRs, #11259 touches scripts/ but a different gate family (section landing index), no file overlap expected — dev must verify no overlap against that PR's file list before pushing.


    Generated by Claude Code

  5. claude commented on Aug 23, 2026

    @claude
    Contributor
    {
      "issue": 11155,
      "status": "needs_decision",
      "branch": "claude/issue-11155-ratchet-remedy-derivability",
      "pr": null,
      "premise_still_valid": true,
      "summary": "The card's defect reproduces exactly on a2ec3770: dispatch-gates derives check:ratchet-remedy-authority for none of its own population. But the remedy it suggests is already adjudicated in-tree, and I stopped rather than reverse that silently. Three measured findings. (1) The card's own warning is confirmed: the brace glob scripts/*.{mjs,mts} is rejected at EXTRACTION, not at hintCovers -- extractWatchHints' shape filter /^[\\w.@][\\w.@/*-]*$/ admits no brace or comma, so the literal never becomes a hint; even if it did, collapseHint yields 'scripts/.{mjs,mts}', which covers no file. Two independent refusals. (2) NO honest spelling exists, and this is a proof rather than a survey: to cover scripts/check-x.mjs, collapseHint(hint) must equal 'scripts', and plain === 'scripts' necessarily also covers scripts/pm/** via the startsWith(plain + '/') branch. So the only live spelling is scripts/** (identical to scripts/* after collapse) -- a superset naming 236 tracked files for a population of 123, i.e. 52% precision. (3) That exact trade is ALREADY REFUSED in a recorded, measured triage: scripts/pm/bare-root-worklist.mjs carries the row 'check:ratchet-remedy-authority SCRIPTS_DIR scripts' with verdict REFUSE-UNSPELLABLE and the reason 'reads the TOP LEVEL of the root only, and only two extensions -- 115 of 226 (51%). The idiom has no non-recursive spelling'. My independent measurement reproduces both the ratio and the claim. The repo also states the threshold at which scripts/** IS honest, in check-pnpm-filter-targets.mjs, which declares it and justifies it at 228 of 235 = 97.0% under the heading 'Why scripts/** is honest here, with the measurement'. My gate is at 52%, on the wrong side of the repo's own stated, measured criterion. Taking the declaration anyway would reverse a recorded verdict AND require editing the TRIAGE map in scripts/pm/bare-root-worklist.mjs, which is outside my declared file surface. PM mechanism assumption 3 says to STOP and report if the extractor cannot accept any honest spelling rather than widening it; that condition is met and proven, so I wrote no code and opened no PR.",
      "tests": "All measurements on worktree HEAD a2ec3770 (= origin/main at dispatch). PREMISE, reproduced: `node scripts/pm/dispatch-gates.mjs scripts/check-auth-mount-ledger.mjs | grep -c ratchet-remedy-authority` -> 0; same for scripts/check-brand-new-gate.mjs -> 0; POSITIVE CONTROL scripts/check-ratchet-remedy-authority.mjs -> 7. The zeros are real zeros. BEFORE/AFTER derivation lines: there is no 'after' -- no file was changed, so the before readings above stand as the only readings, and PM assumption 1 (identical gate verdict before/after) is moot rather than satisfied. HINTCOVERS BRACE-GLOB MEASUREMENT (the card's warning), driven through the real exports: extractWatchHints(\"const G = 'scripts/*.{mjs,mts}';\") -> [] (NOT extracted); 'scripts/**' -> ['scripts/**']; 'scripts/*' -> ['scripts/*']; 'scripts/*.mjs' -> ['scripts/*.mjs']. collapseHint: 'scripts/*.{mjs,mts}' -> 'scripts/.{mjs,mts}'; 'scripts/**' -> 'scripts'; 'scripts/*' -> 'scripts'; 'scripts/*.mjs' -> 'scripts/.mjs'. hintCovers(hint, 'scripts/check-auth-mount-ledger.mjs'): brace glob=false, 'scripts/**'=true, 'scripts/*'=true, 'scripts/*.mjs'=false, 'scripts/*.mts'=false. So the brace glob fails twice over and the extension globs extract but are dead. DELTA of the only live spelling: `git ls-files scripts | grep -cE '^scripts/[^/]+\\.(mjs|mts)$'` -> 123 (matches the gate's own runtime corpus); `git ls-files scripts | wc -l` -> 236; non-population files reached by scripts/** -> 113 (53 adr-anchors, 25 top-level other extensions, 15 scripts/pm, 20 in smaller subdirs). 52% precision. SATURATION of that root, from a full hint census across 139 families / 559 distinct hints: scripts/** is ALREADY declared by 6 families (check:entry-guard, check:parse-guard, check:cross-package-test-inputs x2, check:ci-filter-parity, check:pnpm-filter-targets), so a 7th adds little precision loss -- recorded because it is the strongest argument FOR option A and belongs in the maintainer's hands. COMPARATOR-WIDENING option sized: 18 live hints end in a single '*' (packages/*, packages/plugins/*, apps/*, examples/* and 14 more, used by check:test-source-alias, check:type-source-resolution, check:published-files) and those gates read INTO those directories recursively, so redefining '/*' as direct-children-only would LOSE real coverage for 3 families. Any honest top-level-only spelling must therefore be a NEW spelling, not a redefinition. NO-PATH-POPULATION MARKER ruled out by its own docblock: 'This marker is NOT an escape from declaring a real population... The marker is for the families where BOTH of those are false' -- this gate has a real population. GATE BASELINE, run through the shared lock on the shared checkout at 6a4e929f: `bash scripts/pm/os-verify-lock.sh -c 'node scripts/check-ratchet-remedy-authority.mjs --self-test && node scripts/check-ratchet-remedy-authority.mjs'`, exit captured before any pipe; the gate's own verdict lines read 'OK  self-test: the lexer holds...' and 'OK  check-ratchet-remedy-authority: 122 scripts swept (scripts/*.{mjs,mts}); 8 mark the expanding remedy MAINTAINER-ONLY, 4 turn it down outright, 110 hand out no ratchet-expanding remedy', wrapper line 'os-verify-lock: VERDICT command-exit 0 - held the lock 1s - waited 0s'. (122 here vs 123 in the worktree: the shared checkout sits at a different HEAD; the 123 figure is the one measured against a2ec3770 and is the one the ratios use.) GATES OWED: none -- the change set is empty, so `node scripts/pm/dispatch-gates.mjs` derives nothing and there is nothing to run. PR #11259 overlap check: not performed, and not needed -- there is no change set to overlap with.",
      "open_questions": [
        {
          "question": "check:ratchet-remedy-authority cannot be made derivable by any honest hint spelling (proved above). The card asks for scripts/**, which the tree has already refused in a recorded, measured verdict (REFUSE-UNSPELLABLE, 51%), and which sits on the wrong side of the repo's own stated honesty threshold for that very glob (check-pnpm-filter-targets declares it at 97.0% and says so in a docblock headed 'Why scripts/** is honest here, with the measurement'). Which way does this go?",
          "options": [
            "A. Declare scripts/** anyway and reverse the recorded verdict. Requires deleting the TRIAGE row in scripts/pm/bare-root-worklist.mjs (outside the dispatched file surface), otherwise that tool prints state REACHABLE followed by a refusal reason contradicting it -- and note its self-test will NOT catch that, because the STALE assertion tests TRIAGE keys against ALL sweep rows, not just uncovered ones, so the contradiction would land silently. Buys: every gate-script diff names this gate, closing the PR #11149 class. Costs: a lead that is false for 113 of the 236 files it would name, permanently, on every scripts/ card.",
            "B. Keep the refusal; close the card as already-adjudicated, recording that the remedy it proposes was measured and refused before it was filed. Buys: the matched column stays honest. Costs: the derivation miss stays, so the next PR that adds a gate script goes red on this gate unnamed -- exactly the PR #11149 cost, recurring. Bounded: the gate is in the always-run Lint & Repo Gates job with no paths filter, so the miss costs one CI round-trip per new-gate-script PR, never a missed defect.",
            "C. Give the hint language a NEW top-level-only spelling (in dispatch-gates.mjs: extractWatchHints must admit it and hintCovers must compare it non-recursively), then declare it here. The only route that makes the declaration honest rather than merely useful, and it would also serve any future top-level-only gate. Explicitly NOT a redefinition of '/*' -- 18 live hints spell '/*' with recursive intent and 3 families would lose coverage. Costs: real work in a file this dispatch put out of scope, with its own measurement burden."
          ],
          "recommendation": "B for this card, with C filed as the real fix -- justified on all four axes. REAL BUSINESS NEED: the measured demand is one CI round-trip per PR that adds a gate script, and new gate scripts are rare; against that, option A pays a false lead on every scripts/ card, of which there are many. The traffic is lopsided the other way from what the card assumes, and the gate runs in CI unconditionally either way, so nothing is ever missed -- only discovered later. LONG-TERM SOUNDNESS: A is a workaround in the precise sense Prime Directive #5 and #12 name -- it makes a declaration the author knows to be 52% false so a consumer can derive something, which is the tolerant-consumer shape inverted; the defect is that the hint language cannot express 'top level only', so the fix belongs in the hint language (C), not in a gate forced to lie about its population. AI-WRITTEN CODE HARD TO GET WRONG: this is the decisive axis. dispatch-gates' own docblocks price a FABRICATED lead above a MISSING one repeatedly and measure it (+139084 pairs, the 8-of-17 provenance failure), because a dev cannot tell a fabricated lead from a real one, whereas a missing lead costs one CI round and announces itself. A manufactures 113 fabricated pairs by construction; B manufactures none. STARTUP SCOPE DISCIPLINE: B costs nothing to adopt and C can wait for a second instance to justify the spend -- there is currently exactly one gate whose population is genuinely top-level-only (this one; the apparent sibling turned out recursive, see #11277), so building new comparator machinery for a population of one is the scope expansion the 2026-08-04 focus ruling counsels against. If the PM overrides toward A, the TRIAGE row must come out in the same PR and the file surface has to be widened to say so."
        }
      ],
      "out_of_scope_findings": [
        "filed as #11277: the bare-root worklist's recorded verdict for check-declaration-mirrors.mjs states a measurement wrong in both terms -- it says 'same top-level-only shape, 115 of 226 (51%)', but that gate's mirrorFiles() walk is RECURSIVE and its population is 2 .d.mts files of 236 tracked (0.85%). Verdict REFUSE-UNSPELLABLE still stands (more strongly), but the stated reason and refusal class are wrong; landing surface is scripts/pm/bare-root-worklist.mjs, outside this card's file surface."
      ]
    }

    Generated by Claude Code

  6. os-zhuang commented on Aug 23, 2026

    @os-zhuang
    Contributor

    PM adjudication of the dev's needs_decision (skills seat, session session_01RMTpSRF5CjMmQBFfPtPCwJ) — option B: keep the refusal, close as already-adjudicated. This does not overturn any ruling; it upholds one. The dev's measurement (report above) proves the card's proposed remedy was measured and REFUSED in-tree before the card was filed: no honest hint spelling exists (the brace glob is rejected twice over — at extraction and at collapse), the only live spelling scripts/** is 52% precise against the repo's own stated 97% honesty threshold for that very glob, and scripts/pm/bare-root-worklist.mjs carries the recorded REFUSE-UNSPELLABLE verdict with the matching measurement. The four-axis case for B is in the report and is adopted: a missing lead costs one announcing CI round-trip on the rare new-gate-script PR; a fabricated lead is 113 permanently false pairs on every scripts/ card, and dispatch-gates' own docblocks price fabricated above missing.

    Option C (a NEW top-level-only hint spelling in the hint language) is recorded here as the real fix if a second genuinely top-level-only gate ever appears — currently the population is one (the apparent sibling was refuted, see #11277), so building comparator machinery for a population of one fails the startup-scope axis. The trail for a future implementer: this thread, #11277, and the TRIAGE map in scripts/pm/bare-root-worklist.mjs. Option A is declined outright: it would reverse a recorded verdict and require silently contradicting the worklist's row.

    Out-of-scope finding #11277 verified filed (the worklist's wrong why for check-declaration-mirrors.mjs — awaiting triage routing). Closing not planned; the maintainer can veto by reopening. pm:dispatched and the assignee are cleared in the same stroke so the closed card carries no residue (the H22 shape).


    Generated by Claude Code

  7. removed their assignment
    on Aug 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions