Independent audit of the person-mute shipped last night (PR #6526 + #6528), by a session that built none of it. Fede's two questions answered, with evidence.
2026-08-29 · 6-agent adversarial fleet + hand verification · read-only prod, sandbox bench only · spec audited = the design page at commit 7fae7bf4 (silence + 3 exceptions: deliberate human send, emergency/safety, one team notification on inbound)
Q1 — does it cover all channels? No. The core wire gate is real and most lanes traverse it, but five verified leaks remain — one live in production today for a real Camellia tenant. Q2 — is it the smallest solution? No, but the core is close. ~1,000 of ~3,560 added lines serve the voice mechanism Fede already rejected and can be deleted as a unit; one design change (check the person, not the identifier) both simplifies the feature and closes its worst leak.
| Egress / trigger path | Verdict | Evidence |
|---|---|---|
| SMS replies (inbound → agent loop) | covered | MUTED PERSON GATE at conversation-manager.ts:4196 persists and returns before any model call; bench-proven live (zero assistant rows). |
| Email replies | covered | Same gate, same code path; the pre-gate model call is a tool-less classifier (no side-effecting tools reachable). Lane-normalization bug was caught in review and fixed pre-merge. |
| SMS/MMS outbound wire | covered | sendSms/sendMms → checkSmsSendAllowed → checkSuppression; muted blocks even category transactional. |
| Email outbound wire (Graph, SES, SendGrid) | covered | Both Graph send doors + the shared email client gate on checkSuppression with a declared category. |
| Outreach cadence, chase, re-engagement, renewals, collections | covered | Each Temporal activity calls the gate with category outreach before sending; renewal voice dial gated too. |
| Voice outbound — callback/renewal lanes | covered | Escalation callback checks the gate as transactional (blocked under mute); renewal dial checks as outreach. |
| Voice outbound — vendor dialing | uncovered | The whole chain (dial-approved-session.ts → initiate-vendor-call.ts → initiate-outbound-call.ts) contains zero suppression references. A muted person's phone still rings if they're dialed as a vendor. Contradicts the builder's "all raw lanes traverse the gate" claim. |
| Contact added after the mute | uncovered | Live-proven on bench: mute fans out per identifier once, at mute time. A new email/phone claim added later (PMS sync, application) has no row — the inbound gate AND the outbound wire both treat it as unmuted, while isPersonMuted() correctly says muted. |
| Ops-feed Slack posts | leaks | A muted person's inbound text/email posts their name, unit and message content to the property Slack channel (conversation-manager.ts:3054/:3248, before the gate at :4196), and again at voice call-end. Team-facing, so partially aligned with the new one-notification exception in spirit — but content-bearing, un-deduped, fires multiple times, and nobody designed it as the notification. |
| Voice inbound (as merged) | weak, slated for deletion | Enforcement is prompt-only (no code-level tool hard-stop), and the context-blanking list names variable keys the personalization route doesn't actually emit — real resident context still reaches the model. Fede has already re-scoped voice to a pre-agent forward at the phone layer; these defects confirm that call. |
| Escalated-thread team notices | covered | person_muted outcome suppresses the rail unconditionally — note this is the opposite of the newly decided one-notification exception, which has zero implementation in main yet (002's lane). |
| Escalation matters / tour machinery on muted inbound | covered | Blocked pre-tool-loop for SMS/email; one gap: the tour-day-fallback team email in the voice no-destination posture has no mute check. |
| Model self-service | covered | Clara can never mute or unmute anyone: muted is excluded from every model-selectable scope list, with a compile-time exhaustiveness check. |
| Calendar invites | dormant risk | No production code sets attendees on calendar events today, so Outlook/Google never emails invites. The day someone wires attendees in (a natural tour-reminder ask), those emails come from Microsoft's servers, fully outside our gate. |
transactional and outreach under a mute; anything else falls through to allow. That's deliberate for the two safety categories, but a lane passing a novel or undefined category string leaks past a mute silently (bench-verified at the gate). One default: block-if-muted branch fixes it.skipConsentCheck (today only staff MFA uses it — exempt by design anyway) and internalRecipient. Nothing stops a future caller setting either on a resident-facing send.deliberateHumanSend works through the pipeline-lab rail (bench-proven) but is never threaded through the agents-side dispatch() path — there it over-blocks (fails safe, doesn't leak) rather than allowing the one deliberate human send. The builder's "✅ proven" covered only the working rail.The two commits added ~3,560 lines across 41 files. They split cleanly:
person-mute.ts, the inbound gate, the notice guard, the API route, and the model-surface hardening. This part passes Fede's bar.muted-caller-posture.ts (153) + its test (180), the MUTED CALLER prompt section in triage (19), the personalization-route mute plumbing (~68), the muted_caller stanzas in 5 eval prompt files (30), both voice-harness scenarios (208), and the related drift-test additions. All of it exists only to serve the mechanism Fede rejected; its replacement is one mute lookup at the telephony hop.isPersonMuted() (already written and tested, currently unwired) at the enforcement points instead makes the mute person-grained by construction: the fan-out machinery, the stash/restore encoding, and the leak all go away together. Costs one extra person lookup per gated send.| Decision | Options | |
|---|---|---|
| A | Daniel Brehm's "full mute" order isn't in effect | A1 (recommended): re-record him through the new mute path now — one prod write, makes the record match the order, and is the feature's first real arming. A2: leave as-is until the planned human contact happens (he keeps receiving Clara's replies meanwhile). A3: have a human reply first, then mute. |
| B | Enforcement grain | B1 (recommended): switch the gates to person-level isPersonMuted() — closes the new-claim leak and deletes the fan-out complexity. B2: keep per-identifier rows and add a fan-out hook on every new-claim write (more machinery, same behavior). |
| C | The muted-inbound team notification you decided today | C1 (recommended): reshape the existing ops-feed post into it — it already fires on the right event to the right channel; add mute-awareness, dedupe per person per window, strip to who/what/pane-link, and suppress the duplicate voice call-end post. C2: gate ops-feed off for muted people and build the notification separately (002's current lane — coordinate so we don't ship both). |
| D | Vendor dial lane | D1 (recommended): add the suppression check to initiate-vendor-call (~5 lines, category transactional), matching the callback lane. D2: accept that mute is resident-only and enforce that at the mute API instead (reject vendor identifiers). |
| E | Unknown-category fall-through | E1 (recommended): block unknown categories under a mute (one branch in the matrix). E2: leave as-is and rely on TypeScript. |
Six independent agents (egress client sweep, lane map, inbound trigger map, adversarial bench, simplicity audit, dormancy re-check) plus hand verification of every load-bearing finding by the coordinating session. Bench: all 8 of the builder's scenarios re-run green on merged main, then 8 new adversarial probes on sandbox-only synthetic identities, every created row cleaned up. Boundaries: outbound suppression in the harness proves the decision at the gate, not carrier delivery; no live voice calls were placed (per mandate); the Temporal chase lane was exercised at its gate call site, not via a full workflow invocation; a subscription rate limit capped one agent's tail-end probing. Builder reports were treated as claims and four were contradicted (raw-lane coverage, takeover proof, the pinning drift test, the magic-number state); the rest held up.