Read-only audit of every place Clara messages someone outside the team or changes AppFolio or a calendar, against Fede's bar: in Supervised nothing happens until Fede or Gera approves it.
Proposed — pending Fede's review. Code read at propflowai main 3904ed28ce and robot main 485ce02 (Oct 23). Builds on the Oct 16 leak audit; nothing was run, no PRs.
Contents
Short version: Supervised holds the message. Almost everything else Clara does in the same turn (guest cards, showings, work orders, vendor assignments, renewal offers, calendar events, calls she starts) still happens straight away. Eleven separate switches across two repos decide whether Clara may act.
Each row groups surfaces that share one send path. 29 of the not-held rows are fixed by construction in the "after" design.
"If the customer is off, the lock should be on. Once we're supervised or live, the robot needs to be on, and then only on approval would that robot be able to action." … "I don't think the robot layer should have that concept."
Fede, 2026-10-23
Already Gera's (build on it, nothing new):
Where Fede's ruling extends it:
| Gate today | Where | After |
|---|---|---|
| Robot writePolicy (readonly / live) | robot lib/accountConfigSeed.ts:313, :395, :507; env fallback 'live' accountContext.ts:415; schema default 'readonly' multiTenantConfig.ts:322 | Deleted as customer config. The lock code stays as a per-request dry-run flag for harness runs. |
| Robot allowedPropertyIds | robot seed route :59-70 ("on a live account the allowlist IS the guard") | Stays only as tenancy scoping, never as a safety switch. Open question for Gera. |
| Robot featureFlags (l4Enabled, l4ShadowMode) | accountConfigSeed.ts | Stays: our plumbing (ADR-0144 bucket b). |
| claraLive (company not live) | clara-live.ts; company-live-gate.ts step 3 | Folded into the state: not live = Off. |
| Module status Off / Supervised / Live | resolve-module-entitlement.ts | Stays. This IS the one state (ADR-0139). |
| supervisedCalls (team / live) | supervised-call-state.ts:60-75 | Inbound routing stays a setting; calls Clara starts become held actions. |
| Leasing-Off in the card queue; Maintenance-Off in the work-order writer; renewal prepare "stored Off" | guest-card-outbox-drain.ts:3378; pms registry; agent-jobs.ts:261 | Deleted: the one gate answers before anything is queued. |
| pmsSideEffectMode (default shadow) | pms/side-effects/dispatcher.ts:154 | Folded or deleted. |
| HeldPmsWrite (turnover + new lease only) | ADR-0143; #20064, #20087, #20088, #20112 | Generalised: every write in Supervised uses it, attached to the draft. |
| 'none' declarations (~50 files), always-on kinds, 'high' carve-out, staff_reply, held-reply ack | text-send-module-fence.test.ts:518; always-on.ts; approval-carve-out.ts:20 | Shrinks to life safety, staff notes, sign-in codes, human-typed. 'high' and staff_reply join the bundle. |
| Team notices (default Live) | send-modules.ts | Stays (staff notes are never held, Fede 10-14). |
| leadScope; claraUserId seat; guestCardWriteFromCalls | lead-scope.ts; Property.pmsMessagingSync; building setting | Stay: features and data, not a "may act" gate. |
| gate.plan % rollout | #21900 (open) | Stays; it releases the whole bundle. |
readonly (accountConfigSeed.ts:507) and its AppFolio user is View Only. The action gate must stand before every robot call before the field goes.account.writePolicy === "readonly" (:370, :405, :766, :1000). Re-key those four checks on a per-request dry-run flag. The interceptors, the probe and the ledger stay.BLOCKED_READONLY with HTTP 200 so nothing retries. Only harness runs should ever get it. No propflowai code reads that status today.allowedPropertyIds override only on readonly rows (seed/route.ts:59-70, :269). Decide its role first."After" = fixed by construction in the decided direction. File paths are in propflowai unless marked. "Who" quotes the comment or PR that made the exemption.
| Surface | To | Send / write | Gates before it | Verdict | Who, why | After |
|---|---|---|---|---|---|---|
| Sent under Supervised: messages to people outside the team (16) | ||||||
| Clara's reply to a resident on a general topic (rent, lease, eviction, anything not maintenance or renewal) | resident | supervised-reply-hold.ts:103-163 ('other' maps to no module); reply-module.ts default_lane_clara:null; process-envelope.ts:1006 sends as 'none' | reply hold only holds by topic; door backstop skipped ('none') | Sent under Supervised | Gera 10-04: "general replies are NOT held" (#16514) | fixed |
| Clara's reply to a vendor who texted in, when the topic reads general | vendor | process-envelope.ts vendor lane; decideSupervisedReplyHold recipient 'vendor' | topic hold only; vendor texts never reach the door check | Sent under Supervised | none; 10-16 audit row, not re-verified today | fixed |
| Texts about an urgent ('high') work order, to resident or vendor | resident, vendor | approval-carve-out.ts:20 (emergency AND high); twilio/client.ts:1212; company-live-gate.ts:464 | carve-out skips the hold and both backstops | Sent under Supervised | "Driver ruling 2026-10-04" in a session (#16821); not Fede or Gera | fixed |
| AppFolio emails its own rental application to the prospect (company uses AppFolio applications) | applicant | send-application-link.ts:545 → pms-generated-application-send.ts:446 → guest-card-outbox-drain.ts:2276/2308 | Leasing Off only; the Clara-written link branch IS held | Sent under Supervised | none cited | fixed |
| Staff answer relayed to the person, reworded by the model | prospect, resident | reply-release.ts:2562 (rewrite) → :2698 email / :2768 text, kind staff_reply | always-on kind: no module read, no backstop | Sent under Supervised | #16349 (Fede 08-18 "the only path"); Fede 10-16 asked to relabel it, open | fixed |
| Escalation callback: Clara phones the person back with the staff answer | resident, prospect | escalation-callback-call.ts:189-201 → initiate-outbound-call.ts:500, declared 'none' | per-building voiceCallbackEnabled (Willows only) + quiet hours | Sent under Supervised | "reactive lanes are exempt BY NAME" (decision b89856042, still open) | fixed |
| Renewal call dials when calls are set to Clara ('live') | resident | renewal-workflow.ts:3929 → initiate-outbound-call.ts:500 | supervisedCalls: refused only when 'team' | Sent under Supervised | Gera 10-01, fe08e2091 ("Messages only dials as Live") | fixed |
| Inbound call answered by Clara with all tools, when calls are set to Clara | prospect, resident | supervised-call-state.ts:60-75 | supervisedCalls 'live'; spoken words are never reviewable | Sent under Supervised | Gera 10-01, fe08e2091 | partly |
| Text sent after Clara's email bounces | prospect | agents/clara/lib/email/ndr-bounce.ts:584, declared 'none' | module Off only | Sent under Supervised | audit 10-04 row N3: "nothing to approve it" | fixed |
| Past-due heads-up email to the building's escalation owner | owner (may be outside the company, unverified) | uncured-sweep.ts:556 → owner-escalation.ts:234, 'none' + internalRecipient | sweep skips a stored Off only | Sent under Supervised | "exempt BY NAME: staff notice" (code comment) | fixed |
| Fail-open: company lookup or Supervised read fails, reply goes out live | any | process-envelope.ts:600-627; resolveModuleStateForOrg 'unresolved' = not Supervised (ADR-0139 §3) | none once the read fails | Sent under Supervised | ADR-0139 §3 ("a settings outage must never switch a product off") | fixed |
| "We got your message" ack while a reply is held | resident | process-envelope.ts:1216-1260; twilio/client.ts:1216 isHeldReplyAckText | exact fixed wording only | Sent under Supervised | Gera D13, 10-10 (no facts in it) | kept on purpose |
| Life-safety reply (evacuate, call 911) | resident | reply-module.ts:66 'emergency' | none, by design | Sent under Supervised | always-on kind (always-on.ts) | kept on purpose |
| Tour confirmation email for a tour staff booked | prospect | staff-booking-confirm-prospect.ts:247-250, :366 | the staff booking counts as the approval | Sent under Supervised | comment :366; a person started it | kept on purpose |
| Mass message to residents | resident | mass-comms.ts:561, 'none' | a manager writes and confirms it | Sent under Supervised | person-composed broadcast | kept on purpose |
| Operator takeover text | resident, prospect | pipeline-lab/run-outbound.ts:625, deliberateHumanSend | admin only, human-typed | Sent under Supervised | "human-typed manual send" | kept on purpose |
| Sent under Supervised: AppFolio, listing and calendar writes (15) | ||||||
| Work order created / updated / closed in AppFolio (pipeline, in-call tools, post-call transcript sweep) | PMS | activities/maintenance.ts:179-221; handle-create-work-order.ts:1448; call-ended/route.ts:1729 | Maintenance Off only | Sent under Supervised | AdminBuildingsCard.tsx:58 "only the message waits" (Gera 10-01) | fixed |
| Vendor assigned in AppFolio while the handyman page waits | PMS | tech-sms-dispatch.ts:1308-1312 → :778 | none | Sent under Supervised | Gera #16898, 10-06: "Supervised holds the MESSAGE, never a PMS write" | fixed |
| Guest card created (call, email lead, tour booked) | PMS | guest-card-outbox-drain.ts sendOne:1179; apply-tour-intent.ts:1299, :1359 | Leasing Off (drain :3378-3392), sync pause | Sent under Supervised | Gera #15566, 10-01: "tour bookings, the guest card … go ahead as in Live" | fixed |
| Showing created / moved / cancelled on the guest card | PMS | showing-sync-stream-consumer.ts:152 → drain :2899, :3064, :3111 | PMS connected only | Sent under Supervised | Fede 10-21: "If they have AppFolio it should always be yes" | fixed |
| Guest card notes: lead outcome, tour booked/cancelled, self-guided callback, team answer, open matter | PMS | lead-outcome-card-note.ts:154/202; self-guided-callback-card-note.ts:34; team-answer-knowledge.ts:244; open-matter.ts:1346 | none | Sent under Supervised | none cited | fixed |
| Guest card marked inactive when follow-ups run out | PMS | prospect-outreach.ts:1915 → cadence-end-inactive.ts:105 | outcome 'no_response' only | Sent under Supervised | Fede 09-30: live everywhere, no switch | fixed |
| Lead source / empty contact field updated | PMS | guest-card-source-intent.ts:63; guest-card-contact-intent.ts:130 | none (triggers unverified) | Sent under Supervised | none | fixed |
| Resident's email saved to AppFolio when they text it | PMS | handle-tenant-email-capture.ts → l4-core setTenantEmailL4 | none (trigger unverified) | Sent under Supervised | none | fixed |
| Renewal offer prepared in AppFolio (may email the resident to sign, unverified) | PMS, resident? | activities/renewal.ts:2893 → agent-jobs.ts:199, :261 | only a stored Off refuses | Sent under Supervised | none cited | fixed |
| Renewal accept / decline / change term decided on a call | PMS | tools/renewal/handlers.ts:2186-2195 | the commit hold works in a text turn only | Sent under Supervised | comment cites Fede 10-19 ("voice … act at once") | fixed |
| Holdover: end lease, month-to-month rent, cancel offer | PMS billing | renewal-holdover.ts:149-194 | three switches, no Supervised check | Sent under Supervised | 10-16 audit, not re-verified today | fixed |
| Listing unposted when a unit goes pre-leased | PMS, listing sites | auto-unpublish-listing-hook.ts:83/120 | Leasing Off only | Sent under Supervised | Fede 07-23: "ALWAYS ON" | fixed |
| Listing published on notice to vacate | PMS, listing sites | auto-publish-listing-hook.ts:170 | per-building publish switch | Sent under Supervised | 10-16 audit, not re-verified today | fixed |
| Tour row + event on the staff calendar (incl. retry); tour workflow started | calendar | apply-tour-intent.ts:171, :1398, :1441 → sync-tour.ts; tour-calendar-retry.ts:95 | none; runs before the agent loop (inbound-router.ts:1446-1530) | Sent under Supervised | Gera #15566, 10-01 | fixed |
| Vendor visit / key pickup calendar events | calendar | sync-vendor-visit.ts:384; sync-key-pickup.ts:184 | none; no attendees | Sent under Supervised | none | fixed |
| Sent under Supervised: staff and internal (intended) (3) | ||||||
| Staff notice emails: tour booked, no host, lost slot, renewal notices, PM reminders, page-property-manager | customer's staff | send-pm-tour-confirmation.ts:640-659; tour-no-host.ts:242; team-notice-send.ts:128; renewal-notify.ts:331; pm-action-reminder.ts:579 | Team notices module (default Live); module-owned notices read Supervised as Live (team-notice-send.ts:130) | Sent under Supervised | Fede 10-14 and 10-16: staff notes are never held | kept on purpose |
| Emergency chain, on-call heads-up, weekend tour heads-up texts | customer's staff | emergency-alert-notices.ts:338/431/486; weekend-tour-checkin.ts:445 | none, by design | Sent under Supervised | always-on kinds emergency / team_alert | kept on purpose |
| The hold itself: approval row, Slack card, reviewer email | our team | bake-gate.ts; held-reviewer-email.ts | n/a | Sent under Supervised | intended | kept on purpose |
| Not reachable today (7) | ||||||
| Vendor dispatch, quote and chase emails | vendor | send-vendor-dispatch.ts:441; send-vendor-quote.ts:215; vendor-chase.ts:447 | redirected to a test inbox (vendor-poc-recipients.ts:15); no approval field, so latent | Not reachable today | "no real vendor is ever emailed today" | fixed |
| Prospect invited to a tour calendar event | prospect | no production caller passes attendees | n/a | Not reachable today | — | — |
| Vendor scheduling call; vendor redial | vendor | initiate-vendor-call.ts:224; vendor-redial.ts | retired stubs | Not reachable today | Gera 10-19 "Delete it" | — |
| Smart-lock code write | lock | tour-access-provider.ts | only the manual provider exists | Not reachable today | — | — |
| Handyman texts set to 'live' bypasses the hold | vendor | handyman-text-review.ts:46 | not set at any company | Not reachable today | Gera 10-15 | fixed |
| SES envelope adapter reply; robot test call; dead-letter resend | various | ses-email.ts:381; robot-call.ts:178; email client resendDeadLetteredEmail | no production caller / our own lines / staff only | Not reachable today | — | — |
| Renewal and new-lease packet order by Clara | PMS | renewal-paperwork.ts:122; new-lease-paperwork.ts:136 | default: the team prepares | Not reachable today | ADR-0144 §5: paper writes not held | fixed |
| Held today (15) | ||||||
| Clara's reply on a maintenance or renewal topic, or to any prospect (text and 3 email lanes) | resident, prospect | process-envelope.ts:576-602; inbound-processor handler.ts:736; process-pms-email.ts:281 | reply hold + door backstop | Held | — | — |
| First email to a new lead; first-reply text | prospect | clara-guest-card-first-email-send.ts:744/961; first-reply-text.ts:230-319 | isLeasingSupervisedAt + backstop | Held | — | — |
| Follow-up cadence and re-engagement (texts, emails, post-call) | prospect | prospect-outreach.ts:1232-1604; prospect-reengagement.ts:906-1175 | per-touch review + backstops (enrolment itself is not gated: outreach-trigger.ts:147) | Held | — | — |
| Tour reminders, lost-slot notice, confirm nudge, weekend check-in, after-call tour text | prospect | tour.ts:930-1722; tour-confirm-nudge.ts:542-815; weekend-tour-checkin.ts:348; send-tour-outcome-sms.ts:716-1636 | review helpers + backstops | Held | — | — |
| Self-guided lockbox code and leave check | prospect | self-guided-lockbox-code-delivery.ts:260-396; self-guided-leave.ts:342-486 | rerun review + backstops | Held | — | — |
| Application link Clara writes; post-tour chase | prospect | send-application-link.ts:1047-1616; post-tour-chase.ts:1016-1285 | review helpers + backstops | Held | — | — |
| Clara's reply after a staff answer | resident, prospect | team-answer-supervised-hold.ts:74-109; reply-release.ts:1537 | reply hold (general topic to a resident declares 'none') | Held | — | — |
| Maintenance resident texts: work order created, rating ask, vendor relays | resident | notify-tenant-of-pms-creation.ts:245; rating.ts:830; handle-vendor-completion.ts:1405-2671 | maintenance review (urgent excepted) | Held | — | — |
| Handyman page, relays, stand-down; turnover crew text; turnover vendor email | vendor | tech-sms-dispatch.ts:1297-1436; in-house-dispatch.ts:586; send-turnover-vendor-dispatch.ts:612 | handyman review / maintenance review | Held | — | — |
| Renewal offer, reminders, letter, email, recap, lease-ending warning, tool texts | resident | activities/renewal.ts:1026-2335; recap-sms.ts:254; mtm-warning-send.ts:59 | renewal review + backstops | Held | — | — |
| Collections past-due reminder; weekly owner report | resident, owner | approved-dun-send-ops.ts:493; collections-live-send.ts:72; run-digest.ts:663 | review queue; Live-only send | Held | — | — |
| Tenant text sent through AppFolio's texting inbox | resident | appfolio-inbox-text.ts:212; pms-text-door-drain.ts:120 | same text backstop | Held | — | — |
| Clara claims the guest card + one note per sent message | PMS | guest-card-write-back-outbox.ts:50; guest-card-message-note.ts:108 | runs only after a sent or approved message | Held | — | — |
| Renewal tool commits in a text turn | PMS | tools/renewal/handlers.ts:1037-1950 holdCommitUnderSupervised | deferred with the reply (#21625) | Held | — | — |
| Move-out charges, move-out draft, turnover work orders, unit re-list, new lease | PMS | turnover.ts:819-1893; inspection-complete-move-out-draft-hook.ts:273; execute-lease-draft.ts:44 | HeldPmsWrite or Live-only (ADR-0143) | Held | — | — |