Skip to content

Releases: Synaptic-Labs-AI/PACT-Plugin

v4.6.8

Choose a tag to compare

@michael-wojcik michael-wojcik released this 16 Jul 09:29
b4041cc

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 helperenv={**os.environ, HOME:env_root, CLAUDE_CONFIG_DIR:env_root} at the test spawn site (Form A+B belt-and-suspenders); env_root defaults 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

Choose a tag to compare

@michael-wojcik michael-wojcik released this 16 Jul 04:28
f086d6a

Summary

Test config-root isolation + teams-reaper docstring framing. Fixes #1186, #1187, #1185.

What landed

  • #1186: autouse conftest fixture _isolate_config_root_to_tmp (scrub CLAUDE_CONFIG_DIR + redirect Path.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_teams added 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 from generate_team_name's session- output; widening toward session-* would arm shutil.rmtree against 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

Choose a tag to compare

@michael-wojcik michael-wojcik released this 15 Jul 10:53
19f2df1

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

  • P1git commit --trailer + gh --assignee family positional over-blocks closed via flag-anchored carrier extensions.
  • P2strip-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/stdbuf to arity-aware recursion (with hush/lash/msh shell-applet recognition so wrapped executors stay caught) + a general attached-short-value walker. find/flock remain 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)

Choose a tag to compare

@michael-wojcik michael-wojcik released this 14 Jul 17:11
c21eae1

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/notes messages) — with cherry-pick/revert -m <number> correctly excluded.
  • Attached-equals forms (--message=, --title=, general NAME="…") via the variable-assignment strip, with a dotted-key guard that keeps git -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

Choose a tag to compare

@michael-wojcik michael-wojcik released this 13 Jul 10:24
f6e3639

v4.6.4 — Unify empty-team dedup posture across snapshot seams

What landed (#1169, PR #1171)

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

Choose a tag to compare

@michael-wojcik michael-wojcik released this 12 Jul 23:44
707238b

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_KEYS registry (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 on teachback_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_gate now consumes the shared _read_lead_session_id SSOT copy in pact_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

Choose a tag to compare

@michael-wojcik michael-wojcik released this 12 Jul 18:39
fb95921

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_snapshot journal 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_keys floor) — 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

Choose a tag to compare

@michael-wojcik michael-wojcik released this 12 Jul 14:12
485046d

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 (graceful shutdown_requestTaskStop guarantee 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 offers shutdown_request as 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

Choose a tag to compare

@michael-wojcik michael-wojcik released this 12 Jul 06:42
1a4f1c9

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:refresh command (#1144, PR #1150): harvest → persist (session_refreshed journal event) → teammate shutdown → standby for /compact.
  • Compact-boundary surfacing: session_init injects a "Refreshed workstream detected" prompt (with refresh_ts=) into the first post-compact turn context.
  • Fire-once consumption: /PACT:bootstrap confirms resumption, respawns the secretary, and writes session_refresh_consumed with the verbatim refresh_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 /compact render), 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)

Choose a tag to compare

@michael-wojcik michael-wojcik released this 10 Jul 13:00
51e6c5a

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}") replaces mass_target's hand-rolled @/#/, joins and branch_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_context SSOT, so a faithful single-command click self-matches. No mint/read call site is split.
  • \x00implicit sentinel unchanged (length-framing keeps it disjoint from a real remote named implicit); 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).