Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions docs/audits/impl-PMAT-3459-receipt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
status: complete
ticket: PMAT-3459
github_issue: 3459
part: 2 (the must-carry label universe); part 1 (the in-repo gated tag path) landed in 6a657fc61 / PR #3617
kind: code
model: claude-opus-5-5 (author)
---
# implementation receipt: PMAT-3459 part 2 (#3459)

## Scope: where part 2 comes from

The roadmap title names only part 1. The issue #3459 carries part 2 in its own body, under "Tag-path defect 3
(operator ruling 2026-09-20 ~14:00Z) — `check_milestone_cut.sh` counts PRs":

> **The rule it must implement instead: count ISSUES carrying the must-carry label. Never PRs.**

The same section measures why: S3 asserted "one open PR -> RED", so every open PR on the milestone blocked the tag
(0.69.0: `open_issues=9` from the milestone API, 3 from `gh issue list`; the six PRs made up the difference).

dd's MUST-row sweep (#4159, 2026-09-24T07:11Z comment on #3459) records part 1 as DONE and part 2 as "OPEN.
Claimed by aprender-f5".

The cop (aprender-cf) ruled on 2026-09-24, and the ruling bounds this diff. This is the cop's ruling, not an
operator quotation:
- (a) Create the `must-carry` label, described as "Blocks the release cut of its milestone (check_milestone_cut.sh)".
- (b) The narrowing is approved on one condition: nothing is silently left behind. At the cut, every open item
that is not must-carry is MOVED, with a one-line comment. It goes to the next release if that release's epic
lists it, otherwise to `backlog`.
- A milestone that is tagged while items are still open is RED. Add a self-test row: an unlabelled open issue left
in a tagged milestone gives RED.
- The move lives in the autopilot, before the tag. The gate only verifies.

So the carry script and the autopilot's move-before-tag step are the approval condition for narrowing the
blocker set. They are not extra scope. Without the move, `--must-carry` alone would let unlabelled items silently
ride a tagged milestone.

## What the diff does

| file | change |
|---|---|
| `scripts/check_milestone_cut.sh` | Adds a `--must-carry` mode. It is RED only on an OPEN ISSUE labelled `must-carry`, never on a PR. Every other open item is printed `TO CARRY`, and the JSON gains `mode` and `to_carry`. The strict default is unchanged. New self-test rows S24–S29; S27 is the cop's row (an unlabelled open issue under strict gives RED). |
| `scripts/release/carry_milestone_items.sh` | New. For each open non-must-carry item, it moves the item to the next open semver milestone when that milestone's "EPIC: release train <next>" lists it, otherwise to `backlog`, each move with a `slipped_from:` comment. It refuses (rc 1, zero writes) while any must-carry issue is open. rc 2 means "cannot act": a failed read, no next milestone, or a partial write. |
| `scripts/release/autopilot.sh` `cut_tag()` | Runs the must-carry verdict, then the carry step, then the EXISTING strict call (the line and its case block are byte-identical), then `git tag`. The strict gate still sees every open item, so anything the carry step missed turns the tag RED. |
| `scripts/check_tag_step_gated.sh` | Asserts that order. Must-carry rc 1 or 2 must neither carry nor tag, and carry rc 2 must not tag. New mutants: M4 (must-carry verdict discarded) and M5 (carry call deleted). It also runs the carry script's own self-test. |

The strict gate is not weakened. S3 still asserts "one open PR -> RED" under strict, and strict remains the last
thing before `git tag`. S24 inverts S3 only in `--must-carry` mode, which is exactly the rule the issue asks for.

## Measured (HEAD of this branch)

```
bash scripts/check_milestone_cut.sh --self-test -> 31/31 ok; a mutant ignoring the label is killed by S26
bash scripts/release/carry_milestone_items.sh --self-test -> SELF-TEST PASSED (7 rows, stub gh)
bash scripts/check_tag_step_gated.sh --self-test -> SELF-TEST PASSED (M1..M5 RED, real subject GREEN)
bash scripts/check_bashrs_gate.sh -> PASS, 0 SEC/DET/IDEM errors
check_shell_lint_ratchet / no_pipe_into_grep_q / no_hand_rolled_parsers / guards_are_wired -> PASS
gh api repos/paiml/aprender/labels/must-carry -> exists, color B60205
```

Not done here, per the cop's ruling: applying `must-carry` to the 0.70 scope. dd does that at the 0.70 scope-cut GO.
665 changes: 665 additions & 0 deletions docs/audits/quorum-PMAT-3459-part2.brief.md

Large diffs are not rendered by default.

42 changes: 42 additions & 0 deletions docs/audits/quorum-PMAT-3459-part2.claude-lanes.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"ticket": "PMAT-3459 (#3459 part 2)",
"head": "9a6bc003a6a445ef3105325d65f8a482c235a7c3",
"diff_sha256": "cb1aa389c82a09613a532f57155322bc75add5b18d0e3eedc00b8363617af296",
"brief_sha256": "689025e6a82104e8492ed30d345b9ccd779b55bb9ae5dfd03578c7519bba7f77",
"brief_bytes": 38533,
"brief_note": "brief rebuilt byte-for-byte from quorum-review.sh lines 241-313 (width 1); its byte count equals the agy artifact prompt_bytes; the artifact does not record prompt_sha256 (lane-reduce drops it), so both Claude lanes sha256-ed the file they read and reported it back",
"composition": "cop ruling 2026-09-24: sonnet-5 + 1 agy (gemini-3.1-pro-high) + haiku-4-5; author claude-opus-5-5",
"lanes": [
{
"seat": "agy",
"model": "gemini-3.1-pro-high",
"verdict": "PASS",
"artifact": "quorum-PMAT-3459-r2.json"
},
{
"seat": "claude",
"model": "claude-sonnet-5",
"verdict": "PASS",
"brief_sha256_reported": "689025e6a82104e8492ed30d345b9ccd779b55bb9ae5dfd03578c7519bba7f77",
"agent": "a5742813694bc906c",
"nits": [
"check_milestone_cut.sh:151 --json gains mode/to_carry keys in strict mode too (the verdict is unchanged, the schema grows)",
"carry_milestone_items.sh:48 backlog is matched by title, its open state is not checked"
]
},
{
"seat": "claude",
"model": "claude-haiku-4-5",
"verdict": "PASS",
"brief_sha256_reported": "689025e6a82104e8492ed30d345b9ccd779b55bb9ae5dfd03578c7519bba7f77",
"agent": "a7e36bbfb164b3c8f",
"findings": []
}
],
"result": "3/3 PASS",
"round1": {
"head": "a639f441e",
"lanes": "2 agy (gemini-3.1-pro-high, gemini-3.8-flash-high) FAIL 2/2 on scope only: the brief carried only the part-1 roadmap title; fixed by the receipt commit 9a6bc003a (docs/audits/impl-PMAT-3459-receipt.md), no code change",
"artifact": "quorum-PMAT-3459-r1.json"
}
}
110 changes: 110 additions & 0 deletions docs/audits/quorum-PMAT-3459-part2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
{
"ticket": "PMAT-3459",
"base": "main",
"base_resolved": "origin/main",
"base_note": "local main differs from origin/main by 146 commit(s); judged against origin/main",
"head": "9a6bc003a6a445ef3105325d65f8a482c235a7c3",
"diff_sha256": "cb1aa389c82a09613a532f57155322bc75add5b18d0e3eedc00b8363617af296",
"width": 1,
"executor": "agy",
"prompt_mode": "inline",
"prompt_bytes": 38533,
"author": {
"model": "claude-opus-5-5",
"family": "claude",
"source": "flag"
},
"agreed": true,
"lanes": [
{
"lane": 1,
"status": "SUCCESS",
"verdict": "PASS",
"summary": "The diff precisely implements the rules laid out in PMAT-3459 part 2 and the cop ruling. It correctly scopes the blocker to ISSUES labelled `must-carry`, while ensuring PRs and other issues do not block the cut. It moves unlabelled items to either the next semver milestone (if listed in its epic) or to the backlog, leaving a comment on each. Importantly, the `STRICT` mode of `check_milestone_cut.sh` acts as the final gate in `autopilot.sh` *after* the carry step, ensuring that the tag path is never weakened and no items can silently ride a tagged milestone.",
"findings": [],
"raw_bytes": 3096,
"err_bytes": 0,
"envelope_status": "SUCCESS",
"verdict_source": "structured_output",
"grounding_check": "parity",
"model": "gemini-3.1-pro-high",
"model_measured": "gemini-3.1-pro-high",
"model_source": "measured",
"family": "gemini",
"role": "independent",
"fallback": {
"judged_by": "gemini-3.1-pro-high",
"exhausted": false,
"attempts": [
{
"model": "gemini-3.1-pro-high",
"family": "gemini",
"outcome": "answered"
}
],
"skipped": []
}
}
],
"dissent": [],
"dedup": [],
"uncovered": [],
"coverage_source": "lanes",
"partial": false,
"partial_reasons": [],
"fallback": {
"same_family_width": 1,
"chain": [
{
"model": "gemini-3.1-pro-high",
"family": "gemini",
"disposition": "configured"
},
{
"model": "gpt-oss-120b-medium",
"family": "openai",
"disposition": "fallback"
},
{
"model": "qwen3.5",
"family": "qwen",
"disposition": "not-run",
"why": "no quorum.local_lane in the config — the aprender lane has no model to load"
},
{
"model": "claude-opus-4-6-thinking",
"family": "claude",
"disposition": "width",
"why": "same family as the author: at most 1 lane, recorded role width, counted toward no floor (R-15a)"
},
{
"model": "claude-sonnet-4-6",
"family": "claude",
"disposition": "width",
"why": "same family as the author: at most 1 lane, recorded role width, counted toward no floor (R-15a)"
}
],
"precheck": [
{
"family": "gemini",
"model": "gemini-3.1-pro-high",
"probe": 1,
"outcome": "live"
}
],
"prah": {
"source": "install-receipt",
"path": "/home/noah/.claude/skills/paiml-implement/bin/prah"
}
},
"auto_merge": {
"checked": false,
"was_armed": false,
"disarmed": false,
"note": "no --pr given: nothing to disarm"
},
"lint": {
"ok": true,
"output": "receipt complete: kind=artifact lanes=1 author=claude-opus-5-5/claude"
}
}
77 changes: 68 additions & 9 deletions scripts/check_milestone_cut.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@
# immediately before `git tag`, after the bump PR has merged. Never while the
# bump PR is open: it sits in the milestone and reads RED.
#
# TWO MODES (#3459 part 2, cop ruling 2026-09-24):
# --must-carry the BLOCKING set: open ISSUES labelled `must-carry`. Pull requests and
# unlabelled issues do not block; each is listed as TO CARRY, because the
# release autopilot MOVES it (scripts/release/carry_milestone_items.sh) before
# the tag. Nothing is silently left behind: see strict.
# (default) STRICT: the milestone holds nothing open but its release epic. cut_tag() runs
# it AFTER the carry, so an item the carry missed, or an item nobody carried,
# is RED at the tag. A tagged milestone with an open item is never clean.
#
# Exit 0 = zero open items in the milestone.
# Exit 1 = at least one open item; each is named with its remedy.
# Exit 2 = cannot judge, never a silent pass: gh or python3 missing, gh
Expand All @@ -42,11 +51,11 @@ set -euo pipefail
SELF_PATH="$(cd "$(dirname "$0")" && pwd)/$(basename "$0")"

usage() {
printf 'usage: %s <milestone-title> [--repo O/R] [--json OUT] | --self-test\n' "$(basename "$0")" >&2
printf 'usage: %s <milestone-title> [--repo O/R] [--json OUT] [--must-carry] | --self-test\n' "$(basename "$0")" >&2
exit 2
}

# judge_from_dir DIR TITLE [JSON_OUT]
# judge_from_dir DIR TITLE [JSON_OUT] [MODE] MODE = strict (default) | must-carry
# DIR holds milestones.jsonl and items.jsonl, one JSON object per line: the
# shape `gh api --paginate --jq '.[]'` writes, so a read of more than one page
# is never a concatenation of arrays. Prints the verdict and returns 0, 1 or 2.
Expand All @@ -55,6 +64,10 @@ judge_from_dir() {
import json, sys

d, title, json_out = sys.argv[1], sys.argv[2], sys.argv[3]
mode = sys.argv[4] if len(sys.argv) > 4 and sys.argv[4] else "strict"
if mode not in ("strict", "must-carry"):
print("ENV: unknown mode %s" % mode, file=sys.stderr)
sys.exit(2)

def env(msg):
print("ENV: " + msg, file=sys.stderr)
Expand Down Expand Up @@ -124,28 +137,48 @@ if len(admitted) > 1:
env("%d open items claim to be the release epic of %s: %s" % (len(admitted), title, " ".join("#%s" % r["number"] for r in admitted)))
rows = [r for r in all_rows if not is_release_epic(r)]

# --must-carry: only open ISSUES labelled must-carry block the cut. Everything else is
# listed TO CARRY: the autopilot moves it before the tag, and the STRICT run after the
# move is what proves nothing was left behind (#3459 part 2).
to_carry = []
if mode == "must-carry":
to_carry = [r for r in rows if not (r["kind"] == "issue" and "must-carry" in r["labels"])]
rows = [r for r in rows if r["kind"] == "issue" and "must-carry" in r["labels"]]

verdict = "RED" if rows else "PASS"
if json_out:
with open(json_out, "w", encoding="utf-8") as f:
json.dump({"milestone": title, "number": number, "open": open_n, "closed": closed_n,
"admitted": admitted, "items": rows, "verdict": verdict}, f, indent=2, sort_keys=True)
json.dump({"milestone": title, "number": number, "open": open_n, "closed": closed_n, "mode": mode,
"admitted": admitted, "items": rows, "to_carry": to_carry, "verdict": verdict},
f, indent=2, sort_keys=True)
f.write("\n")

for r in admitted:
print("ADMITTED #%s %s [%s] %s -- the release epic of this train, closed at 06x section 4 step 8"
% (r["number"], r["kind"], ",".join(r["labels"]), r["title"]))
for r in to_carry:
print("TO CARRY #%s %s [%s] %s -- not must-carry: the autopilot moves it before the tag"
% (r["number"], r["kind"], ",".join(r["labels"]), r["title"]))
if not rows:
print("PASS milestone %s (#%s): 0 open besides its release epic, %d closed -- the cut may proceed" % (title, number, closed_n))
if mode == "must-carry":
print("PASS milestone %s (#%s): 0 open must-carry issue(s); %d item(s) to carry before the tag"
% (title, number, len(to_carry)))
else:
print("PASS milestone %s (#%s): 0 open besides its release epic, %d closed -- the cut may proceed" % (title, number, closed_n))
sys.exit(0)

for r in rows:
print("#%s %s [%s] %s" % (r["number"], r["kind"], ",".join(r["labels"]), r["title"]))
for r in rows:
print(" remedy #%s: close it, or carry it: gh %s edit %s --milestone <next> && gh %s comment %s --body \"slipped_from: %s\""
% (r["number"], r["kind"], r["number"], r["kind"], r["number"], title))
print("RED milestone %s (#%s): %d open item(s) -- no tag until each is closed or carried" % (title, number, len(rows)))
if mode == "must-carry":
print("RED milestone %s (#%s): %d open must-carry issue(s) -- they BLOCK the cut and are never carried"
% (title, number, len(rows)))
else:
print("RED milestone %s (#%s): %d open item(s) -- no tag until each is closed or carried" % (title, number, len(rows)))
sys.exit(1)
' "$1" "$2" "${3:-}"
' "$1" "$2" "${3:-}" "${4:-}"
}

# fetch_live REPO TITLE DIR -- writes DIR/milestones.jsonl and DIR/items.jsonl.
Expand Down Expand Up @@ -212,7 +245,7 @@ st_judge() {
t="$4"
shift 4
rc=0
judge_from_dir "$fx" "$t" "" > "${fx}/out" 2>&1 || rc=$?
judge_from_dir "$fx" "$t" "" "${ST_MODE:-}" > "${fx}/out" 2>&1 || rc=$?
st_check "$c" "$want" "$rc" "${fx}/out" "$@"
}

Expand Down Expand Up @@ -330,6 +363,28 @@ self_test() {
{ st_item 20 7 issue open epic "EPIC: release train M — a"; st_item 10 7 issue open P1; } > "${fx}/items.jsonl"
st_judge "$fx" S23 1 M "ADMITTED #20" "#10 issue [P1] item 10" "1 open item(s)"

# --must-carry (#3459 part 2): only open ISSUES labelled must-carry block. The STRICT rows above
# still hold unchanged: cut_tag() runs strict AFTER the carry, so nothing is left behind.
# S24 = S3 inverted in the new scope: one open PR is NOT a blocker, it is listed TO CARRY
st_ms M 7 1 5 > "${fx}/milestones.jsonl"
st_item 11 7 pr open release > "${fx}/items.jsonl"
ST_MODE=must-carry st_judge "$fx" S24 0 M "TO CARRY #11 pr [release] item 11" "0 open must-carry issue(s); 1 item(s) to carry"
# S25 = its twin: one open must-carry ISSUE blocks, named with its label, and is never carried
st_item 30 7 issue open must-carry > "${fx}/items.jsonl"
ST_MODE=must-carry st_judge "$fx" S25 1 M "#30 issue [must-carry] item 30" "1 open must-carry issue(s) -- they BLOCK the cut"
# S26 an unlabelled open issue does not block the must-carry run: it is carried
st_item 10 7 issue open P1 > "${fx}/items.jsonl"
ST_MODE=must-carry st_judge "$fx" S26 0 M "TO CARRY #10 issue [P1] item 10"
# S27 (cop ruling) the SAME unlabelled issue left in the milestone at the TAG is RED: strict is the
# post-carry verification, and a tagged milestone with an open item is never clean
st_judge "$fx" S27 1 M "#10 issue [P1] item 10" "1 open item(s)"
# S28 a PULL REQUEST labelled must-carry does not block: the universe is ISSUES
st_item 31 7 pr open must-carry > "${fx}/items.jsonl"
ST_MODE=must-carry st_judge "$fx" S28 0 M "TO CARRY #31 pr [must-carry]"
# S29 the release epic stays ADMITTED in must-carry mode, whatever its labels
st_item 20 7 issue open epic "EPIC: release train M — schedule" > "${fx}/items.jsonl"
ST_MODE=must-carry st_judge "$fx" S29 0 M "ADMITTED #20 issue [epic]" "0 open must-carry issue(s); 0 item(s) to carry"

# S16 --json records the verdict and the items
st_ms M 7 1 5 > "${fx}/milestones.jsonl"
st_item 10 7 issue open bug > "${fx}/items.jsonl"
Expand Down Expand Up @@ -416,7 +471,11 @@ main() {
shift
repo="paiml/aprender"
json_out=""
mode="strict"
while [ $# -gt 0 ]; do
case "$1" in
--must-carry) mode="must-carry"; shift; continue ;;
esac
[ $# -ge 2 ] || usage
case "$1" in
--repo) repo="$2" ;;
Expand Down Expand Up @@ -446,7 +505,7 @@ main() {
exit 2
fi
rc=0
judge_from_dir "$input_dir" "$title" "$json_out" || rc=$?
judge_from_dir "$input_dir" "$title" "$json_out" "$mode" || rc=$?
exit "$rc"
}

Expand Down
Loading
Loading