Skip to content

fix(pipeline): the required review set can no longer shrink in silence (#4520, #4730) - #4742

Merged
usirin merged 2 commits into
mainfrom
usirin/fix-4520-4730-required-set-coverage-E69D41F9
Aug 2, 2026
Merged

fix(pipeline): the required review set can no longer shrink in silence (#4520, #4730)#4742
usirin merged 2 commits into
mainfrom
usirin/fix-4520-4730-required-set-coverage-E69D41F9

Conversation

@usirin

@usirin usirin commented Aug 2, 2026

Copy link
Copy Markdown
Member

Fixes #4520
Fixes #4730

#4729 is a duplicate of #4520 and is NOT closed by this PR — see Loose end at the bottom.

Two ends of one too-small required-review set, on the merge-authorization path. They land
together because neither half is the whole hazard.


Part A — verdict gate dropped a namespace on a repeated --require (#4520)

--require was declared Flag.string("require")single-valued. A repeated flag kept the
first occurrence and discarded the rest in silence, and the gate still answered
enqueueable: true, exit 0.

Reproduced against PR #4724 at its merged head, where review-doc has no verdict at all:

invocation answer
--require review-code --require review-doc enqueueable: true, exit 0 — review-doc never mentioned
--require review-code,review-doc enqueueable: false, exit 1 — refuses on the absent review-doc

The identical required set, opposite answers, decided by spelling alone. Both prior reports
happened to test with every named namespace passing, which is why it stayed invisible.

The guard that should have caught it was already there and was structurally unreachable.
parseRequired refuses an unrecognized token with "Refusing to drop it from the required set
(that would shrink the gate conjunction)"
— its docblock names shrinking the conjunction as the
exact fail-open this verb exists to prevent. It only ever received one occurrence's string,
so for occurrence 2..n it never ran. Born dead, not drifted. So this is not "add a guard".

The fix: UNION, via Flag.atLeast(1)

Grounded in the flag API, not memory — effect/unstable/cli's Flag exposes atLeast /
atMost / between under @category repetition, and atLeast(1) turns Flag<string> into
Flag<ReadonlyArray<string>> collecting every occurrence while keeping the flag mandatory
(packages/pipeline-cli pins effect@4.0.0-beta.92; the combinator delegates to
Param.atLeast).

Union over usage-error because union is the shape that makes the two spellings the same
input
rather than one legal and one rejected — the caller's intent is unambiguous, and a usage
error would break every existing correct repeated-flag caller for no safety gain. A later
occurrence's bogus token now reaches the existing token guard instead of vanishing upstream of
it, which is the property that guard was written for.

Verified live on this branch, both spellings against PR #4724:

--require review-code --require review-doc   → enqueueable:false, exit 1, [required: review-code + review-doc]
--require review-code,review-doc             → enqueueable:false, exit 1, [required: review-code + review-doc]
--require review-code --require bogus        → refused: unrecognized required namespace 'bogus' …

The independent check — coverageDefect

An affirmative gate answer must be about as many distinct namespaces as argv asked about; if
decisions.length and the parsed distinct set disagree, the pass is refused and the reason names
what was never decided.

This is the part that outlives the spelling. Union-or-error fixes this path; the assertion
refuses any future path that answers about fewer things than it was asked — the general form
of a defect whose signature is a plausible value rather than an error: it runs, exits clean,
returns a well-formed set, just a smaller one, so every natural guard (did it run? exit 0? return
a set?) is satisfied by the wrong answer.

Its operands come from two different origins deliberately: the CLI's own parse of argv
against the decision returned over the Github service boundary. An assertion that re-derived
decisions from requiredGates inside decideGate would be true by construction and could
never fire. It is applied to a pass only — a refusal already refuses, and its own reason is
the more useful one.


Part B — ship-it Step 0 under-reported the class set (#4730)

step0-classify.sh carried a hand-rolled reimplementation of the §CLASS probes: three
grep -Eq class tests plus a UI test, over four re-resolved boundary regexes. A changed file
matching none of the three produced no output at all, and class-probe does the opposite in
code — an unclassified file rides has-code. So the shell could only ever answer a strict
subset
, never a superset. The #2765 no-class rule sat in that file as five comment lines and
zero executable ones.

Step 0 now prints class-probe classify's output. That is not a redesign — the commit that
closed #2765 states the rule "lives once in the shared class-probe core that ship-it Step 0 and
the reviewer fan both run", i.e. Step 0 was always meant to delegate. It is also the verb Step 2
already re-derives the required set from, so Step 0's printed set and Step 2's enforced set are
now identical by construction instead of by matched maintenance. Two implementations of a
merge-gating question is worse than either bug.

The no-class rule landed as executable code, not a second comment. It is a real guard, not a
restatement: it compares the §CPREAD file count ($CP_FILES_N, already proven ≥ 1 —
cp_changed_files fails closed on a zero-length list) against class-probe's stdout. Two
origins, so it can fire. A non-empty diff whose class set comes back empty prints BLOCKING +
STOP and exits 1 — UNKNOWN, never "no gates required".

The regression harness, and why its fixture is the point

claude-plugins/kampus-pipeline/skills/ship-it/scripts/verify-step0-class-parity.sh — hermetic,
reviewer-runnable, asserts Step 0's printed class words equal the real class-probe's over
the same list.

The failure mode is silence and silence subtracts, so a fixture that always classifies as
something proves nothing. Case 1 is therefore the plugin-docs-beside-skills shape — a plugin
home's README.md + docs/** beside its skills/**, files that classify as nothing under
all three predicates. Case 2 is a diff where every file classifies, kept so a green case 1 is
visibly discriminating rather than the harness being green on everything.

The stub serves the real gh-issue-intake-formats.md / ship-it/SKILL.md bytes for the two
contents/ reads, not an empty body — an empty body would hand a re-deriving step the
.-matches-everything fail-closed sentinel, which over-dispatches and would mask the very
subtraction under test.

Falsifiability, measured rather than argued. The pre-fix script run under this harness's stub
on case 1's fixture prints has-skills alone, against class-probe's has-code, has-skills
the harness fails on it. Post-fix it prints has-code, has-skills, matching #4730's AC2 (a
live run against PR #4724's 11-file list gives the same).

The drift anchor is intact

Removing the HAS_*_RE / UI_RE literals from this script does not remove drift detection.
validate-gate-path-drift.sh locks each boundary's canonical (invariants 1b/1d, in
gh-issue-intake-formats.md §CLASS and ship-it/SKILL.md) against the typed const in
packages/pipeline-cli/src/gate-boundaries.ts; 1c value-locks whatever copies exist. A copy that
no longer exists cannot drift, and the anchor it was checked against is untouched. The guard runs
green on this branch — 34 checks, including UI_RE / UI_EXCLUDE_RE canonical-appears-once in
ship-it/SKILL.md, which this PR does not touch.


What this PR does NOT claim

The executed gate was not weakened by the Step-0 half alone. ship-it Step 2 re-derives the
required set from the CLI rather than trusting the carried one, and no executable consumer of
step0-classify exists. The exposure was the prose contract telling an agent to carry the
class set forward, and turning it live needed a second deviation — which is Part A.

So the pair is the urgency; neither half alone demonstrates an un-gated merge end to end.
That is why #4730 priced p1 and #4520 p0. This PR does not claim either half "would have
merged a FAIL" on its own.

The reading contract in ship-it/SKILL.md ("carry the class set into Step 2") is left unchanged
and is now correct as written, because Step 0 prints the same verb's answer Step 2 re-derives
from. That also keeps this PR inside the freeze exception's scope.


Scope, freeze exception, and §CP

  • v1 freeze exception. The in-place edit to claude-plugins/kampus-pipeline/skills/ship-it/
    runs under the founder's explicit fabrika execution core: the ~16 pipeline skills rebuilt skill-first, contract-driven #4650 No-go exception recorded on ship-it Step 0 shell probe under-reports the class set vs class-probe #4730, scoped to this
    defect only
    — not a general licence to edit v1. Nothing outside the Step-0 classifier and its
    new sibling harness is touched in that tree.
  • CONTROL-PLANE — human approval required at head. Verified, not assumed:
    pipeline-cli cp-classify classify --files-file <this PR's file list> returns
    control-plane [path-match] — BLOCKING (human merge), on
    claude-plugins/kampus-pipeline/skills/ship-it/scripts/step0-classify.sh matching the live
    CONTROL_PLANE_RE. This PR needs a @kamp-us/control-plane approval at head and cannot
    auto-merge on a review PASS.

Verification run on this branch

  • pipeline-cli suite: 3180 tests / 191 files pass, including the new cases (flag-shape
    parity, the repeated-shape acceptance set, coverageDefect).
  • typecheck clean; biome check clean; shellcheck -x clean on both scripts.
  • verify-step0-class-parity.sh: OK, 2 cases.
  • verify-chain-resolves.sh: PASS (static, executable, and the $CP_FLAG seam).
  • validate-gate-path-drift.sh: OK, 34 checks.
  • trap-status-guard check over the plugin corpus: clean (349 files).
  • gh-phoenix lint-skills over the corpus: clean.
  • Live verdict gate runs against PR feat(fabrika): the /adr skill and its derived CLI contract — wave-0 pilot (#4704) #4724 in both spellings, as tabled above.

Loose end for the dispatcher — #4729 not closed

#4729 (duplicate of #4520) is still open. Closing it is a number-targeting mutation and the
write-code claim guard refused it, verbatim:

{"issue":4729,"mine":false,"reason":"no-winner","winner":null,"superseded":[]}
claim: #4729: no authorized claim resolves — NOT mine, back off (default-deny, never a false win).

I stopped rather than reasoning past a fail-closed refusal. It needs closing as a duplicate with
a pointer to #4520 by whoever holds that lane. It is deliberately not linked with a Fixes
keyword here, so nothing closes it silently on merge.


Deviations

This section was owed at PR-open (§DEV) and was not written — round 1 rendered no such row, so this
is a skipped step, not a prior [N/A]. It is added in a body-only repair round; the head has not
moved, so every entry below describes the round-1 diff as it stands.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

No preview deploy

  • No preview deploy for this PR — its diff touches no deploy-relevant path, so no preview stack was minted and e2e is not applicable. (0a875ac)
  • web — Stage pr-4742 torn down.

@usirin

usirin commented Aug 2, 2026

Copy link
Copy Markdown
Member Author

review-code: advisory — blocking-set PR (§CP — approval-gated)

PR #4742 is §CP — it touches the control plane (claude-plugins/kampus-pipeline/skills/ship-it/scripts/step0-classify.sh, a gate-critical skill — ADR 0053/0065/0073). Verified, not assumed: pipeline-cli cp-classify classify over this PR's 5-file list returns control-plane [path-match] — BLOCKING (human merge). My verdict is advisory only: it does not authorize a merge. Under the §CP hard gate (ADR 0135), a @kamp-us/control-plane member approves this at its current head and ship-it then enqueues it (ADR 0048 single merge authority) — there is no human hand-merge in the §CP path.

Reviewed-head: @ 326721b

Verified against #4520's acceptance criteria, one at a time, against the PR head in an isolated worktree — all pass:

Acceptance criteria — #4520

  • [PASS] A repeated --require flag can no longer silently shrink the required set: it either accumulates into the union of all occurrences or hard-errors. — union, via Flag.atLeast(1) (command.ts requireFlag) turning Flag<string> into Flag<ReadonlyArray<string>>; parseRequiredGates (gate-decision.ts) flatMaps every occurrence before tokenizing. The API claim was checked against the pinned source, not memory: effect@4.0.0-beta.92, src/unstable/cli/Flag.ts L1163–1208 — atLeast is @category repetition, typed <A>(min: number): (self: Flag<A>) => Flag<ReadonlyArray<A>>, dual(2, …) delegating to Param.atLeast. Mandatory-ness is preserved: omitting the flag entirely still errors Missing required flag: --require (live run at head).
  • [PASS] Unit test pinning flag-shape parity, including a later occurrence carrying an invalid token — it must reach the token guard, never vanish upstream.gate-decision.unit.test.ts, describe("parseRequiredGates — flag-shape parity (#4520)"): repeated ≡ comma, order-blind union, bogus-in-later-occurrence reaches the shrink the gate conjunction refusal, bogus-in-first still refuses. Confirmed live at head: --require review-code --require bogusunrecognized required namespace 'bogus' … Refusing to drop it from the required set, exit 1.
  • [PASS] The gate's fail-closed property holds under the repeated shape: with a live FAIL (or absent verdict) in any later-occurrence namespace, the gate refuses. — live at head against PR feat(fabrika): the /adr skill and its derived CLI contract — wave-0 pilot (#4704) #4724 (where review-doc has no verdict at all): --require review-code --require review-doc and --require review-code,review-doc produced byte-identical JSON and both exited 1 with refused — unverified (no review-doc PASS) … [required: review-code + review-doc; head e3efe5fb…]. The single-occurrence shape the real consumer uses is unregressed: step2-verdict-gate.sh kamp-us/phoenix 4724 runs clean at head, deriving 2 namespaces and passing them as one occurrence.

Checks — my own runs at this head, not a citation of the PR's

  • tsgo -p tsconfig.json in packages/pipeline-cli — exit 0.
  • packages/pipeline-cli suite — 3180 tests / 191 files pass.
  • biome check . — exit 0; changed surfaces clean.
  • shellcheck -x on both scripts — exit 0 each.
  • validate-gate-path-drift.sh — OK, 34 checks. validate-skills.sh — OK, 30 skills. verify-chain-resolves.sh — PASS. trap-status-guard check over the plugin corpus — clean, 349 files. validate-cycle-{absence,presence}.sh — OK.
  • Every dep still catalog:; effect pinned 4.0.0-beta.92 in pnpm-workspace.yaml. No home/local/absolute/sibling path and no operator identity in any changed file.

Run-evidence bundle: PRESENT for head 326721bc — producer run 30736951047, artifact 8829973687, manifest.commit == head, schemaVersion 1; checks 2/2 pass; tests 2420/2420 passed, 0 failed, 0 skipped.

The narrowing — checked, and the body does not overstate it

The PR body states the limit correctly and I verified each half independently: step2-verdict-gate.sh re-derives the required set from class-probe classify --namespaces rather than trusting a carried one, and a repo-wide search finds no executable consumer of step0-classify. The body says so verbatim and adds "This PR does not claim either half 'would have merged a FAIL' on its own" — no standalone un-gated-merge claim is made. The framing matches the evidence.


Advisory findings — none blocking; recorded for the control-plane approver

  1. coverageDefect is a seam postcondition that cannot fire against the shipped implementation. Github.gate passes requiredGates straight through, and decideGate builds decisions = [...new Set(input.requiredGates)].map(…) — so coverageDefect(requiredGates, decision) is identically null for every reachable input today. I checked this rather than accepting that it is positioned to fire. It is not the born-dead shape Part A fixes: parseRequired's old guard was unreachable within one call frame because Flag.string could never hand it a second occurrence, whereas this one constrains an interface whose implementations (including test doubles and any future short-circuiting refactor) can violate it. That is an ordinary unfired postcondition, not a dead guard, and it can only ever refuse — zero false-pass risk. But the tests prove only the pure predicate; nothing stands the wired call site up against a Github double returning an under-covered decision, so the guard's reachability is unpinned. Suggested follow-up (not a merge blocker): one command-level test with such a double.
  2. An unfounded citation. coverageDefect's docblock says the two-origins placement follows ".patterns/skill-script-shell-shape.md's rule, same idea". That doc carries no such rule — grepping .patterns/ and .decisions/ for "two different origins" / "from two origins" returns nothing. In a PR whose subject is not over-claiming, a pointer to a rule that isn't there is worth correcting; the reasoning it summarizes is sound on its own and needs no citation.
  3. On the coverage-refusal path, stdout still says "enqueueable":true while the process exits 1. Console.log(JSON.stringify(decision)) runs before the coverage check, so that one path emits an affirmative JSON body under a non-zero exit. No live consumer is affected — step2-verdict-gate.sh reads the exit status only, and the path is unreachable today (finding 1) — but "the permissive answer is byte-shaped like a legitimate pass" is this PR's own subject, and this is the only path where the printed artifact and the verdict disagree.

My verdict authorizes nothing. A @kamp-us/control-plane approval at head 326721bc6881a6b3713f69a91fa4aef86d3f3117 is what authorizes this merge; I neither merged nor sought that approval.

Verdict-written: 2026-08-02T07:17:48Z

@usirin

usirin commented Aug 2, 2026

Copy link
Copy Markdown
Member Author

review-skill: advisory — blocking-set PR (§CP — approval-gated)

PR #4742 touches the control plane (a gate-critical skill: claude-plugins/kampus-pipeline/skills/ship-it/scripts/step0-classify.sh + its new sibling harness — ADR 0053/0065/0073). My verdict is advisory only: it does not authorize a merge. Under the §CP hard gate (ADR 0135), a @kamp-us/control-plane member approves this at its current head and ship-it then enqueues it (ADR 0048 single merge authority) — there is no human hand-merge in the §CP path.

Reviewed-head: @ 326721b

Verified against #4730's acceptance criteria + the skill-rigor checklist, at the PR head — all checks pass:

Acceptance criteria — #4730

  • [PASS] Step 0's printed class set equals class-probe classify's, including on a diff containing a file that matches none of the three §CLASS probes.step0-classify.sh no longer derives a set; it prints class-probe classify's stdout verbatim, so equality holds by construction rather than by matched maintenance. I ran the new harness rather than trusting it: verify-step0-class-parity.shOK — 2 case(s), case 1 being the plugin-docs-beside-skills fixture (README.md + docs/** beside skills/**) whose first three files classify as nothing.
  • [PASS] On PR feat(fabrika): the /adr skill and its derived CLI contract — wave-0 pilot (#4704) #4724's 11-file list, Step 0 prints has-code and has-skills (currently has-skills alone). — live run at head: step0-classify.sh kamp-us/phoenix 4724CP_STATE=not-control-plane, then has-code / has-skills, exit 0.
  • [PASS] The class-probe has no HAS_* class for root-level lint/build tooling (biome-plugins/**, biome.jsonc) — such PRs can ship with zero required review gate #2765 no-class rule is either executable in the script or provably unnecessary because the script no longer derives the class set. No rule survives as prose-without-code.both limbs hold. The derivation is gone (so the rule is discharged where it lived), and the residual guard is real executable code: if [ -z "$CLASS_OUT" ]BLOCKING + STOP + exit 1, with a second executable guard on class-probe's non-zero exit. I confirmed the operands are genuinely two-origin and the emptiness test is live: class-probe classify writes its class-probe: N changed file(s) → … scope line to stderr and only class words to stdout, so $CLASS_OUT is not unconditionally non-empty — the guard is testable, not decorative.
  • [PASS] The validate-gate-path-drift lockstep anchor on the HAS_*_RE literals is preserved or explicitly re-homed; the fix must not remove drift detection as a side effect.validate-gate-path-drift.shOK — 34 checks, including canonical UI_RE appears exactly once in SKILL.md, canonical UI_EXCLUDE_RE appears exactly once in SKILL.md, and the corpus-copy value locks. The canonical single sources (gh-issue-intake-formats.md §CLASS, ship-it/SKILL.md) and the typed packages/pipeline-cli/src/gate-boundaries.ts are untouched; only a copy was removed, and a copy that no longer exists cannot drift.
  • [PASS] A regression case covering the plugin-docs-beside-skills shape is added, so the divergence cannot silently return.falsifiability measured, not argued. I staged the pre-fix step0-classify.sh (base 042e4697) beside the new harness and ran it: BAD plugin docs beside skills (#4730) step 0 and class-probe DISAGREE / step0: has-skills / class-probe: has-code has-skills, harness exit 1. The control case (every file classifies) passed on both versions, so case 1 is genuinely discriminating rather than the harness being green on everything. Post-fix both cases pass. See finding 1 below on CI wiring.
  • [—] The fix PR carries a §CP human approval at head. — a merge-time condition, not discharged by this gate; cp-classify returning control-plane [path-match] is confirmed above.

Skill rigor

  • [PASS] Behavioral correctness — exercised, not inferred. Step 0 runs clean against a live PR; the parity harness passes at head and fails against the pre-fix script; the real downstream consumer step2-verdict-gate.sh runs clean at head; verify-chain-resolves.shPASS (static, executable, and the $CP_FLAG seam). The dual-mode contract is intact: set -uo pipefail in executed mode only, no EXIT trap, defaulted expansions — shellcheck -x exit 0 on both scripts and trap-status-guard check clean over 349 corpus files.
  • [PASS] Trigger / description quality — both scripts carry an accurate header contract (dual-mode note, stdout channel, usage line). validate-skills.shOK — 30 skills valid; CI's validate skill frontmatter and skill-corpus lint both pass.
  • [PASS] Cross-skill conflict / shadowing — this removes a shadow rather than adding one: Step 0 and Step 2 now answer from the same verb. The new harness is a leaf with no callers into the step chain; the cycle validators pick it up in ship-it's scanned scope and both pass.
  • [PASS] Gate-invariant preservation — walked, none weakened, and the fail-closed direction strictly tightens. Old Step 0 could only under-report (silence subtracts); new Step 0 prints a set that is ≥ the old one on every input, and it adds two hard stops that did not exist (class-probe non-zero → BLOCKING+STOP+exit 1; empty class set → same). The §CP half, the CP_STATE state-word assertion, the ADR-0164 content clause and the cp_changed_files/cp_head_sha §CPREAD guards are all untouched. ship-it/SKILL.md is not edited — I confirmed the diff is 5 files and none is a SKILL.md — and I checked its prose is still correct as written: the Step 0 stdout fence (CP_STATE=, BLOCKING (…), one class word per class, has-ui, STOP:/exit 1) matches exactly what the new script emits, and "Carry the class set … into Step 2" is now more correct than before, since the carried set and Step 2's re-derived set come from one verb. Leaving SKILL.md alone also keeps the change inside the fabrika execution core: the ~16 pipeline skills rebuilt skill-first, contract-driven #4650 freeze exception scoped to this defect.

Advisory findings — none blocking; recorded for the control-plane approver

  1. verify-step0-class-parity.sh is wired into nothing. It is referenced by no CI job, no validator, and not by ship-it/SKILL.md — the only occurrences of its name in the repo are inside the file itself. Its sibling verify-chain-resolves.sh is a CI step (.github/workflows/ci.yml). A regression harness nobody runs is the shape this PR is fixing elsewhere, and it will bit-rot. This is not a blocker because the divergence is closed by construction — there is no second derivation left to drift — so the harness is belt-and-braces rather than the load-bearing guard. Suggested follow-up: one line in ci.yml beside verify-chain-resolves.sh; it is hermetic (stubbed gh, no auth) and runs in seconds.
  2. Step 0's §CLASS boundary provenance changed, though not its answer. The removed shell resolved the HAS_*_RE / UI_RE lines via gh api contents/…?ref=main — the explicit ship-it/review-code §CP decision runs off the injected skill snapshot — a stale snapshot auto-merged a now-control-plane guard-pkg PR (#830 across the ADR-0100 boundary) #981 anti-self-authorization read. class-probe instead parses §CLASS from the local gh-issue-intake-formats.md (--root, else walk up). This is not a weakening here: Step 2 already delegated to class-probe, so the enforced set already had this provenance, and ship-it runs from a checkout at main, so local ≡ main in its operating context. Flagging it because packages/pipeline-cli/src/gate-boundaries.ts's docblock still asserts "the merge-deciding gates still re-resolve these patterns from the prose on origin/main" — a statement now in tension with the delegation path. Pre-existing on main, not introduced by this PR; worth a separate ticket.
  3. A line of ship-it/SKILL.md prose is now stale (near the UI_RE / UI_EXCLUDE_RE fence): "The classification shell that consumes them moved into a sourced script" — that shell no longer consumes them at all. Correctly left untouched to stay inside the freeze exception's scope; noting it so it is not lost.

My verdict authorizes nothing. A @kamp-us/control-plane approval at head 326721bc6881a6b3713f69a91fa4aef86d3f3117 is what authorizes this merge; I neither merged nor sought that approval.

Verdict-written: 2026-08-02T07:17:59Z

usirin and others added 2 commits August 2, 2026 00:26
#4520, #4730)

Two ends of one too-small required-review set, on the merge-authorization path.

`verdict gate --require` was a single-valued `Flag.string`, so a repeated flag kept
only the first occurrence and dropped the rest without a word: `--require review-code
--require review-doc` cleared PR #4724 as enqueueable while `--require
review-code,review-doc` refused on an absent `review-doc` — the same required set,
opposite answers, decided by spelling. The `parseRequired` guard one layer below
already refuses to shrink the conjunction; it never saw occurrence 2..n, so it was
born dead rather than drifted. The flag is now `Flag.atLeast(1)` and every occurrence
unions in, which also routes a later occurrence's bogus token back into that guard.

Beside it, a coverage assertion: an affirmative gate answer must cover as many
distinct namespaces as argv asked about. That is the general form — it refuses any
future path that answers about fewer things than it was asked, which is the shape of
this whole class (a check that runs, exits clean, and returns a plausible value that
is simply smaller). Its operands come from two origins on purpose: the CLI's parse of
argv against the decision returned over the service boundary.

ship-it Step 0 re-derived the artifact-class set instead of delegating to
`class-probe`, and its copy could only ever answer with a SUBSET: a file matching none
of the three predicates produced silence, and silence subtracts a class. On PR #4724's
list it printed `has-skills` alone where the probe printed `has-code, has-skills` —
the plugin-docs-beside-skills shape any plugin home reproduces. The #2765 no-class
rule lived there as five comment lines and zero executable ones. Step 0 now prints
`class-probe`'s answer, which is what #2765's own commit said it was always meant to
do, and the no-class rule lands as an executable fail-closed branch keyed on the
§CPREAD file count against the probe's stdout.

Scope note: the executed gate was not weakened by the Step-0 half alone — Step 2
re-derives the set from the CLI and no executable consumer of `step0-classify` exists.
The exposure was the prose contract telling an agent to carry Step 0's set forward,
and turning it live took a second deviation, which is the `--require` defect. Neither
half demonstrates an un-gated merge on its own; the pair is the urgency.

In-place edit to the frozen v1 `claude-plugins/kampus-pipeline/` corpus under the
founder's #4650 No-go exception, scoped to this defect only (recorded on #4730).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… invocation

`cli-invocation-guard` scans a `.sh` file as one implicit runnable fence, so the
CLI's name in a leading word position — even inside an `echo`'d diagnostic that
executes nothing — matches BARE_INVOCATION and reds the gate. Lead with what is
wrong instead, leaving the single-source shim path in the `/`-prefixed position
the matcher excludes; the message keeps the path and gains the remedy.

The resolution itself is untouched: PCLI is already resolved by path and called
as "$PCLI", the canonical shape §CLI asks for. #4432 owns removing the need for
this reword.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@usirin

usirin commented Aug 2, 2026

Copy link
Copy Markdown
Member Author

review-code: advisory — blocking-set PR (§CP — approval-gated)

PR #4742 is §CP — it touches the control plane (claude-plugins/kampus-pipeline/skills/ship-it/scripts/step0-classify.sh, a gate-critical skill — ADR 0053/0065/0073). Verified, not assumed: pipeline-cli cp-classify classify over this PR's live 5-file list returns control-plane [path-match] — BLOCKING (human merge), on step0-classify.sh matching the live CONTROL_PLANE_RE. My verdict is advisory only: it does not authorize a merge. Under the §CP hard gate (ADR 0135) a @kamp-us/control-plane member approves this at its current head and ship-it then enqueues it (ADR 0048, single merge authority). I neither merged nor sought that approval.

Reviewed-head: @ 0a875ac


Round 4 — body-only re-gate at the same head. This clears.

The head has not moved. 0a875ac9… is the SHA all three prior verdicts bound to, and the only change since round 3 is a PR-body edit. This verdict upserts round 3's record at that same SHA (ADR 0058 rule 2 / ADR 0213 — same PR, gate, head, run). It does not stack a new one.

Why the form returns to the advisory. Round 3 emitted a review-code FAIL rather than the advisory because ADR 0226 makes the advisory a PASS path only — an advisory carrying a failing criterion resolves to unverified in decideNamespace's §CP arm (failCheckboxRe, gate-decision.ts:51/174), which is neither shippable nor pickable for repair. Round 3's single failing row is now discharged and every criterion passes, so 0226 puts the form back to the canonical advisory.

What round 3 held on, and its discharge

Round 3 passed every acceptance criterion of #4520 and #4730 and failed on exactly one row: deviation-disclosure (§DEV). Three disclosed class-3 defects had no filed follow-up, and §DEV's judgment branch is explicit — a class-3 defect with no filed issue is a failing row. The remedy I named was body-only: file one issue per outstanding item and cite the numbers in each Disposition.

Discharged, and verified against the issues themselves rather than against the body's say-so. All five exist, are open, and carry status:needs-triage and nothing else — no type, no priority, correctly left for triage:

None is a thin ticket. Each is actionable cold by a stranger: file, line, quote, why-it-matters, and a named fix shape. That is the bar the disclosure had to reach, and it reaches it.

The two advisory findings round 3 carried are now folded in as entries 6 and 7#4743 (merge-boundary class resolution reads the local file rather than origin/main, the #981 anti-self-authorization read) and #4744 (verify-step0-class-parity.sh is wired into no job). Both are correctly marked non-blocking with durable homes. #4743 states in its own body that it is pre-existing on main and is filed separately rather than folded in, and #4744 names the sibling verify-chain-resolves.sh CI step as the idiom it missed. The disclosure is now complete rather than partial.

The splice was additive — checked, not accepted

  • [PASS] The pre-existing body was not reworded. All six original ## headings sit at their round-3 line numbers, unmoved (11 / 75 / 131 / 148 / 161 / 173), ## Deviations is still the seventh at 189, and the growth is entirely below it (footer 246 → 267). Every passage rounds 1–3 quoted is present verbatim — the Loose end for the dispatcher paragraph, the §CP paragraph, the freeze-exception scoping, the Deviations preamble, and entries 1 and 2 including entry 2's self-named gap ("nothing in this PR stands the wired call site up against a Github double … Coverage of the wired path is not delivered here").
  • [PASS] The narrowing in What this PR does NOT claim is intact. Verbatim at head: "ship-it Step 2 re-derives the required set from the CLI rather than trusting the carried one, and no executable consumer of step0-classify exists", "the pair is the urgency, neither half alone demonstrates an un-gated merge end to end", and "This PR does not claim either half 'would have merged a FAIL' on its own."
  • [PASS] No standalone un-gated-merge claim was introduced. A grep of the whole body for the phrase returns exactly one hit — body line 140, the closing half of that negation. The load-bearing narrowing survived the edit untouched.
  • [PASS] Entries 3, 4 and 5 were appended to, not rewritten. Each carries its original Said/Did/Why/Disposition text with a single trailing Filed as #NNNN. — the numbers match the issues that exist, and each issue's content matches the entry it discharges.
  • [PASS] No local paths, no operator identity. A scan of the body for home, machine-local and sibling-clone path shapes and for operator names or emails returns zero hits.

The tree and CI are where they were

  • [PASS] Still the same 5-file diff. pulls/4742/files at head returns exactly ship-it/scripts/step0-classify.sh, ship-it/scripts/verify-step0-class-parity.sh, verdict/command.ts, verdict/gate-decision.ts, verdict/gate-decision.unit.test.ts387 insertions, 108 deletions, matching the totals of rounds 2 and 3 exactly.
  • [PASS] Required-namespace set unchanged. class-probe classify --namespaces over the live file list: has-code, has-skillsreview-code + review-skill, exit 0, no review-design. Both markers land this round, and a coverage self-check confirms each namespace carries a head-bound verdict before this review is reported complete.
  • [PASS] CI at head, read directly from check-runs and counted. total_count 46, entries actually received 46 — the page is complete, not truncated. All 46 completed: 42 success, 4 skipped, zero failures, zero in_progress, zero queued. The legacy combined-status endpoint reports state: pending, total_count: 0 on this commit. That is an empty statuses list, not a pending check — every gate here is a check-run.

Carried forward from rounds 1–3 — the tree is byte-identical, so these stand

Not re-derived, and not re-asserted as fresh work: every acceptance criterion of #4520 (the Flag.atLeast(1) union grounded in the pinned effect@4.0.0-beta.92 source, the flag-shape-parity tests including a bogus token in a later occurrence reaching the guard, the fail-closed property under both spellings against PR #4724) and of #4730 (Step 0 delegating to class-probe, the no-class rule landed as executable code with two origins, the parity harness whose case-1 fixture was measured to fail against the pre-fix script), plus the byte-identical rebase, coverageDefect's inertness at source, the third zero-hit grep, the run-evidence bundle (present, manifest.commit == 0a875ac9…, 2420/2420 tests), and the clean drift, skill, trap and leak validators.

Sub-gates

A deviation-disclosure: PASS row means nothing undisclosed that this gate could see, never no deviations exist. Class 3 is Tier D, disclosure-only. What the section buys is that these seven now have a named place on the board instead of dying in a squash message.


Overall — clear

Every acceptance criterion of #4520 and #4730 passes. The body edit is verifiably additive and the narrowing survived it intact. The three disclosed class-3 defects are filed as #4745 / #4746 / #4747, each substantive enough to act on cold, with #4743 / #4744 folded in as non-blocking entries with durable homes. The tree is unchanged and CI is genuinely green at 46 of 46 received. Nothing is outstanding on the review-code class.

This authorizes nothing. A @kamp-us/control-plane approval at head 0a875ac97e877f8dd9e26a6fc99f06a3b70449df is what authorizes this merge, and ship-it is the only thing that merges it. There are zero reviews on this PR as of this verdict.

Note on tooling, not a finding: .claude/.pipeline does not exist in a spawned worktree, so the skill's literal ./.claude/.pipeline/… script paths exit 127 there. That is UNKNOWN, not a failing check (§ZS) — every verb above was run from the worktree's own tracked claude-plugins/kampus-pipeline/… copy. Already tracked as a live recurrence on #4666, not re-filed.

Verdict-written: round 4, body-only re-gate at 0a875ac

@usirin

usirin commented Aug 2, 2026

Copy link
Copy Markdown
Member Author

review-skill: advisory — blocking-set PR (§CP — approval-gated)

PR #4742 is §CP — it touches the control plane (claude-plugins/kampus-pipeline/skills/ship-it/scripts/step0-classify.sh, a gate-critical skill — ADR 0053/0065/0073). Verified, not assumed: pipeline-cli cp-classify classify over this PR's live 5-file list returns control-plane [path-match] — BLOCKING (human merge). My verdict is advisory only: it does not authorize a merge. Under the §CP hard gate (ADR 0135) a @kamp-us/control-plane member approves this at its current head and ship-it then enqueues it (ADR 0048, single merge authority). I neither merged nor sought that approval.

Reviewed-head: @ 0a875ac


Round 4 — body-only re-gate at the same head. This clears.

The head has not moved. 0a875ac9… is the SHA all three prior verdicts bound to, and the only change since round 3 is a PR-body edit. This verdict upserts round 3's record at that same SHA (ADR 0058 rule 2 / ADR 0213 — same PR, gate, head, run). It does not stack a new one.

Why the form returns to the advisory. Round 3 emitted a review-skill FAIL rather than the advisory because ADR 0226 makes the advisory a PASS path only — an advisory carrying a failing criterion resolves to unverified in decideNamespace's §CP arm, which is neither shippable nor pickable for repair. Round 3's single failing row was the shared deviation-disclosure (§DEV) row, it is now discharged, and every skills-class criterion passes. So 0226 puts the form back to the canonical advisory.

The skills class on this PR

Two of the five changed files are behavioral artifacts under claude-plugins/kampus-pipeline/skills/, and they are the whole of the skills class:

  • ship-it/scripts/step0-classify.sh — the Step 0 classifier, rewritten to print class-probe classify's output instead of re-implementing the §CLASS probes in shell.
  • ship-it/scripts/verify-step0-class-parity.sh — a new hermetic parity harness for it.

The behavioral verification of both — the delegation, the no-class rule landed as executable code with two independent origins ($CP_FILES_N from §CPREAD against class-probe's stdout) rather than as a second comment block, the fail-closed BLOCKING + STOP on a non-empty diff whose class set comes back empty, the shell shape (set -uo pipefail and no EXIT trap, per .patterns/skill-script-shell-shape.md), the harness fixture that was measured to fail against the pre-fix script rather than argued to, and the intact drift anchor (validate-gate-path-drift.sh green at 34 checks, including the UI_RE / UI_EXCLUDE_RE canonical-appears-once assertions in ship-it/SKILL.md, which this PR does not touch) — was completed in rounds 1 and 2 against this same byte-identical tree. The tree has not changed since, so those verdicts stand and are carried forward, not re-asserted as fresh work.

What this round re-checked

  • [PASS] The skills-class file set is unchanged. pulls/4742/files still returns exactly the two paths above under skills/, with no SKILL.md edited. The 5-file diff totals 387 insertions, 108 deletions, matching rounds 2 and 3 exactly.
  • [PASS] Required-namespace set unchanged. class-probe classify --namespaces over the live file list: has-code, has-skillsreview-code + review-skill, exit 0. A coverage self-check confirms both namespaces carry a head-bound verdict before this review is reported complete — one marker per namespace, each on its own comment's first line.
  • [PASS] CI at head, read directly from check-runs and counted. total_count 46, entries actually received 46 — complete, not truncated. All 46 completed: 42 success, 4 skipped, zero failures, zero pending. The legacy combined-status endpoint's state: pending, total_count: 0 on this commit is an empty statuses list, not a pending check.
  • [PASS] ship-it/SKILL.md is still deliberately untouched, and the freeze exception is still correctly scoped. The founder's fabrika execution core: the ~16 pipeline skills rebuilt skill-first, contract-driven #4650 No-go exception recorded on ship-it Step 0 shell probe under-reports the class set vs class-probe #4730 covers this defect only. The stale UI_RE line at SKILL.md:354 that this change creates is disclosed in the body and now filed as ship-it/SKILL.md: the UI_RE fence still says a classification shell consumes them — stale after Step 0 delegated to class-probe #4747, which is exactly the ticket that entry called for. Leaving it is the authorized narrowing, not an omission.

The body edit — additive, and the narrowing survived

  • [PASS] The pre-existing body was not reworded. All six original ## headings sit at their round-3 line numbers, unmoved, ## Deviations is still the seventh at 189, and the growth is entirely below it. Every passage rounds 1–3 quoted is present verbatim.
  • [PASS] The narrowing in What this PR does NOT claim is intact, and no standalone un-gated-merge claim was introduced. Verbatim at head: "ship-it Step 2 re-derives the required set from the CLI rather than trusting the carried one, and no executable consumer of step0-classify exists", "the pair is the urgency, neither half alone demonstrates an un-gated merge end to end", and "This PR does not claim either half 'would have merged a FAIL' on its own." A grep of the whole body for that phrase returns exactly one hit — body line 140, the closing half of the negation. This matters most to the skills class: the Step-0 half is the one that could be over-claimed, and the body still refuses to.
  • [PASS] No local paths, no operator identity anywhere in the body.

Sub-gates


Overall — clear

The skills class is verified: Step 0 delegates rather than re-implements, the no-class rule is executable and can fire, the harness is measurably discriminating, the drift anchor is untouched, and ship-it/SKILL.md was correctly left alone under a narrowly-scoped exception with its consequence filed. The body edit is additive and the narrowing survived intact. Nothing is outstanding on the review-skill class.

This authorizes nothing. A @kamp-us/control-plane approval at head 0a875ac97e877f8dd9e26a6fc99f06a3b70449df is what authorizes this merge, and ship-it is the only thing that merges it. There are zero reviews on this PR as of this verdict.

Note on tooling, not a finding: .claude/.pipeline does not exist in a spawned worktree, so the skill's literal ./.claude/.pipeline/… script paths exit 127 there. That is UNKNOWN, not a failing check (§ZS) — every verb above was run from the worktree's own tracked claude-plugins/kampus-pipeline/… copy. Already tracked as a live recurrence on #4666, not re-filed.

Verdict-written: round 4, body-only re-gate at 0a875ac

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment