Supervised mode: what it holds and what it does not (audit, 2026-10-23)

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

  1. Before and after
  2. Headline counts
  3. Decided direction
  4. Each gate, after
  5. What the robot needs
  6. Every surface
  7. Not verified

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.

Before and after, counts, decided direction, gates, robot, every surface, not verified

Before and after

Before: the hold wraps the send, everything else goes around it

Who decides "may Clara act?" today: 11 places, 2 repos claraLivecompany row Module statusOff / Supervised / Live supervisedCallsteam / live Team noticesdefault Live leadScopewhich leads Clara takes ~50 'none' + 4 kinds+ 'high' carve-out Leasing-Off in card queueguest-card drain only Maintenance-Off in WO writerwork orders only claraUserId seatassign only Robot writePolicyreadonly / live, hard-coded seed Robot allowedPropertyIds"the guard" on live accounts Clara's turnreply text+ tool actions+ background jobs+ calls Supervised holdwraps the MESSAGE send onlyreply hold + text / email door backstops Text / emailto the person AppFolio robotcards, showings, work orders, offers Calendartour + vendor events Calls Clara startsrenewal call, callback Exempt sendsgeneral resident reply,urgent WO, relay, bounce text Dashed red = goes around the hold. Robot writePolicy is the only thing stopping Situs writes today (plus their View Only AppFolio user).

After: one state, one gate, one held bundle

ONE state in PropFlowOff · Supervised · Liveper module, building over company (ADR-0139) Clara's turn / jobproduces a draft:message + PMS writes+ calendar + calls ONE action gateOff: nothing actsSupervised: hold the bundleLive: actlife safety always acts Held bundle (one row)reply + every writeApprove: all go, onceReject: all droppedHeldPmsWrite, generalised Text / email / call doors AppFolio robotplain executor, no policy Calendar Stays outside the hold on purpose: notes to the customer's staff (Fede 10-14),human-typed sends, sign-in codes, life safety. Robot read-only lock = dry-run flag only. Gera's rollout % (#21900) releases the whole bundle, not only the reply.

Headline counts

56surfaces audited (grouped rows)
34sent under Supervised (11 outside messages, 15 writes, 8 on purpose)
15held today
7not reachable today

Each row groups surfaces that share one send path. 29 of the not-held rows are fixed by construction in the "after" design.

Decided direction: Gera's design + Fede's Oct 23 ruling

"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:

Each gate today, after

Gate todayWhereAfter
Robot writePolicy (readonly / live)robot lib/accountConfigSeed.ts:313, :395, :507; env fallback 'live' accountContext.ts:415; schema default 'readonly' multiTenantConfig.ts:322Deleted as customer config. The lock code stays as a per-request dry-run flag for harness runs.
Robot allowedPropertyIdsrobot 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.tsStays: our plumbing (ADR-0144 bucket b).
claraLive (company not live)clara-live.ts; company-live-gate.ts step 3Folded into the state: not live = Off.
Module status Off / Supervised / Liveresolve-module-entitlement.tsStays. This IS the one state (ADR-0139).
supervisedCalls (team / live)supervised-call-state.ts:60-75Inbound 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:261Deleted: the one gate answers before anything is queued.
pmsSideEffectMode (default shadow)pms/side-effects/dispatcher.ts:154Folded or deleted.
HeldPmsWrite (turnover + new lease only)ADR-0143; #20064, #20087, #20088, #20112Generalised: every write in Supervised uses it, attached to the draft.
'none' declarations (~50 files), always-on kinds, 'high' carve-out, staff_reply, held-reply acktext-send-module-fence.test.ts:518; always-on.ts; approval-carve-out.ts:20Shrinks to life safety, staff notes, sign-in codes, human-typed. 'high' and staff_reply join the bundle.
Team notices (default Live)send-modules.tsStays (staff notes are never held, Fede 10-14).
leadScope; claraUserId seat; guestCardWriteFromCallslead-scope.ts; Property.pmsMessagingSync; building settingStay: features and data, not a "may act" gate.
gate.plan % rollout#21900 (open)Stays; it releases the whole bundle.

What the robot needs to drop writePolicy safely

  1. PropFlow's gate first. Today Situs is safe only because its row is readonly (accountConfigSeed.ts:507) and its AppFolio user is View Only. The action gate must stand before every robot call before the field goes.
  2. Keep the lock as a dry-run flag. lib/readonlyLock.ts arms on 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.
  3. Keep the dry-run answer. accountRoute.ts:279-300 returns BLOCKED_READONLY with HTTP 200 so nothing retries. Only harness runs should ever get it. No propflowai code reads that status today.
  4. Untangle the allowlist. The seed route takes an allowedPropertyIds override only on readonly rows (seed/route.ts:59-70, :269). Decide its role first.
  5. No flip route exists. account-config/[accountId] is GET only. Changing the policy means a code change plus a forced reseed, so deleting it removes a code path, not an admin button.

Every surface, worst 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.

SurfaceToSend / writeGates before itVerdictWho, whyAfter
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)residentsupervised-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 SupervisedGera 10-04: "general replies are NOT held" (#16514)fixed
Clara's reply to a vendor who texted in, when the topic reads generalvendorprocess-envelope.ts vendor lane; decideSupervisedReplyHold recipient 'vendor'topic hold only; vendor texts never reach the door checkSent under Supervisednone; 10-16 audit row, not re-verified todayfixed
Texts about an urgent ('high') work order, to resident or vendorresident, vendorapproval-carve-out.ts:20 (emergency AND high); twilio/client.ts:1212; company-live-gate.ts:464carve-out skips the hold and both backstopsSent under Supervised"Driver ruling 2026-10-04" in a session (#16821); not Fede or Gerafixed
AppFolio emails its own rental application to the prospect (company uses AppFolio applications)applicantsend-application-link.ts:545 → pms-generated-application-send.ts:446 → guest-card-outbox-drain.ts:2276/2308Leasing Off only; the Clara-written link branch IS heldSent under Supervisednone citedfixed
Staff answer relayed to the person, reworded by the modelprospect, residentreply-release.ts:2562 (rewrite) → :2698 email / :2768 text, kind staff_replyalways-on kind: no module read, no backstopSent under Supervised#16349 (Fede 08-18 "the only path"); Fede 10-16 asked to relabel it, openfixed
Escalation callback: Clara phones the person back with the staff answerresident, prospectescalation-callback-call.ts:189-201 → initiate-outbound-call.ts:500, declared 'none'per-building voiceCallbackEnabled (Willows only) + quiet hoursSent under Supervised"reactive lanes are exempt BY NAME" (decision b89856042, still open)fixed
Renewal call dials when calls are set to Clara ('live')residentrenewal-workflow.ts:3929 → initiate-outbound-call.ts:500supervisedCalls: refused only when 'team'Sent under SupervisedGera 10-01, fe08e2091 ("Messages only dials as Live")fixed
Inbound call answered by Clara with all tools, when calls are set to Claraprospect, residentsupervised-call-state.ts:60-75supervisedCalls 'live'; spoken words are never reviewableSent under SupervisedGera 10-01, fe08e2091partly
Text sent after Clara's email bouncesprospectagents/clara/lib/email/ndr-bounce.ts:584, declared 'none'module Off onlySent under Supervisedaudit 10-04 row N3: "nothing to approve it"fixed
Past-due heads-up email to the building's escalation ownerowner (may be outside the company, unverified)uncured-sweep.ts:556 → owner-escalation.ts:234, 'none' + internalRecipientsweep skips a stored Off onlySent under Supervised"exempt BY NAME: staff notice" (code comment)fixed
Fail-open: company lookup or Supervised read fails, reply goes out liveanyprocess-envelope.ts:600-627; resolveModuleStateForOrg 'unresolved' = not Supervised (ADR-0139 §3)none once the read failsSent under SupervisedADR-0139 §3 ("a settings outage must never switch a product off")fixed
"We got your message" ack while a reply is heldresidentprocess-envelope.ts:1216-1260; twilio/client.ts:1216 isHeldReplyAckTextexact fixed wording onlySent under SupervisedGera D13, 10-10 (no facts in it)kept on purpose
Life-safety reply (evacuate, call 911)residentreply-module.ts:66 'emergency'none, by designSent under Supervisedalways-on kind (always-on.ts)kept on purpose
Tour confirmation email for a tour staff bookedprospectstaff-booking-confirm-prospect.ts:247-250, :366the staff booking counts as the approvalSent under Supervisedcomment :366; a person started itkept on purpose
Mass message to residentsresidentmass-comms.ts:561, 'none'a manager writes and confirms itSent under Supervisedperson-composed broadcastkept on purpose
Operator takeover textresident, prospectpipeline-lab/run-outbound.ts:625, deliberateHumanSendadmin only, human-typedSent 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)PMSactivities/maintenance.ts:179-221; handle-create-work-order.ts:1448; call-ended/route.ts:1729Maintenance Off onlySent under SupervisedAdminBuildingsCard.tsx:58 "only the message waits" (Gera 10-01)fixed
Vendor assigned in AppFolio while the handyman page waitsPMStech-sms-dispatch.ts:1308-1312 → :778noneSent under SupervisedGera #16898, 10-06: "Supervised holds the MESSAGE, never a PMS write"fixed
Guest card created (call, email lead, tour booked)PMSguest-card-outbox-drain.ts sendOne:1179; apply-tour-intent.ts:1299, :1359Leasing Off (drain :3378-3392), sync pauseSent under SupervisedGera #15566, 10-01: "tour bookings, the guest card … go ahead as in Live"fixed
Showing created / moved / cancelled on the guest cardPMSshowing-sync-stream-consumer.ts:152 → drain :2899, :3064, :3111PMS connected onlySent under SupervisedFede 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 matterPMSlead-outcome-card-note.ts:154/202; self-guided-callback-card-note.ts:34; team-answer-knowledge.ts:244; open-matter.ts:1346noneSent under Supervisednone citedfixed
Guest card marked inactive when follow-ups run outPMSprospect-outreach.ts:1915 → cadence-end-inactive.ts:105outcome 'no_response' onlySent under SupervisedFede 09-30: live everywhere, no switchfixed
Lead source / empty contact field updatedPMSguest-card-source-intent.ts:63; guest-card-contact-intent.ts:130none (triggers unverified)Sent under Supervisednonefixed
Resident's email saved to AppFolio when they text itPMShandle-tenant-email-capture.ts → l4-core setTenantEmailL4none (trigger unverified)Sent under Supervisednonefixed
Renewal offer prepared in AppFolio (may email the resident to sign, unverified)PMS, resident?activities/renewal.ts:2893 → agent-jobs.ts:199, :261only a stored Off refusesSent under Supervisednone citedfixed
Renewal accept / decline / change term decided on a callPMStools/renewal/handlers.ts:2186-2195the commit hold works in a text turn onlySent under Supervisedcomment cites Fede 10-19 ("voice … act at once")fixed
Holdover: end lease, month-to-month rent, cancel offerPMS billingrenewal-holdover.ts:149-194three switches, no Supervised checkSent under Supervised10-16 audit, not re-verified todayfixed
Listing unposted when a unit goes pre-leasedPMS, listing sitesauto-unpublish-listing-hook.ts:83/120Leasing Off onlySent under SupervisedFede 07-23: "ALWAYS ON"fixed
Listing published on notice to vacatePMS, listing sitesauto-publish-listing-hook.ts:170per-building publish switchSent under Supervised10-16 audit, not re-verified todayfixed
Tour row + event on the staff calendar (incl. retry); tour workflow startedcalendarapply-tour-intent.ts:171, :1398, :1441 → sync-tour.ts; tour-calendar-retry.ts:95none; runs before the agent loop (inbound-router.ts:1446-1530)Sent under SupervisedGera #15566, 10-01fixed
Vendor visit / key pickup calendar eventscalendarsync-vendor-visit.ts:384; sync-key-pickup.ts:184none; no attendeesSent under Supervisednonefixed
Sent under Supervised: staff and internal (intended) (3)
Staff notice emails: tour booked, no host, lost slot, renewal notices, PM reminders, page-property-managercustomer's staffsend-pm-tour-confirmation.ts:640-659; tour-no-host.ts:242; team-notice-send.ts:128; renewal-notify.ts:331; pm-action-reminder.ts:579Team notices module (default Live); module-owned notices read Supervised as Live (team-notice-send.ts:130)Sent under SupervisedFede 10-14 and 10-16: staff notes are never heldkept on purpose
Emergency chain, on-call heads-up, weekend tour heads-up textscustomer's staffemergency-alert-notices.ts:338/431/486; weekend-tour-checkin.ts:445none, by designSent under Supervisedalways-on kinds emergency / team_alertkept on purpose
The hold itself: approval row, Slack card, reviewer emailour teambake-gate.ts; held-reviewer-email.tsn/aSent under Supervisedintendedkept on purpose
Not reachable today (7)
Vendor dispatch, quote and chase emailsvendorsend-vendor-dispatch.ts:441; send-vendor-quote.ts:215; vendor-chase.ts:447redirected to a test inbox (vendor-poc-recipients.ts:15); no approval field, so latentNot reachable today"no real vendor is ever emailed today"fixed
Prospect invited to a tour calendar eventprospectno production caller passes attendeesn/aNot reachable today——
Vendor scheduling call; vendor redialvendorinitiate-vendor-call.ts:224; vendor-redial.tsretired stubsNot reachable todayGera 10-19 "Delete it"—
Smart-lock code writelocktour-access-provider.tsonly the manual provider existsNot reachable today——
Handyman texts set to 'live' bypasses the holdvendorhandyman-text-review.ts:46not set at any companyNot reachable todayGera 10-15fixed
SES envelope adapter reply; robot test call; dead-letter resendvariousses-email.ts:381; robot-call.ts:178; email client resendDeadLetteredEmailno production caller / our own lines / staff onlyNot reachable today——
Renewal and new-lease packet order by ClaraPMSrenewal-paperwork.ts:122; new-lease-paperwork.ts:136default: the team preparesNot reachable todayADR-0144 §5: paper writes not heldfixed
Held today (15)
Clara's reply on a maintenance or renewal topic, or to any prospect (text and 3 email lanes)resident, prospectprocess-envelope.ts:576-602; inbound-processor handler.ts:736; process-pms-email.ts:281reply hold + door backstopHeld——
First email to a new lead; first-reply textprospectclara-guest-card-first-email-send.ts:744/961; first-reply-text.ts:230-319isLeasingSupervisedAt + backstopHeld——
Follow-up cadence and re-engagement (texts, emails, post-call)prospectprospect-outreach.ts:1232-1604; prospect-reengagement.ts:906-1175per-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 textprospecttour.ts:930-1722; tour-confirm-nudge.ts:542-815; weekend-tour-checkin.ts:348; send-tour-outcome-sms.ts:716-1636review helpers + backstopsHeld——
Self-guided lockbox code and leave checkprospectself-guided-lockbox-code-delivery.ts:260-396; self-guided-leave.ts:342-486rerun review + backstopsHeld——
Application link Clara writes; post-tour chaseprospectsend-application-link.ts:1047-1616; post-tour-chase.ts:1016-1285review helpers + backstopsHeld——
Clara's reply after a staff answerresident, prospectteam-answer-supervised-hold.ts:74-109; reply-release.ts:1537reply hold (general topic to a resident declares 'none')Held——
Maintenance resident texts: work order created, rating ask, vendor relaysresidentnotify-tenant-of-pms-creation.ts:245; rating.ts:830; handle-vendor-completion.ts:1405-2671maintenance review (urgent excepted)Held——
Handyman page, relays, stand-down; turnover crew text; turnover vendor emailvendortech-sms-dispatch.ts:1297-1436; in-house-dispatch.ts:586; send-turnover-vendor-dispatch.ts:612handyman review / maintenance reviewHeld——
Renewal offer, reminders, letter, email, recap, lease-ending warning, tool textsresidentactivities/renewal.ts:1026-2335; recap-sms.ts:254; mtm-warning-send.ts:59renewal review + backstopsHeld——
Collections past-due reminder; weekly owner reportresident, ownerapproved-dun-send-ops.ts:493; collections-live-send.ts:72; run-digest.ts:663review queue; Live-only sendHeld——
Tenant text sent through AppFolio's texting inboxresidentappfolio-inbox-text.ts:212; pms-text-door-drain.ts:120same text backstopHeld——
Clara claims the guest card + one note per sent messagePMSguest-card-write-back-outbox.ts:50; guest-card-message-note.ts:108runs only after a sent or approved messageHeld——
Renewal tool commits in a text turnPMStools/renewal/handlers.ts:1037-1950 holdCommitUnderSuperviseddeferred with the reply (#21625)Held——
Move-out charges, move-out draft, turnover work orders, unit re-list, new leasePMSturnover.ts:819-1893; inspection-complete-move-out-draft-hook.ts:273; execute-lease-draft.ts:44HeldPmsWrite or Live-only (ADR-0143)Held——

Not verified

PropFlow Docs