Skip to content

fix(pm): derive the always-runs tail dispatch-gates could never name - #13501

Merged
os-project-manager merged 2 commits into
mainfrom
claude/issue-13333-dispatch-gates-always-runs-tail
Aug 30, 2026
Merged

fix(pm): derive the always-runs tail dispatch-gates could never name#13501
os-project-manager merged 2 commits into
mainfrom
claude/issue-13333-dispatch-gates-always-runs-tail

Conversation

@claude

@claude claude Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Fixes #13333

Triage adjudicated option (b) on this card — have dispatch-gates enumerate the
unconditional Lint & Repo Gates steps as an "always runs" tail — and explicitly
refused option (a), adding check-reference-carrier-shape to the derivation. This
implements (b), derived rather than hand-listed.

What was measured first

Reproduced on current origin/main, with positive controls, because the card's own
figures had drifted:

reading card measured now
reference-carrier in dispatch-gates.mjs 0 0
positive control where-matcher, same file 12 14 (triage saw 14 too)
check-reference-carrier-shape in lint.yml :3281-3284 :3338-3339
the gate anywhere in a full --residue universe of 187 families 0 hits; controls turbo-task-graph 1, where-matcher 2

So the gate is not in the silent bucket and not in undetermined — it is in no
bucket at all
, the one output shape this file's contract forbids.

H1 as stated is FALSIFIED, and the falsification changes the fix

The sharpened mechanism on the card is "every package-local script invoked by path
inside a multi-line run: | block"
. Measured over all 26 workflows:

A fix built on that mechanism would therefore have been option (a) with extra steps.
The population that is real is the one triage named — unconditional steps — and
it is invisible for at least three unrelated reasons: a package-local path, an
interpreter that is not node, and a root script not named check:*. That is why no
fourth widening of the three matchers was going to be the last one.

The coverage measurement triage asked for

Derived live, on the merge commit of this branch:

  1. unconditional steps CI runs on every PR — 191, across the pull-request workflows
    with no paths: filter, in jobs and steps with no if: (11 conditional jobs and 12
    conditional steps excluded, and both counts printed rather than dropped);
  2. accounted for by a discovered family — 164;
  3. named by nothing, i.e. the new tail — 27 step instances / 16 distinct commands.

Proof that it reaches the CLASS, not the instance

The tail names the card's gate and members invisible for different reasons:

- [lint.yml · Lint & Repo Gates] ESLint
      pnpm lint
- [lint.yml · Lint & Repo Gates] Verify-lock entry-point self-test
      bash scripts/pm/os-verify-lock.sh --self-test
- [lint.yml · Lint & Repo Gates] os-regen-merge self-test
      bash scripts/pm/os-regen-merge.sh --self-test
- [lint.yml · Lint & Repo Gates] Claude hook guard self-tests (worktree-first · stash ban)
- [lint.yml · Lint & Repo Gates] Relationship carriers are spelled as the string the spec declares
      node packages/lint/scripts/check-reference-carrier-shape.mjs --self-test
- [lint.yml · Type Check · consumer gates] Root-entry type nameability pin (built dist, declaration emit)
      pnpm --filter @objectstack/spec exec vitest run scripts/root-entry-type-nameability.pin.test.ts

pnpm lint and the two bash … --self-test steps are real verification that no
widening of the node scripts/… matchers would ever have reached
.

That claim is a self-test case, derived live, not a sentence here: the tail must contain
a member whose interpreter is not node and that member must not be the card's own
gate. Reverse verification (ablation, run against the committed implementation, the
mutation confirmed on disk by object hash 8e8d9489 -> ac39a3df before any reading,
restored and re-proved byte-identical to the HEAD blob afterwards):

✗ a gate run by another interpreter IS in the tail
✗ the live tail reaches the CLASS: a second member invisible for a DIFFERENT reason (another interpreter)
✗ and that second member is not the card's own gate wearing a different name
✗ dispatch-gates self-test: 4 of 976 case(s) failed.

The instance case (a package-local gate invoked by path IS in the tail) stayed green
through that ablation — which is exactly the discriminator between (a) and (b).

Why this is not "22 leads is the same as none"

The header's refusal stands: 166 of 189 discovered families sit outside both path
declarations CI obeys, and naming them per card would say nothing. This tail is the
complement of that set, not a slice of it — a step leaves the list the moment the
derivation names any family for it, so it is bounded by the derivation's own blind spot
and shrinks as discovery improves. It carries no per-card claim and says so.

What it deliberately does NOT do

Verification

All on 263b4923, exit codes captured before any pipe.

  • check:pm-dispatch-gatesEXIT=0, ✓ dispatch-gates self-test: 976 cases pass.
    (944 -> 976, +32 cases: the walk's fixtures, the renderer's refusals, and the live
    class pins.)
  • bare-root-worklist.mjs --self-testEXIT=0,
    OK self-test: 51 live row(s), 43 unreachable as spelled, 43 recorded verdict(s) — none stale, none missing, none contradicted.
  • The 14 families dispatch-gates derives for this diff — all EXIT=0, including
    check:watch-hint-literal, check:entry-guard, check:parse-guard,
    check:self-test-wired (✓ every one of the 152 script(s) CI runs that ship a --self-test has that self-test run by CI)
    and check:nul-bytes.
  • check-test-completeness.mjsEXIT=3 = NOT MEASURED, neither red nor green, per
    its own printed refusal.
  • ESLint, narrowed and declared: eslint scripts/pm/dispatch-gates.mjs --no-inline-config --format json -> EXIT=0, 1 file, 0 errors, 0 warnings, 0 suppressed. The narrowing is
    sound because (i) the linted population and the rules are read from ESLint's own
    --print-config for this path, not guessed — 2 rules apply,
    comment-swallow/no-code-inside-block-comment and no-restricted-imports; (ii) the file
    count is ESLint's own --format json output; (iii) eslint.config.mjs states, with a
    recorded positive-control measurement, that this repo never enables type-aware linting
    for any file
    (no parserOptions.project, no typed @typescript-eslint rules), confirmed
    live here (parserOptions.project present: false), so a one-file diff cannot move any
    untouched file's verdict. The repo-wide eslint . is CI's run.
  • No changeset: the diff is scripts/pm/** only and publishes from no package, so
    skip-changeset is applied on this PR.

Generated by Claude Code


Generated by Claude Code

os-project-manager and others added 2 commits August 30, 2026 14:48
…13333)

Report the unconditional CI steps the family derivation names NOTHING for,
instead of widening a matcher for the one gate that reddened.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 30, 2026
@os-project-manager
os-project-manager marked this pull request as ready for review August 30, 2026 15:17
@os-project-manager
os-project-manager added this pull request to the merge queue Aug 30, 2026
Merged via the queue into main with commit b95ff78 Aug 30, 2026
34 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-13333-dispatch-gates-always-runs-tail branch August 30, 2026 15:41
This was referenced Aug 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/l skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants