Releases: Synaptic-Labs-AI/PACT-Plugin
Release list
v4.6.8
Summary
Closes the latent config-root isolation gap in test_postcompact_archive's run_hook helper (#1191 — the deferred #1189 security-engineer follow-up). Adds a per-test env pin so the postcompact subprocess child resolves a tmp root (not the operator's real ~/.claude) + a delete-the-fix counter-test proving the pin is load-bearing. Bumps version 4.6.7 → 4.6.8.
What landed
- test(isolation): pin config-root for postcompact run_hook helper —
env={**os.environ, HOME:env_root, CLAUDE_CONFIG_DIR:env_root}at the test spawn site (Form A+B belt-and-suspenders);env_rootdefaults to mkdtemp (universal isolation for all callers). Delete-the-fix counter-test (two-distinct-tmp, sole-provider) proves the pin is load-bearing. - chore(release): version bump 4.6.7 → 4.6.8 (4-file: marketplace.json, plugin.json, pact-plugin/README.md, README.md).
Why
The #1191 subprocess-isolation audit (convergent preparer + security-engineer) found zero active destructive-write-through-config-root gaps + one latent gap: run_hook spawned the postcompact child with no env isolation; the child's write (gated off today by a coincidental is_lead check) would resolve the operator's real ~/.claude under a future lead-frame input. The autouse fixture's Path.home setattr is in-process-only — it does not cross to the subprocess child. This closes that latent gap at the test spawn site.
Verification
- 3-reviewer peer-review: convergent GREEN/PASS, 0 blocking (architect + test-reviewer + security-engineer; load-bearing trace independently confirmed; test-reviewer empirical delete-the-fix).
- Full suite: 11562 passed, 0 errors.
- SACROSANCT reaper (_TEAM_NAME_PATTERN / cleanup_old_teams / ^pact-) byte-unchanged.
v4.6.7
Summary
Test config-root isolation + teams-reaper docstring framing. Fixes #1186, #1187, #1185.
What landed
- #1186: autouse conftest fixture
_isolate_config_root_to_tmp(scrubCLAUDE_CONFIG_DIR+ redirectPath.home()-> tmp) closes the destructive marker leak where test-fixture dirs wrote into the operator's real~/.claude/teams/since ~2026-04-05. Inverts isolation from opt-in to opt-OUT. - #1187:
_prune_registry_dead_teamsadded to_patch_main_deps; with the redirect, the unrooted registry write is unreachable by construction. - #1185: corrected 3 false-framing comment sites in
cleanup_old_teams(the^pact-gate deliberately diverges fromgenerate_team_name'ssession-output; widening towardsession-*would armshutil.rmtreeagainst live platform dirs). - Pinning test: a delete-the-fix counter-test for the fixture (fails loudly if the fixture is disabled — closes the silent-regression hazard).
- Version bump 4.6.6 -> 4.6.7.
Why
Test isolation was opt-in (forgetting = the default); the marker leak had quietly polluted the operator's real ~/.claude/teams/ for ~3 months. The false docstring had already misled two prior agents toward the catastrophic reaper-widening. No reaper scope touched (SACROSANCT); suite 11607 passed / 0 errors.
Follow-ups filed: #1190 (import-time-freeze leak class), #1191 (subprocess-isolation audit).
v4.6.6 — merge-guard positional over-block closure
v4.6.6 — merge-guard positional-argument over-block class closure
Closes the structural positional-argument over-block class in the merge-guard: a danger-looking literal inside an inert command's quoted argument no longer blocks a faithful single-command approval click, while every genuinely-destructive form stays caught and routed through the approval mint.
What landed
- P1 —
git commit --trailer+gh --assigneefamily positional over-blocks closed via flag-anchored carrier extensions. - P2 —
strip-inert-default: an unrecognized command's quoted positional is POSIX-inert argv → stripped by default; a curated keep-visible set is preserved (shell-string executors via basename-normalized whole-head-token match; exec-prefix wrappers via arity-aware recursion; http-clients; anything the shared danger battery flags). Command-head detection skips leading env-assignments and redirections. - busybox/stdbuf closure — the COARSE wrapped-inert over-block closed by moving
busybox/stdbufto arity-aware recursion (withhush/lash/mshshell-applet recognition so wrapped executors stay caught) + a general attached-short-value walker.find/flockremain coarse (unbounded grammars). - Certification — comprehensive bidirectional non-vacuous certs (baked-baseline + mutant-of-live-source) for the positional class and the busybox/stdbuf closure.
- CI fix — corrected a pre-existing carrier-5 cert guard that crashed in clean clones (baseline SHAs unreachable), greening CI on
main.
Why
The merge-guard defends against honest mistakes, not adversaries: a faithful single-command click must always mint and merge. This release removes an over-block class where danger-looking prose sat in an argument the outer shell never executes — while preserving every genuinely-destructive catch.
Design posture
The cardinal over-block direction is sound by construction (argv inertness); the tolerated under-block direction carries only a bounded catalog (shell-string executors + exec-prefix wrappers — a finite language feature). The strip is monotonic toward allow — it can only close over-blocks, never create one.
Follow-ups
- git/gh-prose over-block (inert
--grep/--author/--search) — tracked separately. - carrier-5 cert baseline reachability (differentials skip in CI) — tracked separately.
v4.6.5 — merge-guard over-block closures (#1140)
Summary
Closes the merge-guard over-block class where a faithful, inert command whose text merely describes a destructive operation was wrongly blocked (issue #1140). A benign commit message, PR title, log line, or variable assignment that mentions something like git branch -D or git push --force no longer triggers the guard.
What landed
The fix migrates every prose-bearing carrier in the shell-command classifier to a shared, quote-context-faithful substitution span-scanner, and widens the carriers' coverage:
- Command-substitution prose (
-m "as of $(date): …"): benign$()/backtick spans are preserved (they execute, so stay caught); the surrounding inert literal is stripped — across all six prose carriers (git message, tag, gh issue/pr/release/gist, HTTP body, gh-api selector). - Message-abbreviation forms (
--mess, bundled-am/-sam) and sibling verbs (git merge/stash/notesmessages) — withcherry-pick/revert -m <number>correctly excluded. - Attached-equals forms (
--message=,--title=, generalNAME="…") via the variable-assignment strip, with a dotted-key guard that keepsgit -c section.key=config injections at status quo. - Multi-argument
echo/printf(danger prose in a 2nd+ positional argument), both quote styles.
Why it's safe
The change is a strict monotonic improvement on a SACROSANCT control, certified bidirectionally against the real classifier: zero new over-blocks, zero new under-blocks, every genuinely-destructive command and command-substitution still caught, quote-balanced output (no leg-merge desync), and linear-time (ReDoS-safe). Backed by a 297-vector non-vacuous certification suite (each closure proven to flip blocked→allowed against its own pre-fix baseline) and an independent fresh-lens adversarial re-review.
Deferred (separate issue)
The broader structural positional over-block surface — danger-looking prose in an unstripped argument of an arbitrary command — is not folded here: it is entangled with true-positives (e.g. bash -c "…" / ssh host "…" execute their argument), so closing it safely requires per-command execution modeling, a re-architecture tracked as its own audit issue.
v4.6.4 — empty-team dedup posture
v4.6.4 — Unify empty-team dedup posture across snapshot seams
Closes the empty-team_name dedup fail-open at the task_metadata_snapshot seams with a single, consistently-applied posture: a session-dir-anchored marker fallback (two-root scheme). When a lead frame has an empty team_name but a resolvable journal, the snapshot family now claims its content-hash dedup marker under {session_dir}/.task_metadata_snapshot_emitted/ instead of fail-opening past dedup. Healthy-team markers do not move — the default path is byte-identical (regression-pinned).
Why this posture
Under-mirror is the unrecoverable direction; duplicate-append is the tolerated one. Blanket fail-closed was rejected (it forfeits the only firing mirror on an empty-team frame — B/C seams starve by construction — creating zero-mirror sessions). Status-quo fail-open was rejected (unbounded duplicate appends, and it silently inherited a prior fail-direction ruling whose premise — a functioning dedup marker — is void in exactly this state). The fallback restores that premise: dedup resolvability becomes equal-by-construction to journal resolvability, so the gap cannot reopen structurally.
Certification (bidirectional)
- Close direction: every pre-change emit still fires; no-journal defer, healthy-team two-root byte-identity, and marker-subsystem-fault fail-open all pinned.
- Open direction: the named defect — unbounded identical per-write duplicates — is closed to ≤1 duplicate per content-key per session-dir (~1.1 KB median). The counter-test reproduces the base defect under source-only revert, so the cert is non-vacuous.
- Both-modes standing gate (in-process / tmux / lead) keyed on runtime structural signals only.
- Full suite: 11,007 passed / 15 skipped / 0 failed / 0 errors. Concurrent auditor GREEN (AST-census). Three independent review lanes, one greedy remediation cycle (session-dir umask 0o700 fix + healing-transition pin).
Residuals tracked separately: #1172 (agent_handoff event family inherits the same fail-open) · #1173 (write_context non-empty team_name guard + sibling degenerate-form class).
Tier: PATCH (internal durability-substrate hardening; no user-facing surface change).
v4.6.3 — per-write metadata mirror
Summary
Per-write journal mirroring for open-task-consumed metadata keys — the narrow complement to v4.6.2's completion-time snapshot. Load-bearing metadata written to OPEN tasks now survives the platform's whole-store, status-blind task drains.
What landed
PER_WRITE_MIRROR_KEYSregistry (7 keys):scope_contract,nesting_depth,worktree_path,teachback_submit,teachback_rejection,audit_summary,audit_summary_authored— targeted-key-in-delta gating keeps journal churn bounded (the mirror-every-write generalization stays rejected).- Two new gate legs in the PostToolUse task-lifecycle flow: non-completing TaskUpdate (disk∪delta overlay emit) and TaskCreate (via the response task id). READ-ONLY payload build; exit-0 advisory contract — a mirror failure can never block the hooked tool call.
- Canonical-journal-frame predicate (
is_lead OR session_id == leadSessionId, fail-closed): teammate-written keys mirror in-process; tmux teammate frames defer structurally (no journal silo, no marker poisoning). One named residual: tmux pre-first-lead-touch drain window onteachback_submit(accepted). - Cross-seam dedup: whole-payload mirror hash-equals the completion snapshot — a per-write emit plus identical completion collapses to ONE journal event.
- Recovery paths documented: CONSOLIDATE and rePACT Phase 0 journal-fallback clauses for drained scope tasks.
- Consolidation:
task_claim_gatenow consumes the shared_read_lead_session_idSSOT copy inpact_context.
Why
Empirics (v4.6.2 arc + this arc, live): task-store destruction is whole-store, set-drain-shaped, and status-blind — open tasks are destroyed mid-workstream with their metadata while it is still load-bearing. This release closed its own motivating failure class in real time: a live drain destroyed tasks mid-review and the pre-drain journal mirrors preserved everything.
Certification: 3-lane peer review (0 blocking), concurrent audit GREEN-VERIFIED, behavioral base-vs-HEAD (8/8 input classes, real subprocesses), per-AC non-vacuity via directional mutations, full suite 10,979 passed / 0 failed / 0 errors.
v4.6.2 — journal-mirror task metadata
Summary
Task-store destruction (whole-store, status-blind drains at workflow boundaries) can no longer destroy load-bearing task metadata: the session journal now mirrors ALL metadata keys at completion time via the new task_metadata_snapshot event.
What landed
task_metadata_snapshotjournal event emitted from the proven three-seam topology (lead-side completion + post-completion backstop + teammate-frame TaskCompleted twin) via one shared substrate (hooks/shared/task_metadata_snapshot.py)- Full-mirror payload (
SNAPSHOT_EXCLUDE = {"handoff"}only) — ad-hoc keys invented mid-arc are covered by construction - Deterministic dual-cap truncation (16KB/value, 64KB/payload; canonical serialization; largest-first;
_dropped_keysfloor) — key existence is never silently lost - Provenance-tracked marker identity + content-hash dedup/supersession markers in a separate namespace (byte-identical default proven on the existing marker suite, UNMODIFIED)
- Signal-task inclusion (ratified): blocker/algedonic HALT context is now GC-durable
- Harvest three-tier fallback: task file → latest-ts snapshot event (occupant join, arc-scoped) → graceful degrade
- Cross-family task_id sanitization parity (also fixes a b1/b2 marker-key dedup split)
- 117 new tests incl. subprocess real-hook seam legs, GC-drain round-trip, both-modes matrix, adversarial truncation; full suite 10,959 passed / 0 failed / 0 errors
Why
The #1144 incident class: consultation task files were destroyed before harvest, taking sibling-key payloads with them. PREPARE-phase forensics falsified the assumed "~20-min GC window" — destruction is drain-shaped and status-blind, striking at workflow boundaries. This release makes the journal the durable mirror. Follow-ups: per-write mirroring for mid-arc-consumed keys, pathological-input dispositions, sanitization SSOT unification.
v4.6.1 — pause TaskStop guarantee tier + termination-primitive codification
Summary
v4.6.1 fixes the /PACT:pause tmux shutdown leak and codifies TaskStop as the termination primitive across every shutdown-guidance surface, with structural pins guarding the set.
What landed
commands/pause.md: two-tier teammate shutdown (gracefulshutdown_request→TaskStopguarantee tier, mirroring refresh), continue-on-not-found loop guidance, ported post-state guards, unified stagger phrasing.commands/imPACT.md: the when-to-terminate prose no longer offersshutdown_requestas a standalone termination path; "last resort" scoped to the unrecoverable-agent decision; TaskStop signature unified.skills/pact-agent-teams/SKILL.md: honest dual-mode scoping — non-termination on approval is empirically established on the tmux backend; in-process semantics are explicitly unprobed; memory-saving reframed as incremental.agents/pact-orchestrator.md+hooks/teammate_idle.py: the same cooperative-only / termination-primitive codification, including the idle-cleanup ACTION-REQUIRED advisory.- Tests:
TestPauseShutdownStructure(6 pins) +TestShutdownTerminationSkeletonAcrossSurfaces(5 surfaces, uniform token) + emitter-side advisory pin. Suite: 10798 passed / 11 skipped / 0 failed / 0 errors.
Why
The v4.6.0 release-gate live probe (R1) established empirically that in tmux teammate mode an approved shutdown_response does not terminate the teammate's pane/process — so any flow relying on the graceful request alone leaks live teammates. /PACT:refresh already shipped the correct two-tier pattern; this release brings /PACT:pause and every instructional surface in line, under a 3-lane peer review with a verify-only pass (1 Blocking + 12 further findings fixed in-PR; full disposition trail in PR #1161).
v4.6.0 — /PACT:refresh mid-workstream context checkpoint
Summary
v4.6.0 adds /PACT:refresh — a mid-workstream context checkpoint that lets an orchestrator harvest and persist state, shut down teammates, and hand off cleanly across a /compact boundary, resuming via /PACT:bootstrap with fire-once prompt consumption.
What landed
/PACT:refreshcommand (#1144, PR #1150): harvest → persist (session_refreshedjournal event) → teammate shutdown → standby for/compact.- Compact-boundary surfacing:
session_initinjects a "Refreshed workstream detected" prompt (withrefresh_ts=) into the first post-compact turn context. - Fire-once consumption:
/PACT:bootstrapconfirms resumption, respawns the secretary, and writessession_refresh_consumedwith the verbatimrefresh_ts— the prompt never re-surfaces in later sessions. - CI layers: journal schema round-trips, fail-safe resolver + spent-check + arbitration, real-
main()surfacing matrix, reaper-guard truth table, command structural pins. - Runtime gate discharged (PR #1156): live-probe runbook
1144-refresh-checkpoint-live-probes.md— R1 (tmux TaskStop shutdown semantics), R2 (manual/compactrender), R3 (full fire-once cycle) — all GO on installed 4.6.0 / CC 2.1.207, with lead-side independent evidence reproduction.
Why
Long workstreams exhaust context; compaction without choreography loses team state. /PACT:refresh makes the compaction boundary a first-class, journaled checkpoint: state is harvested before teammates stop, the resume prompt is delivered exactly once, and resumption is a declared continuation rather than a guessed one. CI proves the machinery's logic; the live-probe gate proved the runtime choreography CI structurally cannot reach (compact render, tmux shutdown semantics, end-to-end fire-once).
v4.5.7 — merge-guard canonical-identity SSOT (issue 1136)
v4.5.7 — merge-guard canonical-identity SSOT (issue 1136)
Closes the mass_target canonical-identity collision — a delimiter class, not a single-delimiter bug.
What landed
- One injective netstring SSOT —
_canonical_join(items) = "".join(f"{len(i)}:{i}")replacesmass_target's hand-rolled@/#/,joins andbranch_set's NUL-join. Length-prefix framing is injective by construction and content-agnostic, so it eliminates the@/#structural delimiters and closes the whole collision class (the refspec comma and the#remote/refspec boundary) at once — not just the originally-filed comma. - Over-block closed by construction — mint and read derive the identity from the same helper via the one
extract_command_contextSSOT, so a faithful single-command click self-matches. No mint/read call site is split. \x00implicitsentinel unchanged (length-framing keeps it disjoint from a real remote namedimplicit); refspecs now deduped.
Why it is safe
- 54-row bidirectional certification: injectivity by exhibited constructive left-inverse (+ a 5000-random property test), base(9256c93)-COLLIDES → HEAD-REFUSES for both witnesses at the real mint→execute gate, a comprehensive over-block self-match corpus, sentinel isolation, and the dedup row.
- Reviewed by a 4-specialist panel with a fresh independent load-bearing security pass (no HALT, own adversarial vectors) — zero Blocking findings.
- CI portability: baked-base cert files self-skip differentials under a shallow clone, and CI now fetches full history so every differential runs.
Fully resolves issue 1136 (a standalone hardening; the #1129 arc keeps its own separate PR-3 track).