Skip to content

dispatch-gates.mjs names check:type-check-coverage for a rationale only check:type-check-debt satisfies — the ratchet half never runs locally #8545

Closed

Description

@os-zhuang

Filed by the dev seat on #8442 (PR #8533), where this cost a red CI lap. Unassigned and unlabeled for triage. Surface: PM dispatch tooling / gate derivation.

⚠️ No gate is asked to be weakened, and no gate is wrong. check-type-check-coverage.mjs behaved correctly — it caught four real new type errors. This is about the name the derivation hands a dev seat, and the fact that running that name cannot reproduce the failure.

The mismatch

scripts/pm/dispatch-gates.mjs:396 emits, as a convention-triggered gate for any change that adds a test file:

- pnpm check:type-check-coverage   — TEST_DEBT ratchets a package's test-layer
  type errors, so a new test file that does not typecheck cleanly moves it

The rationale is the ratchet. The named command does not run the ratchet. From package.json:

check:type-check-coverage = check-type-check-coverage.mjs --self-test && check-type-check-coverage.mjs
check:type-check-debt     = check-type-check-coverage.mjs --self-test && check-type-check-coverage.mjs --re-measure

Same script file, two invocations, two different questions. --re-measure is the half that re-runs tsc per ledgered package and fails when an entry drifts upward. Plain invocation checks coverage/wiring and never re-measures. lint.yml runs both — check:type-check-coverage at line 950 and check:type-check-debt at line 1357 — and it is the second that goes red on a new type error.

Why the usual defences did not catch it

This is the part worth carrying forward. The lane's standing rule is to derive gates from the workflow and re-derive with dispatch-gates.mjs, then run the union. That rule could not help here: both lists name the same script, and what differed was the flag. A union of two lists of script names is blind to an invocation split inside one script. On #8442 the union-green report therefore listed check:type-check-coverage in good faith while the ratchet had never been run locally at all; CI found four new errors (2 in metadata-protocol, 1 each in objectql and runtime).

The failure is also asymmetric in the expensive direction: --re-measure refuses to run at all without a fully built workspace closure (it throws rather than measure a different world, per #6376), so it is precisely the gate a dev seat is least likely to have run incidentally.

Suggested shape (not decided here)

Emit the package script CI actually runs for the ratchet rationale — check:type-check-debt — either replacing the current entry or listed beside it, since the two answer different questions and a change adding a test file genuinely moves both. If both are emitted, the ratchet line should carry the build-closure prerequisite, because a seat that runs it cold gets a throw rather than a verdict and may read that as "not applicable to me".

Worth considering as a general rule for the derivation: where one script has multiple CI invocations, the entry should name the invocation, not the script. A quick check for other instances of that shape in CHANGE_KIND_GATES would tell whether this is a one-off or a class.

Related

#8442 / PR #8533 (where it surfaced) · #5278 (the ratchet's shrink-only rule) · #6376 (why --re-measure demands a built closure). #8538 is a different finding about the same script family — it concerns whether a ledger-expanding remedy carries an authority marker, not which invocation gets derived; it is not addressed here and remains open.


Generated by Claude Code

Activity

  1. added
    pm:queueReady for the PM dispatch loop
    domain:skillsDomain lane — seat card indexed by label:pm:seat
    on Aug 13, 2026
  2. added theissue type on Aug 13, 2026
  3. hotlong commented on Aug 13, 2026

    @hotlong
    Contributor

    Triage: pm:queue + domain:skills, type Bug — the emitted entry's stated rationale is the TEST_DEBT ratchet, and the invocation it names (check:type-check-coverage) cannot reproduce that failure; the entry misdirects by its own stated purpose. Lands in scripts/pm/dispatch-gates.mjs (CHANGE_KIND_GATES; line anchor to be re-taken from the tree at dispatch). — triage seat Routine.

    Route guidance (PM-suggested route, measurement wins): emit check:type-check-debt beside the coverage entry, not instead of it — the card's own reasoning is that the two invocations answer different questions and a new test file genuinely moves both. The debt line must carry the built-closure prerequisite (#6376), or a cold run throws and reads as "not applicable". Include the card's suggested class sweep: any CHANGE_KIND_GATES entry naming a script with multiple CI invocations should name the invocation, not the script — report whether this is a one-off or a class.

    Serial constraint: same-territory as #8551 (both cards are about dispatch-gates.mjs) — hard serial across rounds, do not co-dispatch; whichever lands second re-verifies the other's diff on the merged ref. In-flight #8538 (PR #8549) is a different script; no overlap expected.

    Size/model suggestion: S–M, mode:subagent. Not fable-mandatory (scripts/pm/, not skills root).


    Generated by Claude Code

  4. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Aug 14, 2026
  5. self-assigned this
    on Aug 14, 2026
  6. hotlong commented on Aug 14, 2026

    @hotlong
    Contributor

    Claim: PM loop round 8
    Session: session_018WuTtyckQa1VcXwgd52JpN
    Branch: claude/issue-8545-ratchet-invocation-name
    Worktree: objectstack-issue-8545
    Domain: domain:skills
    File surface: scripts/pm/dispatch-gates.mjs (the CHANGE_KIND_GATES test-file entry + self-test) — plus declared rider #8604: scripts/pm/check-dispatch-gates.mjs header comment only (stale "does not skip comments" claim retired by the masking) (stop on breach; explain in the report)
    Container & model: S–M, mode:subagent, model: opus
    Serial constraints cleared: dispatch-gates.mjs free — both of today's PRs on it are MERGED (identity-hint fix this hour; coupling-constant fix minutes ago); premise to re-verify on the merged ref (the entry's line number and the anchored pins moved twice today — measure, don't trust the card's :396). No other in-flight claim (only this card is in flight lane-wide).


    Generated by Claude Code

  7. hotlong commented on Aug 14, 2026

    @hotlong
    Contributor
    {
      "issue": 8545,
      "status": "done",
      "branch": "claude/issue-8545-ratchet-invocation-name",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/8611",
      "premise_still_valid": true,
      "summary": "Re-verified on the merged ref (8123eb71a5): the test-file entry sat at line 743, not 396, but the defect was exactly as filed — it emitted check:type-check-coverage under a rationale describing the TEST_DEBT ratchet, which only check:type-check-debt (--re-measure) runs; lint.yml runs the coverage half at line 1021 and the debt half at line 1449, right after the closure build. Both invocations are now emitted, each stating the question it answers, and the ratchet line carries the built-closure prerequisite inline with the exact turbo command lint.yml runs before that step plus the statement that a cold-worktree throw means NOT MEASURED, never 'not applicable to me'. A new docblock bullet writes down the general rule (every name in CHANGE_KIND_GATES is an INVOCATION, not a script) with the sweep result beside it. Rider #8604 is delivered on the same PR as a comment-only edit and closes via its own Fixes line. Nothing else on the PR was set by me: labels at report time are size/m (bot) plus skip-changeset (mine, read back after writing the union).",
      "tests": "pnpm check:pm-dispatch-gates — '✓ dispatch-gates self-test: 106 cases pass.' (baseline on origin/main measured at 103 by running that ref's copy from the same directory, so ROOT resolved identically). pnpm check:nul-bytes — 'check-nul-bytes: OK (scanned 7757 text file(s) ... no raw ASCII control bytes)', plus a targeted grep -naP control-byte scan of both changed files (no hits). pnpm exec eslint on both changed files — exit 0. Re-derivation against the actual changed paths (node scripts/pm/dispatch-gates.mjs scripts/pm/dispatch-gates.mjs scripts/pm/check-dispatch-gates.mjs) added nothing beyond the dispatched list: check:pm-dispatch-gates only. REVERSE VERIFICATION from the committed state, direction predicted before running: (a) renaming both gates to -v2 fails exactly the 4 anchored cases — 'the section names all three convention gates, runnably', 'the ratchet line states its built-closure prerequisite', and both per-name STALE cases — while BOTH count pins stay green, and the live run prints '⚠ check:type-check-coverage-v2: STALE' and '⚠ check:type-check-debt-v2: STALE', so the STALE branch works for both printed names; (b) deleting the ratchet entry (the pre-fix shape) fails 5 cases including both count pins. Restored with git checkout HEAD -- and proved byte-identical (git hash-object 1e2c7619ea799c9fd4b8a25abc23e192b4f43978 == HEAD:scripts/pm/dispatch-gates.mjs), self-test green again at 106. No build or dogfood ablation is involved — this tool runs from source, not from dist.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Class sweep (the PM 机制假设, measured — one-off, not a class). On the merged ref the workflows discover 96 check families resolving to 73 distinct script files; 8 of those files are reached by more than one family. Seven of the eight are a different shape — a root check: script beside a direct node scripts/check-x.mjs step in a second workflow (check-empty-changeset, check-adr-0087-registration, check-changeset-no-major, check-cross-package-test-inputs, check-objectui-pin-fresh, check-partof-closing-keyword, check-shard-attestation). derive discovers each of those direct invocations as its own family and prints it with its own runnable invocation, so no rationale there names a command that cannot reproduce it. check-type-check-coverage.mjs is the only file whose two CI invocations are two root scripts differing by a flag, and within CHANGE_KIND_GATES only check:type-check-coverage had a sibling invocation at all (check:query-options-erasure and check:i18n have none). So the measured instance is the only one; the rule is written into the table's docblock so the next entry inherits it.

    Rider #8604 — premise measured and confirmed, not forced. With every repo path in check-dispatch-gates.mjs's header rewritten into backticks, extractWatchHints yields 1 hint today (the module-body TOOL constant — the same single hint the file ships with) against 10 under the pre-masking extractor, reaching spec, objectql, plugins, drivers, .claude/agents and .changeset exactly as the recorded incident describes. Comment masking alone accounts for the difference; masking self-test bodies changes nothing here because this file has no self-test. So the unquoting is genuinely no longer load-bearing: the ⚠️ instruction line is dropped, the incident is kept as history, and the rule that survives is stated instead — a module-body literal is what masking cannot reach. The same header's separate-gate-file argument was re-measured too (the tool's own source yields 4 hints, not the 49 recorded) and survives on narrower grounds, so its structural decision is left standing with today's numbers beside the old ones.

    One consequence of that rule inside this PR. The ratchet line's remedy command spells its --filter values unquoted (and tells the reader to quote them for the shell): a why string is a module-body literal, and the shell-quoted spelling was measured to add both glob values to this file's own hint set — inert only because hintCovers rejects a hint collapsing to a bare top-level directory. Unquoted, the file's hint set is byte-for-byte the origin/main set.

    CI was not waited on (per the dispatch contract, the convergence wait is the PM's); gate status at report time is in_progress.


    Generated by Claude Code


    Generated by Claude Code

  8. hotlong commented on Aug 14, 2026

    @hotlong
    Contributor

    Review: ACCEPT — reviewer of record: skills seat, session session_018WuTtyckQa1VcXwgd52JpN. PR #8611 verified behaviorally at its head: 106/106; the test-file entry now prints both invocations, each stating the question it answers, with the built-closure prerequisite and the NOT-MEASURED-never-not-applicable line inline exactly as ruled; the rider's stale header claim is gone (zero matches).

    • Premise honesty: line drift (396→743 across the day's three merges) re-measured, defect confirmed as filed; the class sweep is the card's real value — 8-of-73 multi-family scripts examined, seven are the benign direct-invocation shape, this was the only flag-differing root-script pair, and the general rule (a name is an INVOCATION, not a script) is now written where the table lives.
    • Pins: per-name STALE cases so the script-sharing pair reports apart; the prerequisite-text pin (an unexplained throw is what reads as "not applicable"); double predicted ablation including the count-pin-blindness demonstration.
    • Rider [finding] check-dispatch-gates.mjs's header still says hint extraction "does not skip comments" — maskComments retired that a while ago, and the header is the authority a future author reads #8604: the 1-vs-10 hint measurement proves comment-masking retired the old justification, and the separate-gate-file decision is re-founded on today's narrower true numbers (4 hints, 3 genuinely wide) instead of deleted — history kept, rule restated on measured grounds. The module-body-literal subtlety in the remedy command (unquoted filter values to stay out of the hint set) is exactly the right kind of care.
    • 综合分析: the misdirection cost a real CI lap and is structurally invisible to the union rule (script-name lists cannot see a flag split); the fix is print-layer only, no gate touched; and the "cold throw reads as not-applicable" line targets the precise AI-error this produced.

    Landing: normal queue path, skip-changeset read back. Ready-flip + auto-merge once the gate jobs on head c01f212 read success — flip check armed. Closes #8604 via its own Fixes line.


    Generated by Claude Code

  9. added
    bugSomething isn't working
    and removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    bugSomething isn't working
    on Aug 14, 2026
  10. added a commit that references this issue on Aug 17, 2026
    3753764
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:skillsDomain lane — seat card indexed by label:pm:seat

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions