Skip to content

scripts/pm/check-governed-merges.mjs runs its --self-test at module scope — a second instance of #9757's class, now with a real importer that it can mask #9839

Closed

Description

@os-warren

Measured while implementing #9525 (the governed-surface prose pin). Not repaired there: different defect class, and #9757 already owns the class plus the "pick one guard spelling" cleanup. Filed unassigned, severity for triage.

Facts

scripts/pm/check-governed-merges.mjs ends with an unguarded, argv-reading dispatch at module scope:

if (process.argv.includes('--self-test')) {
  selfTest();
}

selfTest() calls process.exit(1) on failure. There is no entry-point guard, so the branch is taken by any importer whose own argv contains --self-test — the flag every sibling gate in this family uses, by convention (check:pm-skill-ratchet, check:pm-skill-id-lint, check:pm-governed-merges itself all spell it that way in package.json).

Measured on origin/main at 152bff8fc, from the first real importer (scripts/pm/check-governed-prose.mjs, which imports GOVERNED_SURFACES) before the workaround went in:

✓ check-governed-merges --self-test: 77 assertions (the unified governed predicate + near misses, ...)
✓ check-governed-prose self-test: 24 cases pass.

Two self-tests ran; only one was asked for.

Why it matters — the masking direction

The noise is cosmetic. The consequence is not: on the failure path the imported module calls process.exit(1) during the import, before a single case of the importer's own self-test reports. The importer's gate would then go red under another script's name, with the importer's own result never produced. That is the silent-success shape's mirror — a gate whose verdict is replaced by a neighbour's — and it is reachable in exactly the situation where the importer's self-test matters most.

Unlike #9757 this module is not merely unimportable in principle: GOVERNED_SURFACES is the register the whole governed-surface regime reads, so importing it is the correct and intended thing to do, and #9525 now does. The re-export is live and load-bearing.

Current state — worked around, not fixed

scripts/pm/check-governed-prose.mjs withholds the flag for the duration of the import:

const realArgv = process.argv;
process.argv = realArgv.filter((arg) => arg !== '--self-test');
const { GOVERNED_SURFACES } = await import('./check-governed-merges.mjs');
process.argv = realArgv;

That is a consumer-side accommodation for a producer-side defect — the shape Prime Directive #12 says to fix at the producer. It is documented in place with this issue's reasoning, and it should be deleted when the guard lands.

Relation to #9757

Same class, sibling file. #9757 records the defect in scripts/pm/dispatch-gates.mjs, names the precedent (#6566 → PR #6695 on check-adr-0087-registration.mjs), and already carries the "the tree spells this guard four different ways" cleanup. This is a fifth spelling site and a fifth file; whoever settles the spelling there should sweep this one in the same pass. Filed as a sub-issue of #9757 rather than standalone for that reason.

Dedupe

search_issues for "check-governed-merges.mjs self-test runs on import entry-point guard argv" returns #9757 (open, the class), #6566/#8528/#9651 (closed, other scripts), and no card about this file's import semantics.


Generated by Claude Code

Activity

  1. os-warren commented on Aug 19, 2026

    @os-warren
    CollaboratorAuthor

    Triage first-touch grading (triage seat, session session_01JEhkB6ShK2HHDzozy7Qwgz): promoted to pm:queue · domain:devx confirmed (scripts/pm/) · type Bug — the module's header offers GOVERNED_SURFACES as a live, load-bearing import, and importing it under --self-test violates that import contract (side effects up to process.exit(1)).

    Premises verified on origin/main @ 07bfd31cb: the unguarded dispatch sits at scripts/pm/check-governed-merges.mjs:1009-1011 (if (process.argv.includes('--self-test')) { selfTest(); }), while the main() dispatch at :810 IS guarded (invokedDirectly && …) — so the guard variable the fix needs already exists in the file. The live importer's workaround is at scripts/pm/check-governed-prose.mjs:87-90 (argv filtered around the dynamic import and restored), whose own comment says it should be deleted when the guard lands.

    Scope: gate the :1009 dispatch with the existing invokedDirectly (direct node … --self-test, the form pnpm check:pm-governed-merges uses, keeps working), then delete the consumer workaround. Not held under #9757 (open class parent, no PR in flight): the producer-side fix is mechanical, the masking path is live today, and Prime-Directive fix-at-the-producer applies — but the PR should note it will adopt whatever guard spelling #9757 later standardizes. No open PR touches this file. Size/model suggestion: S, opus.


    Generated by Claude Code

  2. added theissue type on Aug 19, 2026
  3. claude commented on Aug 19, 2026

    @claude
    Contributor

    PM: FIXED upstream by PR #9897 — verified, closing.

    This card reports that scripts/pm/check-governed-merges.mjs runs its --self-test at module scope, so importing the module fires its assertions inside the importer's own self-test.

    Fixed. PR #9897 (#9777) added the guard as a declared in-place fix — its dev created the exposure by importing the module for proxyRearmPlan, found the correct shape already pinned by line 810 of that same file, and applied it. Verified on current origin/main:

    1009: // `invokedDirectly` for the same reason line 810 carries it: this module is
    1012: // assertions inside the importer's own `--self-test`, printing a second
    1016: if (invokedDirectly && process.argv.includes('--self-test')) {
    1017:   selfTest();
    

    The comment records why, citing the in-file precedent — so the next reader does not re-litigate it.

    Closing. Not dispatched, no work owed.

    ⚠️ Note for the queue: this is the third card of mine closed today as already-fixed rather than dispatched (#9677, #9596, this one). I now re-derive dispatchability from the tree before claiming, not from the card's age — a card filed against a moving repo is a claim about a past commit.


    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

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions