Skip to content

fix(pm): check-clause2-carriers names the board's SOURCE, answers --help, and refuses an argument it does not honour - #16699

Merged
os-musk merged 2 commits into
mainfrom
claude/issue-16623-clause2-carriers-repo-refusal
Sep 8, 2026
Merged

fix(pm): check-clause2-carriers names the board's SOURCE, answers --help, and refuses an argument it does not honour#16699
os-musk merged 2 commits into
mainfrom
claude/issue-16623-clause2-carriers-repo-refusal

Conversation

@os-musk

@os-musk os-musk commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Fixes #16623

The card was corrected by its own filer (comments 5573481868 / 5573596217 / 5573604263) and re-graded by the seat (5576224596) before this branch made its first edit. The original shape — port dispatch-gates.mjs's --repo assertion — is withdrawn, and no --repo is added here. This PR implements the re-graded XS shape only.

The rule, and what it actually asked for

The board this script reads was already a parameter. main() resolves it through resolveSweepRepo, imported by name from check-half-states.mjs, whose precedence is PM_SWEEP_REPOGITHUB_REPOSITORYDEFAULT_SWEEP_REPO. Verified here, not relayed — the requested URL is printed inside the transport-failure line, so the two legs are decisive even in a container that cannot read GitHub at all:

leg requested URL
unset GET /repos/objectstack-ai/objectstack/pulls?state=open&per_page=100&page=1
PM_SWEEP_REPO=objectstack-ai/objectui GET /repos/objectstack-ai/objectui/pulls?state=open&per_page=100&page=1

resolveSweepRepo has always returned source beside repo. The run simply never printed it, so a deliberate target and the default fallback rendered as the same sentence — and grep PM_SWEEP_REPO scripts/pm/check-clause2-carriers.mjs returns nothing to this day (exit 1), because the capability arrives through the import. That is exactly how the filing seat concluded the capability was absent. The value was already computed; this PR renders it.

What changed — three things, one file

1. Every run opens with a provenance line. Naming the board and on what basis it is the board:

check-clause2-carriers: every row below is read from objectstack-ai/objectstack (source: default — set PM_SWEEP_REPO to target another repo).
check-clause2-carriers: every row below is read from objectstack-ai/objectui (source: PM_SWEEP_REPO).
check-clause2-carriers: every row below is read from objectstack-ai/cloud (source: GITHUB_REPOSITORY).

It goes to stderr in every mode, --json included, for the reason the read-path report next door already states: stdout is contractually the ANSWER, and a provenance line on stdout would travel into the round report that pastes it as though it were part of a finding. It prints before the first request, so a run that dies in transport has still said which board it was about — the state the filing seat was in.

⚠️ Declared deviation from the re-grade's example spelling. The re-grade wrote the phrase as a suffix on the sweep headline (… in objectstack-ai/objectstack (source: …)). A separate first line was chosen instead because the headline is printed only by a sweep that reached the network: --pair — the invocation the skill tells seats to run before requesting review — prints no board name at all today, and neither does the PREREQUISITE NOT MET path. A banner covers all four modes and leaves the sweep headline byte-unchanged, so round reports that paste stdout are unaffected. The re-grade marked that spelling e.g.; if the seat wants the suffix instead, it is a one-line move.

2. --help / -h print usage and exit 0, before any network read. The usage text carries the env precedence, so the one action a seat takes to discover the mechanism no longer hides it behind a sweep. Measured: exit 0, zero GET /repos attempts.

3. An argument this file does not honour is refused before any read, naming it and printing the usage.

The usage message and the exit chosen

check-clause2-carriers: REFUSING — '--this-flag-does-not-exist' is not an argument this file honours.
  ⛔ Not a warning: an unrecognised argument used to change NOTHING — same report, same board, same exit —
  so a typo and a deliberate invocation were the same command. This is a landing pre-check with nothing
  behind it, and a well-formed report reads exactly like a clean one whatever you meant to ask for.

usage: node scripts/pm/check-clause2-carriers.mjs [--help] [-h] [--self-test] [--pair N] [--pair-json path] [--json]

  --help              print this text and exit 0, without reading any board
  -h                  the same
  --self-test         run this file's own battery, offline -- no board is read
  --pair N            judge ONE open PR by number -- a predicate about that pair
  --pair-json path    read the pair from a document (`-` = stdin) instead of the network
  --json              emit the sweep on stdout as JSON

  Which board is read is resolved PER RUN, not from this checkout -- this file reads no file in
  the tree at all. The precedence is `resolveSweepRepo`'s, imported from check-half-states.mjs:

    1. PM_SWEEP_REPO=owner/name    an explicit target; a sibling repo's seat uses this
    2. GITHUB_REPOSITORY           what Actions sets, i.e. the repo the workflow is installed in
    3. objectstack-ai/objectstack  the fallback, for a bare terminal

  Every run prints which of the three answered, so a deliberate target and the fallback are
  never the same line. ⛔ A report about the wrong board reads exactly like a report about this one.

Exit 1 (EXIT_USAGE), and why not 2. dispatch-gates.mjs uses process.exit(2) for the same refusal. Here 2 is EXIT_INCOMPLETE — a pinned verdict meaning UNJUDGED. Reusing it would let a seat reading $? read a mistyped invocation as an incomplete sweep, which is the exact confusion the file's exit-code table exists to prevent. So the register's own documented usage code is used, which is also what every pre-existing usage refusal in this file already returns (malformed --pair, unreadable --pair-json, the pair-json repo conflict, a malformed sweep repo). A self-test case pins EXIT_USAGE === 1 && EXIT_INCOMPLETE === 2 so the collision cannot be reintroduced silently. --help is not an error: it exits 0.

A repo-shaped positional is pointed at the mechanism rather than at --pair, because that is the confusion this change exists to end:

$ node scripts/pm/check-clause2-carriers.mjs --repo objectstack-ai/objectui
check-clause2-carriers: REFUSING — '--repo', 'objectstack-ai/objectui' are not arguments this file honours.
  'objectstack-ai/objectui' looks like a repo. The board is not an argument — set PM_SWEEP_REPO='objectstack-ai/objectui' to target it.

The known-flag table

KNOWN_FLAGS is the single source for both the parse and the usage text. Every parse site now looks its flag up through flagIndex, which throws on a flag absent from the table, so a flag the tool honours and does not document is unreachable rather than merely unlikely. The five converted sites were argv.includes('--self-test') (twice), argv.indexOf('--pair'), argv.indexOf('--pair-json') and argv.includes('--json').

flag value takes the next argument?
--help no
-h no
--self-test no
--pair N yes
--pair-json path yes (- = stdin)
--json no

A value-taking flag consumes the next token only when one is really there: a missing or flag-shaped value is left for that flag's own site to refuse with its own message, so --pair --json still reaches --pair's existing refusal and --json is not swallowed.

Self-test delta and the ablation

209 → 251 cases (both measured, not inferred: the base file was run from git show 0f07b2c86:… and removed). One new battery, the argv contract and the board provenance (#16623), declared floor 42 = its measured case count; SELF_TEST_BATTERY_FLOOR 13 → 14.

Ablation — neuter argvRefusalLines (return null first), from the committed state, with a trap … EXIT INT TERM restore on absolute paths:

leg evidence
HEAD blob a7dfc0e2252c46437d108ddb9134fe0805b9e2f1
working tree before a7dfc0e2252c46437d108ddb9134fe0805b9e2f1 (equal — tree was at HEAD)
mutation reached disk injected marker grep -c = 1; tree hash moved to bac0969c0c07fde275b8c79cf569258889cf01d2
baseline run exit 0, 251 pass
mutated run exit 1✗ check-clause2-carriers self-test: 11 of 251 case(s) failed.
restore git checkout HEAD -- (absolute path); hash back to a7dfc0e2…; marker count 0; git diff HEAD empty
post-restore run exit 0, 251 pass

Direction observed: turned RED, as predicted. No build/dist preflight applies — this script is executed from source by node scripts/pm/…, not resolved through a package's exports.

The five premises of the original dispatch, as measured

# premise verdict
1 file is 2600 lines, last touch c3ead5524; --repo 0 hits; process.argv 3 hits HOLDS. 2600 lines; c3ead5524 (#16604); --repo grep exit 1; process.argv at :2584 :2596 :2598 (the card's :2341 :2353 :2355 moved with #16604). ⚠️ The card's exit-0, byte-identical report could not be reproduced here: this container answers 403 to every GitHub read, so all three probes exit 3. What is reproduced is the defect itself — after normalising only the ephemeral proxy port, a bare run, --help, --this-flag-does-not-exist and --issue 7760 --repo objectstack-ai/objectui all hashed to 326175b3a7b77d87.
2 implement --repo mirroring dispatch-gates.mjs FELL, and was withdrawn. The board is already a parameter; a second spelling was refused by the re-grade. Replaced by the three items above.
3 self-test cases + one trap/restore ablation DONE — table above.
4 four sibling scripts measured, never counted DONE — table below.
5 no open PR names the path; merge-tree reading HOLDS — see below.

Premise 5, measured. list_pull_requests (open) returned 18 PRs; none names this path. Verified by file surface rather than by title: every open PR's head ref was fetched and diffed against its own merge base — zero of the 18 touch scripts/pm/ at all. (The first attempt returned merge-base exit 1 on all 18 because this is a shallow checkout; git fetch --deepen=300 was run and the control leg then answered exit 0, so the negative is real and not a shallow artifact.)

Merge-tree reading, before opening this PR:

git fetch origin main && git merge-tree --write-tree --name-only origin/main HEAD
→ exit 0, tree 96a316385a99d794eaaf1c0e815304892065f293, 0 conflicted paths

origin/main had moved 16 commits ahead and dispatch-gates printed a STALE TREE warning naming 14 changed derivation files, so origin/main was merged in (clean) and every reading below was retaken at the merged HEAD ca3b108b76, where the warning is gone.

Sibling readings — readings, never a count

The card listed four scripts/pm/** entries as same-shape residue and explicitly forbade quoting them as a count. Measured at final HEAD, ⛔ none edited:

script --repo objectstack-ai/objectui --this-flag-does-not-exist the two outputs
check-half-states.mjs exit 3 exit 3 byte-identical
check-governed-queue-guard.mjs exit 1 exit 1 byte-identical
bare-root-worklist.mjs exit 0 exit 0 byte-identical
ci-failure.mjs exit 3 exit 3 byte-identical

⚠️ Read these as readings of this container, not as verdicts. check-half-states and ci-failure exit 3 because the network is refused here, and check-governed-queue-guard exits 1 on a missing GITHUB_EVENT_PATH — in all three the environment answers before argv is ever judged, so these runs say nothing about whether those scripts would refuse a bad flag on a healthy container. Only bare-root-worklist.mjs — which reads the tree, not the network — is a clean reading of the same swallow: a full report, exit 0, identical for both probes. ⛔ No number of affected scripts is claimed, and no sibling was edited.

Gates — derived, never hand-listed

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at final HEAD ca3b108b76; --repo verdict line: --repo 'objectstack-ai/objectstack' checked against this checkout's 'origin' remote — it holds. Change set: 1 path, three-dot vs merge base 779710213. Every command run with its exit captured to a file before any pipe.

34 derived, 34 run, 0 unrun — reconciled, not asserted:

✓ dispatch-gates --ran: 34 derived famil(ies) accounted for — 34 run, 0 NOT-MEASURED.

All 34 exited 0. The families most directly implicated:

command exit verdict line
pnpm check:pm-clause2-carriers 0 ✓ check-clause2-carriers self-test: 251 cases pass
pnpm check:pm-dispatch-gates ✓ dispatch-gates self-test: 1534 cases pass. (0 failing-case lines)
node scripts/check-self-test-wired.mjs 0
node scripts/check-self-test-wired.mjs --self-test 0
pnpm check:nul-bytes 0
node scripts/check-scripts-symbol-anchors.mjs (+ --self-test) 0 / 0
node scripts/check-changeset-no-major.mjs --base origin/main 0
remaining 25 derived families 0

pnpm check:pm-dispatch-gates cannot finish inside the 600 s foreground cap (~10 min, 1534 cases), so it was started detached with its output redirected and blocked on in the foreground by its own PID (tail --pid=… -f /dev/null); its verdict is quoted from the gate's own printed line, never from $?. Run additionally, outside the derivation, because this file imports it: pnpm check:pm-widening-tells → exit 0, ✓ check-widening-tells self-test: 131 cases pass.

Governed-merges reading, quoted:

governed-surface predicate: 0 of 1 path(s) hit the register (5 surfaces, repo-agnostic).
  ✅  NOT governed — ordinary queue landing applies to a PR with exactly this file list.

Lint — a declared narrowing with its three evidence pieces. pnpm lint is eslint . --no-inline-config, a repo-wide scan CI owns. Narrowed to the one changed file:

  1. Population read from eslint's own config, not guessedeslint --print-config scripts/pm/check-clause2-carriers.mjs exits 0 and returns a real rule set (2 rules, sourceType: module), so the file is inside the configured population and is not ignored.
  2. File count read from --format jsoneslint scripts/pm/check-clause2-carriers.mjs --no-inline-config --format json → exit 0, files linted: 1, errors 0, warnings 0.
  3. Invariance for untouched fileseslint.config.mjs never enables type-aware linting: no parserOptions.project and no typed @typescript-eslint rules anywhere (grep returns only ecmaVersion/sourceType entries; the config states this at L326–329 with its own measured positive control). With no cross-file type program, a one-file diff cannot move the verdict on any file it does not contain.

⇒ The narrowing is a measurement, not a skip. The full repo scan remains CI's.

验收备注

  • --help on the four sibling scripts. bare-root-worklist.mjs is the clean reading above: exit 0 and a full report for any flag, on a script that reads the tree rather than the network. This PR ⛔ does not touch it — the re-grade scoped the work to one file. 承接者:seat, if it judges the same self-description is owed one script over. ⛔ Not filed as a card: it is one instance of a class the filer has already recorded on ⛔ CORRECTED BY FILER (see comment 5573481868) — the cross-repo capability EXISTS via PM_SWEEP_REPO; what remains is that check-clause2-carriers.mjs never names it and --help runs a sweep instead of saying so #16623 and asked not to be generalised into a count.
  • Provenance line placement. Declared above as a deviation from the re-grade's e.g. spelling, with the reason. 承接者:seat at ACCEPT.
  • The stale-tree warning the filer observed (dispatch-gates reporting the checkout ≥20 commits behind) reproduced here at 16 commits and was resolved by merging origin/main. It is checkout hygiene, ⛔ not a defect. 承接者:无.
  • --json stdout shape is unchanged. The provenance line is stderr, so machine consumers of --json see identical bytes. 承接者:无.

Landing

skip-changeset — nothing publishes from scripts/pm/**. Ungoverned: the seat reviews, flips ready and arms auto-merge. ⛔ This branch does not ready, enqueue, arm, request reviewers or submit a review. Clause-②: no per the claim, so no needs:contract-review.


Generated by Claude Code

os-musk and others added 2 commits September 7, 2026 22:35
…elp, and refuses an argument it does not honour

The board was already a parameter — `resolveSweepRepo` (imported from
check-half-states.mjs) resolves PM_SWEEP_REPO, else GITHUB_REPOSITORY, else the
default, and returns `source` alongside `repo`. The run simply never printed
`source`, so a deliberate target and the fallback rendered as the same sentence,
and a seat reading only this script concluded the board was hard-scoped.

Three changes, one file:

1. Every run opens with a provenance line naming the board AND on what basis it
   is the board, on stderr in every mode (stdout stays contractually the answer),
   printed before the first request so a run that dies in transport has still
   said which board it was about.
2. `--help` / `-h` print usage and exit 0 before any network read. The usage text
   carries the env precedence, so the one action a seat takes to discover the
   mechanism no longer hides it behind a sweep.
3. An argument this file does not honour is refused before any read, naming it
   and printing the usage, exiting 1 — the register's usage code, deliberately
   not dispatch-gates' 2, which here is the UNJUDGED verdict.

A KNOWN_FLAGS table is the single source for both the parse and the usage text:
every parse site looks its flag up through `flagIndex`, which throws on a flag
absent from the table, so a flag the tool honours and does not document is
unreachable rather than merely unlikely. A repo-shaped positional is pointed at
PM_SWEEP_REPO — the confusion this change exists to end.

No verdict, row, reader or `--pair` semantics changed. 42 self-test cases added
(251 pass, was 209).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018dxq7YqsLDMeZDZ5AzsgJX
@github-actions github-actions Bot added the size/m label Sep 7, 2026
@os-musk os-musk added skip-changeset PR has no user-facing published change; bypasses the changeset gate and removed size/m labels Sep 7, 2026 — with Claude

os-musk commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator Author

ACCEPT — flight O, PR #16699 at head ca3b108b76 (#16623 as re-graded at 5576224596: board provenance, --help, unknown-argument refusal), contract-tier review on the seat's own readings, 2026-09-07T22:56Z.

Own readings (review worktree detached at ca3b108b76, origin/main = 7797102139 = merge base):

  • Diff: one file, scripts/pm/check-clause2-carriers.mjs, +327/−8, one content commit plus a clean merge of main; no control bytes; git merge-tree --write-tree --name-only origin/main HEAD exit 0, tree 96a316385, no conflicted path.
  • Behavioural probes run here: --help and -h print usage: node scripts/pm/check-clause2-carriers.mjs [--help] [-h] [--self-test] [--pair N] [--pair-json …] … and exit 0 with zero GET /repos attempts; --this-flag-does-not-exist exits 1 with REFUSING — '--this-flag-does-not-exist' is not an argument this file honours and zero reads; --repo objectstack-ai/objectui exits 1 naming both tokens and zero reads; a bare run opens on stderr with every row below is read from objectstack-ai/objectstack (source: default — set PM_SWEEP_REPO to target another repo).
  • Self-tests: node scripts/pm/check-clause2-carriers.mjs --self-test251 cases pass (… the argv contract with its usage and its refusal, the board provenance line, and the exit register); pnpm check:pm-widening-tells → 131 cases pass (this file imports it). The register pin holds by the dev's own case: EXIT_USAGE === 1 && EXIT_INCOMPLETE === 2, so the refusal never collides with the UNJUDGED verdict.
  • check-governed-merges.mjs --test scripts/pm/check-clause2-carriers.mjs exit 0 — NOT governed, ordinary queue landing.
  • CI at 22:55Z: 34 of 36 check runs completed success/skipped, Lint & Repo Gates (carrying the 13-minute dispatch-gates self-test) and Type Check · consumer gates in progress, 0 failures; the dev's detached run of that self-test reads 1534 cases pass. The seat readies and arms auto-merge once every check run is complete — the next patrol.

Review checklist:

  • ① Derived judgments: the provenance banner as a separate first stderr line in every mode (the dev's option A) is accepted over the headline-suffix spelling the re-grade gave as an example — it reaches --pair, --json and the transport-failure path, which print no board today, and it is the shape dispatch-gates.mjs already uses; stdout is byte-unchanged so pasted sweep output is unaffected. KNOWN_FLAGS as the single source for parse and usage, with flagIndex throwing on a flag absent from the table, is the structural form of the refusal. No verdict, row, reader or --pair semantics moved; check-half-states.mjs, check-widening-tells.mjs, dispatch-gates.mjs untouched.
  • ② semver: none — a repo-internal PM gate, skip-changeset.
  • ③ Boundary flags: ungoverned. The sibling readings stay readings (a table, no count) as the filer asked; bare-root-worklist.mjs showing the same swallow on a tree-reading script is noted for a later card, not widened here — 承接者: this seat, next shift. The card's original premise fell before any edit, as the report says; the landed shape is the re-graded one.

Implemented-by: os-dev subagent a08bc59eb628491a1 (container & tier as recorded on the claim 5573140836 on #16623; held on the filer's correction, then ran the re-graded shape)
Reviewed-by: domain:skills seat, session session_018dxq7YqsLDMeZDZ5AzsgJX, at CONTRACT_REVIEW_TIER (own get_session reading: session_context.model = last_served_model)

Landing: ready → enable_pr_auto_merge (SQUASH) → queue-ref probe, by the seat, once every check run on this head is complete.


Generated by Claude Code

@os-musk
os-musk marked this pull request as ready for review September 7, 2026 23:46
@os-musk
os-musk enabled auto-merge September 7, 2026 23:46
@os-musk
os-musk added this pull request to the merge queue Sep 7, 2026
Merged via the queue into main with commit a0856e3 Sep 8, 2026
39 checks passed
@os-musk
os-musk deleted the claude/issue-16623-clause2-carriers-repo-refusal branch September 8, 2026 00:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

1 participant