Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"name": "PACT",
"source": "./pact-plugin",
"description": "Orchestration harness that turns Claude Code into a coordinated team of specialist AI agents",
"version": "4.4.52",
"version": "4.4.53",
"author": {
"name": "Synaptic-Labs-AI"
},
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -605,7 +605,7 @@ When installed as a plugin, PACT lives in your plugin cache:
│ └── cache/
│ └── pact-plugin/
│ └── PACT/
│ └── 4.4.52/ # Plugin version
│ └── 4.4.53/ # Plugin version
│ ├── agents/
│ ├── commands/
│ ├── skills/
Expand Down
2 changes: 1 addition & 1 deletion pact-plugin/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "PACT",
"version": "4.4.52",
"version": "4.4.53",
"description": "Orchestration harness that turns Claude Code into a coordinated team of specialist AI agents",
"author": {
"name": "Synaptic-Labs-AI",
Expand Down
2 changes: 1 addition & 1 deletion pact-plugin/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# PACT — Orchestration Harness for Claude Code

> **Version**: 4.4.52
> **Version**: 4.4.53

Turn a single Claude Code session into a managed team of specialist AI agents that prepare, design, build, and test your code systematically.

Expand Down
19 changes: 19 additions & 0 deletions pact-plugin/hooks/merge_guard_post.py
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,13 @@ def _retire_token_for_command(
token_dir = TOKEN_DIR
pattern = str(token_dir / f"{TOKEN_PREFIX}*")
current_session = get_session_id()
# #1097: target-aware retirement. Bring the match up to the granularity the mint
# records — retire ONLY the token for the SPECIFIC operation (same op AND same
# target), not any same-op token in the session. Uses the SAME _target_value the
# mint/read derive, so retirement, mint, and read agree on target identity by
# construction. (The API-merge form reuses pr_number per #1096, so this covers it
# uniformly with zero extra code.)
cmd_target = _target_value(extract_command_context(command))
for path in glob.glob(pattern):
basename = os.path.basename(path)
# Skip terminal-rename siblings and per-use markers (mirrors the
Expand All @@ -705,6 +712,18 @@ def _retire_token_for_command(
# subsequent op of the same type would need a fresh token anyway).
if ctx.get("operation_type") != op_type:
continue
# #1097: target-aware ONLY when the token carries a target (every PRODUCTION-
# minted token does — _collect_pairs mints no pair without a non-None
# _target_value). A target-LESS token (a degenerate/legacy shape) has no target
# to discriminate on, so it falls back to the pre-#1097 op+session match rather
# than becoming un-retirable. SAFE: a target-less token is never the operator's
# PROTECTED token (that one has a target), so the fallback cannot re-open the
# over-block the fix cures — a real protected {op, target:42} token is NOT
# retired by an unrelated {op, target:43} command (token_target=42 non-None,
# 43 != 42 -> continue).
token_target = _target_value(ctx)
if token_target is not None and cmd_target != token_target:
continue
# Session scoping (SEC-S1 cycle-2 revised asymmetric predicate).
token_session = token_data.get("session_id", "")
if current_session:
Expand Down
326 changes: 266 additions & 60 deletions pact-plugin/hooks/shared/merge_guard_common.py

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions pact-plugin/tests/runbooks/RUNBOOK_RUN_DATES.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,3 +189,9 @@ the calibration record.
| Run date (UTC) | Operator | Plugin version | Verdict | Harness-independence evidence | Notes (corpus; supersede reconciliation; per-set outcomes) |
| -------------- | -------- | -------------- | ------- | ----------------------------- | ----------------------------------------------------------- |
| 2026-07-02 | michael-wojcik | 4.4.52 installed (contains all v4.4.51 changes; hooks byte-identical to origin/main `60f14796`) | **29/29 PASS, post-side** — all fixed forms ALLOW/mintable, every preserved-block canary DENY, all four laundering channels REFUSE end-to-end, faithful round-trips AUTHORIZE; ZERO installed-vs-source divergence · no HALT | SINGLE-harness POST-INSTALL re-drive (test-engineer; same-engineer as both pre-merge probes — independence stays attributed to the two blind reviews that converged SAFE-TO-MERGE pre-merge). Corpus recovered VERBATIM from both pre-merge harnesses (not re-derived from row prose): the 17-row batch set + the 14-row fix-level set, deduplicated on byte-identical probe forms (the fix-level D1082-cured + N5-superseded rows == batch N5's command) → 29 distinct probes through the INSTALLED v4.4.52 `merge_guard_pre.py`/`merge_guard_post.py` mains as REAL SUBPROCESSES (hooks.json `python3 <hook>.py` shape) under macOS system python3 3.9.6 (the runtime floor); per-probe isolated `CLAUDE_CONFIG_DIR` + `HOME` (TOKEN_DIR derives from `$CLAUDE_CONFIG_DIR` at import), zero `~/.claude` contamination; dangerous literals in script files. BYTE-IDENTITY GATE first: full `diff -r` of the installed hooks tree vs the worktree at `60f14796` byte-identical (excl. `__pycache__`) — covers both hook mains + every `shared/` module transitively. Harness-fidelity control: the P1a/P1b mint→authorize pair passed BEFORE any REFUSE was interpreted (rules out envelope-shape infidelity masquerading as a shipped-artifact divergence). | This row DISCHARGES the post-install gate for #1064/#1077/#1078/#1082/#1083 (gated-closure discipline: the two pre-merge rows above ran STAGED hooks and satisfied the pre-merge gates; this is the installed-binary confirmation per the v4.4.45 post-install standard). Expected verdicts = FINAL shipped semantics, reconciled toward the LATER fix-level row where the two sets overlap: N5 (`git push origin feature && rm -f stale.txt`) probed as ALLOW per the D1082-cured supersede — the batch row's N5 DENY→DENY is historical; prior rows untouched (append-only). Per-set outcomes: 17/17 batch-derived (P1a/P1c1 mint exactly 1 token `{push-to-main, target_ref=main, bound_flags=[--force-with-lease]}`; P1b/P1c2 byte-identical exec ALLOW; P2a/P2b httpie + R17 https-alias ALLOW; P3a/P3b/P3c compound-leg ALLOW; N1–N4 DENY; N5 ALLOW; A1/A2 ALLOW) · 2/2 fix-level read-floor non-overlap (permanent `git push && rm -f x.txt` ALLOW; same-leg `cd /repo && git push --force origin main` DENY) · 5/5 laundering-closed (close fwd + close rev + push-lease + merge-admin + force-noverify: mint from the benign member → token written → ESCALATED exec DENY) · 5/5 faithful round-trip (byte-identical member AUTHORIZE, incl. both close-member shapes). **NOT probed**: #1087 (multi-close ambiguity laundering — open, pre-existing, tracked) — per the fix-level row above; #1079 closed won't-fix 2026-07-01. No HALT. |

## merge-guard-cross-leg-completion-fix-level-live-probe (#1087 close-arm laundering + #1086 API cross-leg over-blocks)

| Run date (UTC) | Operator | Plugin version | Verdict | Harness-independence evidence | Notes (per-set outcomes; §0 primary gate; identity-slice faithfulness) |
| -------------- | -------- | -------------- | ------- | ----------------------------- | ---------------------------------------------------------------------- |
| 2026-07-02 | michael-wojcik | rides the eventual merge (fix-level pre-merge probe; NO bump decided here) — probe ran hooks: PRE-FIX = branch base `60f14796` (== `33260521^`; close AND API arms still whole-command/cross-leg) vs POST-FIX = worktree HEAD `3e2da931` (per-leg `_CLOSE_LITERAL_ARMS` + `_API_LITERAL_ARMS`) | **14/14 PASS, BOTH SIDES** — #1087 close laundering CLOSED end-to-end (mint 1→0, escalated single ALLOW→DENY); #1086 all six API/close cross-leg over-blocks REMOVED (DENY→ALLOW); every same-leg dangerous form STILL gates incl. the CRITICAL `cd /repo && gh pr close 42 --delete-branch` (DENY/DENY); benign faithful singles + the faithful close round-trip ALLOW both sides; ZERO runtime-vs-unit divergence · no HALT. FIX-LEVEL pre-merge probe; the POST-INSTALL probe rides the eventual merge per the #1085 precedent. | SINGLE-harness empirical before/after (test-engineer run): the SAME crafted hook stdin through the REAL `merge_guard_pre.py`/`merge_guard_post.py` mains as SUBPROCESSES (matching hooks.json `python3 <hook>.py`) under macOS system python3 3.9.6 (runtime floor); per-probe isolated `CLAUDE_CONFIG_DIR` + `HOME` (TOKEN_DIR derives from `$CLAUDE_CONFIG_DIR` at import — env-redirectable, no fidelity fallback), zero `~/.claude` contamination; dangerous literals in script files. SAME-ENGINEER probe (also authored the permanent §7a/§7b sweep classes this run certifies) — the two DIRECTIONS self-attribute per row (pre-fix reproduces every over-block + the laundering; post-fix shows the cure), and the blind adversarial review is the independence signal. Identity: pre-fix `git archive 60f14796` verified to LACK the delta symbols (`_CLOSE_LITERAL_ARMS`/`_API_LITERAL_ARMS` absent — grep 0); post-fix = the clean worktree hooks at `3e2da931`. Full suite (plain `python -m pytest`, errors-token scanned — rtk NOT used for the gate): **10476 passed, 11 skipped, 0 failed, 0 ERRORS** (baseline 10436 + 40 new §7a/§7b cases). | LAUNDERING-CLOSED (#1087, end-to-end mint→token→ESCALATED exec): AMBIG member `gh pr close 42 && gh pr close 43 && echo --delete-branch` → pre mints 1 token → escalated single `gh pr close 42 --delete-branch` AUTHORIZES (laundering OPEN); post mints 0 → escalated single DENIES (CLOSED). OVER-BLOCK REMOVED (#1086/#1087, pre DENY→post ALLOW, one per family): close single-member + reversed; API git/refs DELETE, merge PUT, curl git/refs, wget git/refs (all `... && echo <method-token>` benign compounds). SAME-LEG STILL GATES (DENY/DENY): the CRITICAL `cd /repo && gh pr close 42 --delete-branch` (True today ONLY via the in-leg per-leg match the fix re-establishes), `gh pr close 42 --delete-branch`, `gh api -X DELETE .../git/refs/heads/x`, `cd /repo && gh api -X DELETE .../git/refs/heads/x`. BENIGN SINGLES run FREE both sides (`gh pr close 42`, `gh api .../git/refs/heads/x`). FAITHFUL ROUND-TRIP (§0 inviolable): `gh pr close 42 --delete-branch` mints 1 → authorizes itself ALLOW both sides (no over-block on a faithful click). **IDENTITY-SLICE FAITHFULNESS** (the coder's flagged reviewer-confirm item, discharged): the permanent §7a/§7b non-vacuity mutates `_slice_stripped_legs`→identity (restores whole-command matching) so each of the 11 cured rows flips D=False→True, AND neutering ONLY that row's family arms (`_CLOSE_LITERAL_ARMS` / `_API_LITERAL_ARMS`) returns it to False — proving each whole-command flip is caused by that family's arm ALONE (no second-family co-match), so the identity-slice mutation is a faithful single-family pre-fix simulation for every row (uniform coupling, no uneven map). CONFIRMED already-green (coder-authored): §7c approval-source canaries (AMBIG mints 0 → escalated + different-target DENY), the OPEN-Q D tripwire (close + API halves), the real post→pre seam laundering-closed canary. No HALT. Satisfies the fix-level pre-merge gate for #1087/#1086; gated closure rides the eventual merge per the no-auto-close discipline. |
Loading