Skip to content

fix(hooks): decouple secretary-spawn carve-out to a members-only join witness (#1023)#1025

Merged
michael-wojcik merged 5 commits into
Synaptic-Labs-AI:mainfrom
michael-wojcik:fix-bootstrap-carveout-1023
Jun 24, 2026
Merged

fix(hooks): decouple secretary-spawn carve-out to a members-only join witness (#1023)#1025
michael-wojcik merged 5 commits into
Synaptic-Labs-AI:mainfrom
michael-wojcik:fix-bootstrap-carveout-1023

Conversation

@michael-wojcik

Copy link
Copy Markdown
Collaborator

Summary

Fixes the #1023 bootstrap-gate deadlock regression introduced by PR #1021 (the #1019 fix, v4.4.39). The first fresh CLI session under 4.4.39 had its canonical secretary spawn DENIED by bootstrap_gate, re-deadlocking bootstrap.

Root cause

#1021 widened bootstrap_marker_writer._team_has_secretary with a config-less inbox-witness fallback for the marker writer. But bootstrap_gate._is_canonical_secretary_spawn binding 5 also consumes that predicate. TaskUpdate(owner="secretary") (bootstrap Step 2) creates inboxes/secretary.json BEFORE the Agent(secretary) spawn (Step 3), so the inbox arm returned True prematurely → binding 5 (not True) → carve-out declined → DENY of the very spawn it exists to permit.

What landed (3 commits)

  1. 5463821afix: members[]-only JOIN witness _secretary_in_members() in bootstrap_gate.py for binding 5; the local marker_writer import is removed; _team_has_secretary keeps its inbox DISPATCH witness for the marker writer. Broad except Exception: return False (load-bearing — catches the get_claude_config_dir()/Path.home() RuntimeError seam that would otherwise re-deadlock). Migrates the obsolete-design tests that encoded the old error→DENY direction, strengthens the import-discipline test to any-scope, and shrinks the bootstrap_gate SEAM-closure literal.
  2. a56d5134test: non-vacuous regression (R1 premature-inbox repro + in-process/tmux legs + config-less always-fire + empty-SSOT-preserved + containment + helper unit tests).
  3. 4e3370a3chore: version bump to 4.4.40 (PATCH).

Key decisions

  • Fork, not revert — the marker writer genuinely needs the inbox arm (probe-confirmed via TestInboxWitnessGate2); a revert would re-break Bootstrap deadlock under Claude Desktop / Agent-SDK harness: no native team scaffold → config-less UUID team defeats name resolution, #989 self-heal, and marker #1019's Desktop fix.
  • JOIN vs DISPATCH witness — the carve-out needs "has the secretary JOINED members[]?"; only the marker writer needs the dispatch/inbox witness.
  • Uniform error→ALLOW — a witness-read error now fires the carve-out (allow) instead of deny. Safe: the witness is reachable only after bindings 1-3 (exact Agent + pact-secretary + secretary) + the empty-SSOT guard, so it only ever permits the canonical secretary spawn. deny-on-error was the re-deadlock the fix removes.
  • One-shot migrates to marker-presence (D3 docstring record) — under config-less Desktop members[] is structurally empty, so the is_marker_set fast-path (which runs before the carve-out) is the durable one-shot.
  • empty-SSOT fail-closed preserved — the if not expected_team: return False guard stays ahead of the witness; empty/None team → DENY, unchanged.

Testing

Scope

  • Touches bootstrap_gate.py only (source); bootstrap_marker_writer.py unchanged.
  • Does NOT touch session_init.py docstring-parity or pact-protocols.md SSOT extracts.

Closes #1023. Deferred follow-up tracked as #1024 (marker writer's inbox witness optionally excluding task-assignment-only inboxes — availability/liveness, not priv-esc).

… witness (Synaptic-Labs-AI#1023)

Synaptic-Labs-AI#1021's inbox-witness fallback in _team_has_secretary was also consumed by the
bootstrap_gate carve-out (binding 5). The inbox is created by
TaskUpdate(owner=secretary) before the Agent spawn, so the witness read True
prematurely and the carve-out DENIED the canonical secretary spawn —
re-deadlocking bootstrap on every fresh CLI session.

Add a members[]-only JOIN witness _secretary_in_members() in bootstrap_gate
(broad except for the get_claude_config_dir/Path.home RuntimeError seam); binding
5 calls it and the reciprocal-cycle local import is removed. _team_has_secretary
keeps its inbox DISPATCH witness for the marker writer.

A witness-read error now fires the carve-out (allow) instead of deny — the safe
direction, since it only ever permits the canonical secretary spawn (bindings 1-3
gate all else). Migrate the obsolete-design tests that encoded the old
error->deny direction, strengthen the import-discipline test to any-scope, and
shrink the bootstrap_gate SEAM-closure literal to match the live import graph.
…tic-Labs-AI#1023)

Additive regression + unit coverage for the members-only join witness, on top
of the obsolete-design migrations in the fix commit.

TestPrematureInboxCarveOutRegression: a premature task-assignment inbox
(members[] still lacking the secretary) no longer defeats the carve-out — the
canonical secretary spawn is allowed (R1), in both in-process and tmux
topologies (R2/R3); the carve-out fires under config-less Desktop (C2);
empty-SSOT still fails closed even with a premature inbox (E1); and a
non-canonical input stays blocked even with a pending witness error
(containment). TestSecretaryInMembersUnit: the helper is members-only and
broad-except total (False on a raising read).

Non-vacuity measured by single-file source revert of bootstrap_gate.py: RED set
{7 failed, 2 passed} (R1/R2/R3/C2 behavioral; U1 trio import-absent; E1 +
containment correctly fix-independent), restored byte-identical.
PATCH — the Synaptic-Labs-AI#1023 bootstrap-gate carve-out regression fix (restores the
pre-Synaptic-Labs-AI#1019 CLI secretary spawn; no visible behavioral change beyond that).
…t line number (Synaptic-Labs-AI#1023)

Re-cite the D3 one-shot-migration docstring anchors (and two sibling drift-prone
self-references) by symbol name instead of bare line numbers, which had drifted
when the _secretary_in_members helper was inserted (the cited mechanism was
always correct; only the numbers were stale). Docstring/comment-only, AST-proven
behavior-neutral. The 16 cross-subsystem line pins found in the audit are left
for a separate cleanup (some are load-bearing, e.g. the session_init.py
docstring-parity byte-equality ranges).
…/ intentional_wait / session_state

Convert 7 purely-navigational `<file>.py:NNN` self-references to durable
symbol / pattern / test-name anchors across 4 hook files (merge_guard_post,
merge_guard_pre, shared/intentional_wait, shared/session_state). Docstring and
comment only, AST-proven behavior-neutral. Each converted citation was verified
to have no test coupling on its line number.

The pin_caps cluster (its citations ARE test-coupled — hook+test pairs that must
convert together) and the session_init.py docstring-parity byte-equality ranges
(deliberately line-based for the parity gate) are left for a dedicated follow-up.
@michael-wojcik michael-wojcik merged commit 66e52c0 into Synaptic-Labs-AI:main Jun 24, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant