Supervised mode: leak audit

Proposed — pending review · read-only code audit of main at ce62f94a74 · 2026-10-16 · Opus 5.5

Contents

  1. One screen
  2. Situs incident, Oct 27: the first real leak
  3. Final live rerun, Oct 28 evening
  4. Every real staff reply, replayed
  5. How the gate works: the three send doors
  6. Leasing: every path
  7. Maintenance: every path
  8. Renewals and Collections: every path
  9. Tests: what goes red, and what locks a leak in
  10. What to fix, as small PRs (none opened)
  11. Five yes/hold lines for Fede
  12. Corrections to earlier pages
  13. Method, limits, why a new page

One screen

Verdict: Off holds almost everywhere, and Supervised holds every message Clara writes, but Supervised holds no AppFolio write, no call Clara starts, and no Clara text sent under the "staff reply" label.

Fede's rulings, Oct 16 (16:00–17:00Z)

ModulePathsGatedOther switch onlyUngated🔴
Leasing311312613
Maintenance2593136
Renewals219847

The 🔴 leaks, biggest first (counted by kind, not by path):

  1. Every AppFolio write goes through in Supervised: guest cards, showings, work orders, vendor assignment, renewal offers.
  2. AppFolio itself emails the customer in Supervised: the application, and likely the renewal e-sign email.
  3. A staff answer triggers a full Clara reply that goes out unreviewed.
  4. Calls Clara starts: the callback dials even at Off; renewal calls dial in Supervised when calls are set to Clara.
  5. Clara's reply to a vendor can go out unreviewed.
  6. A notice to vacate publishes the listing in Supervised.
  7. The bounced-email text skips Supervised on purpose.
  8. "High" work orders skip Supervised on a session's ruling, not Fede's or Gera's.
  9. Rare fail-open: a failed company lookup sends the reply live.
Situs incident, Oct 27: the first real leak

Situs incident, Oct 27

What happened: at a company set to Supervised for leasing and maintenance, an approved applicant with a housing voucher emailed her RFTA packet to leasing@. Clara could not read the PDF, asked the Situs team by email, and three minutes later a staff reply to that email was reworded and texted to the applicant. Clara's own reply to the applicant was held correctly. Nothing was approved by a person.

Timeline (Oct 27, Mountain time)

TimeWhatGateReached a real person?
11:18Situs staff text the applicant from AppFolio: "Please email it to leasing@thesitusgroup.com"—staff's own text
11:24Applicant emails the RFTA packet (PDF, 1.4 MB). The PDF reader was unavailable in that runtime, so Clara saw only the file name.—inbound
11:25Clara calls forward_to_property_manager: matter opened, decision-request email to leasing@thesitusgroup.com (bcc hello@)sent by design: staff notices are never held (Fede, Oct 14 and 16; ADR-0143)🟡 yes, the Situs team
11:25Clara's reply to the applicantheld (eschold_95e18598, card in #situs-group)no ✅
11:28Lee (Situs) replies to the email: "I just responded to Nicole, thank you for following up."—inbound
11:28Relay rewrites Lee's words as Clara and texts the applicant: "I've already reviewed the RFTA packet and responded to Nicole directly. Thanks for checking in on it!" (Twilio SM98792a2…, delivered, from the Situs Clara line)no gate: sent under the staff_reply label, which skips the Supervised check🔴 yes, the applicant
11:28"Got it — I've let Nicole M. know" emailed back to Leestaff notice, exempt🟡 the Situs team
11:33Gera writes "wheres the team notice link" in the Slack card thread; the bot treats it as draft feedback and rewrites the held draft to "I'll check with the team and send over the team notice link"still heldno, but the held draft is now wrong

Four defects, one incident

#DefectWhy it happenedKnown before?Fix shape
1Staff answer relayed to the applicant under Supervised. The one real leak.The relay (reply-release.ts, relayAnswerToPerson) sends with label staff_reply, which the text and email doors treat like "none": no module check, no Supervised check. The label was justified when "the words are the staff member's"; since Oct 16 the body is always model-rewritten. The team-note relay got a Supervised wrapper on Oct 16; the decision-request relay did not.Yes. This page, decision 4 and fix 6, Oct 16. Not opened as a PR.Bug fix, no switch: the relay to a person goes through the same reply hold as Clara's own reply. One PR.
2Wrong message relayed at all. Lee said he had handled it himself; Clara told the applicant "I've already reviewed the RFTA packet", which nobody said.The reply classifier only recognises "I called/texted/emailed them"; "I responded to Nicole" fell through to "decision". The rewrite prompt has no "this is not for the person" exit, and the fabrication guard only checks numbers.Partly (decision 4: Clara's rewording).Bug fix: a staff reply that says the person was handled closes the matter and sends nothing; the rewrite refuses when the answer is addressed to Clara.
3Team-ask email reached the Situs team while Supervised.By rule: "staff notes are never held" (Fede, Oct 14). The decision request is declared module "none", audience staff; only the company on/off switch applies, and Situs is on.By design.A ruling, not a bug. Fede, Oct 27: "I think I'm gonna fold in team notifications into supervised." See the yes/hold line below.
3bA human question in the Slack card thread rewrote the held draft.Any thread reply is read as feedback on the draft.No.Follow-up: a thread reply that is a question to the bot, or from an engineer, is answered, not applied. Now: the held draft reads "I'll check with the team and send over the team notice link"; reject that card so nobody approves it.
4Trigger: the PDF was never read. The inbound email says "NOT READ: the PDF reader is unavailable in this runtime", so Clara saw a file name and escalated.The mailbox lane normally extracts PDF text (with OCR). Why the reader was unavailable in that run is unverified.No.Inbound-path bug, own PR, cause to be found first.

Is this the first leak at Situs?

Where Supervised is consolidated today

Clara Refactor's askTheTeam seam now fronts all 23 "ask the team" callers, but it is a pass-through: it checks no mode. The decision-request email and the staff-answer relay both still send directly. Folding team notifications into Supervised means one check inside that seam (the matter's module stage) plus the relay fix above; no second gate.

Yes / hold for Fede

  1. Team-asks to the customer's staff are held under Supervised, like Clara's replies? Yes, Fede 2026-10-27: "we need to start holding staff replies"; the Situs case "is an example of we should have not replied because we can't help." Replaces the Oct 14 rule. Build: PR A (relay under the hold) and PR B ("handled" replies relay nothing) by session 007; PR C (team-asks through the team-notice hold door) by the Clara Refactor session. Still open: delete the separate Team-notices switch now that notices follow the module? Recommend yes.
  2. Fixes ship as bug fixes, no switch, Willows proof then live everywhere. In build, Oct 27.
  3. No recovery message to the applicant. Lee already answered her directly and the company is Supervised. Recommend leave it.

Not verified: whether the teaching step saved Lee's line as property knowledge; why the PDF reader was unavailable; Gera's side's intent for the "team notice link" (bridge question q-situs-teamask-2026-10-27, open).

Records: matter_41896f45, conversation dbe8f8e0, hold eschold_95e18598, Twilio SM98792a226973b9b26373c11a62415ce5, Slack #situs-group 1793121922.037549. Code read on main 7765053b4d.

Fix shipped, Oct 28

Live everywhere, no switch. Every staff reply is now read once into one of five outcomes: answer, handled, question, note or unclear. Only the facts in an answer reach the person, in a reply Clara writes, and that reply waits in the Supervised hold like her own. The reword-and-relay step is deleted. Correction: that reword step dates from Oct 4, not Oct 16.

Staff reply (Willows, Oct 28)BeforeAfterMatter
"I just responded to her"waiting on staffclosed, nothing sent, Clara's held reply withdrawnmatter_e3763598
A real answer (TV mount rules)waiting on staffClara's reply from the facts, held for approvalmatter_f2bf5d68
"hmm"waiting on staffnothing sent, one line back to staff, still openmatter_aff66f1f
Leasing, two-reply: "hmm", then a real answer to Clara's one-line askwaiting on staffFailed. The ask to staff was held, then sent without the thread link, so the real answer reached no matter. No draft, nothing sent, still open.matter_623b413d
Leasing, two-part question: staff answer part one ("fee waived, still checking the deposit"), then part twowaiting on staffFailed. Draft 1 held from the answered part and says the rest is open, but the matter closed as resolved, so part two has nowhere to go. Stopped before reply 2. Nothing sent. (Replaces a first run where Clara had asked staff only one question.)matter_1f13859d
Leasing, the same answer sent twicewaiting on staffPassed. One draft from the facts, still pending after the duplicate, no second hold, nothing sent.matter_0734d88b
Leasing, text arriving through the AppFolio inbox (Situs shape)n/aNot run. The text was mirrored in and woke Clara, but the only door test thread has hit the 40-reply safety ceiling, so she stopped. Nothing sent anywhere. Needs a fresh door test lead.none

Shipped as: a test that sends leak-shaped staff replies through the real release path; one reader for staff replies that fails closed; the release path rewired to it, with the old relay deleted.

On 208 real and planted staff replies the new reader is right 96% of the time, against 69% for the old phrase list, with zero false relays (the old one had 41). The leak test: 0 of 21 reach the person.

Live matrix in the incident's shape (Fairhaven, Oct 28)

12 of 12 live cells sent nothing to the person. The setup matched the incident: an applicant texts, Clara asks staff by email, staff reply by email, and the company is fully Supervised. Zero texts went to any test applicant, zero left the Fairhaven line all day, there were zero rows in the AppFolio text outbox, and nothing was written to knowledge. All 31 new holds are accounted for. One defect: a late answer on a matter staff had already closed reopened it, and Clara's held draft added "approved", which staff never said. Caveat: the test applicants came in on Clara's own line, so the AppFolio inbox door never applied to their threads; Fairhaven has no AppFolio account to reproduce that entry. Rerun that evening with more fixes live: see Final live rerun.

Staff replyExpectedWhat happened
"I just responded to <name>" / "I called her." / the same from a second stafferclosed, nothing sentclosed as handled, Clara's draft withdrawn, ack "nothing was sent"3 of 3
Facts: packet complete + decision Friday; $350 due Nov 3held draft with exactly thoseheld draft with exactly those, nothing added2 of 2
Own words + signature + forwarded owner noteonly own words usedstored answer stops at the forward; nothing from the note anywherepass
A question back; "see attached" + PDF; a policy line; "I told her cosigner it's fine"nothing to the personall read as "unclear": matter stays open, a one-line ask to staff held. The third-party rule was never reached4 of 4 no leak
Out-of-officenothing sent, stays openauto-reply detected, matter open, no knowledgepass
Late answer after the matter closednothing sent, no new matternothing sent, no new matter, but the matter reopened and the held draft added "approved"defect
Reply in the Slack thread under a held cardthrough the reader?not run; in code it goes to Smith, never to the readergap

Every setting was restored and every hold rejected afterwards. Applicants used Twilio test numbers with made-up names. Full table with ids in the fix's description.

Final live rerun, Oct 28 evening

No test reached a person: 0 sends across 20 live cells, with every fix in production. 15 behaved as expected, 2 have a safe defect, 2 sent nothing but behaved differently than planned, and 1 is still a gap. All four are fixed and in production; three are proven live, one fails safe and awaits a test reviewer identity (Oct 29).

Production ran commit 87f7630e0 with ten fixes merged and live since 21:15Z: the fully-Supervised hold (#22881), the one staff-reply reader with the old relay deleted (#22979), and #23191, #23188, #23194, #23198, #23199, #23192, #23214, #23211. The only later commit (99bd1713) touches evals and CI only. Test companies: Fairhaven (fully Supervised) and Willows (Leasing set to Supervised). Applicants used Twilio test numbers with made-up names; staff replied from a corporate test mailbox.

Morning → now

CellMorningNow
Late reply after staff closed the matter✗ matter reopened, draft added "approved"✓ stays closed, no draft
Staff say "hmm", then answer Clara's one-line ask✗ answer reached no matter✓ ask kept the thread, held draft
Two-part answer, sent in two replies✗ closed after part one✓ stays open, one combined draft
Same answer sent twice✓ one draft✓ one draft
Staff correct an earlier answernot run✗ amount right, due date dropped
Text arriving through the AppFolio inboxpartial: Clara's line only✓ real AppFolio-inbox text, approved live, went to the AppFolio door, 0 Twilio
Staff ask a question backheld one-line askanswered staff directly; nothing to the applicant
Free-text reply under a held text card in Slackgap, code read onlystill a gap, now run live

Every cell

#Staff replyExpectedActualMatter / draft
1"I just responded to Brisa"closed, nothing sentclosed, draft withdrawn, "nothing was sent" note to staffmatter_fb2129ba✓
2"I called him."same as 1same as 1matter_701099e0✓
3Packet complete, decision Fridayheld draft, staff's facts onlyold draft withdrawn; held draft with exactly thosematter_34ae29b9 / 21436d2b✓
4"which unit is she asking about?"held one-line ask, matter waitsmatter waits; Clara answered staff directly; staff's line saved in the applicant's thread as if the applicant wrote itmatter_1024d702safe, differs
5Own words + signature + forwarded noteown words onlydraft from own words; forwarded note unusedmatter_c6cc9b9b / 6011cc37✓
6Out-of-officenothing sent, waitsmatter waits, no new holdmatter_099b7642✓
7"see attached" + PDFunclear: held askone-line ask heldmatter_a8ac086b✓
8"We never discuss other applicants."unclear: held askone-line ask heldmatter_a985059b✓
9Case 1 from a second staffersame as 1same as 1, note to that staffermatter_ff8f5b76✓
10Late answer on case 1 after closestays closed, no draftstays closed, no draft; the "already closed" note was held, not sentmatter_fb2129ba✓
11"$350, due by November 3"exactly thoseexactly those (hold label wrongly says "renewal")matter_d834fcba / 2583d896✓
12"I told her cosigner it's fine"draft never names the third partyread as unclear, one-line ask held; the third-party rule was not reachedmatter_b6a04577safe, rule not reached
P1Half answer: "pay stubs came in Monday"stays open, staff's facts onlystays open; draft adds "background check hasn't come back yet", which staff never saidmatter_c7e7e0ae / ed996a9f✗ safe
P2The rest + "stubs Tuesday not Monday"one combined draft, newer winsone draft: cleared this morning, stubs Tuesdaye3c610ac✓
W-A"hmm", then the answerask keeps the thread; held draftask threaded to the original; held draft from the answermatter_2a7b5530 / ca4cb05d✓
W-B"fee waived", then the deposit answeropen + draft, then one combined draftexactly thatmatter_d349170c / 6c2ab920✓
W-C"$350, due Friday", then "I meant $400"newer wins, rest kept$400 right; draft says it doesn't have the due datematter_707570e9 / 532da391✗ safe
W-DSame answer twiceno second draftone draftmatter_f75d6404 / e7b092eb✓
14Applicant texts the AppFolio line; staff answerheld draft; approved reply leaves through AppFolio, not Twilioheld draft with staff's facts; approved live at 23:05Z; went to the AppFolio outbox, which held it (test number sits on several AppFolio cards); 3 tries, never deliveredmatter_4698f8b9; c0b0d25a✓
13Free text under a held text card in Slackgoes through the readernot acted on; never reaches the readere7b092ebgap

Draft ids are eschold_ + the 8 characters shown.

Controls

CheckResult
Texts to the 17 test applicant numbers, since 22:15Z0
Texts from the Fairhaven / Willows Clara lines0 / 0
AppFolio text outbox, Fairhaven0 rows
AppFolio text outbox, Willows: matrix window0 new rows
AppFolio text outbox, Willows: door approval+2 rows (the held reply and an alert row); 0 sent
Emails22, all to the corporate test mailbox; 0 to anyone else
Knowledge, full-content diff0 changed
Holds created / left pending51 (Fairhaven 33, Willows 18) / 0
Settings at both companiesmatch the before snapshots; 62 writes ledgered

What this shows: under these fixes, no staff reply in these 20 shapes reached a person. It does not show a clean AppFolio delivery: that needs a test number on exactly one AppFolio card.

Defects and open fixes

Follow-up fixes, Oct 29

All four fixes are merged and in production (last deploy Oct 29, 11:08Z). Three are proven live; the fourth fails safe and waits for a test reviewer. 0 texts or emails to any person across 20 cells on Oct 28 and 6 follow-up cells on Oct 29.

What was wrongFixLive result, Oct 29
A correction dropped the facts it didn't change (W-C)#23330: the correction merges into the earlier answer; newer fact wins, the rest stays✓ held draft "$400, due this Friday"; one pending hold (matter_01c4864d / 4af57228)
A half-answer draft added a status staff never gave (P1)#23347: the facts check now covers status claims✓ draft says "no update yet, I'll check with the team" instead; matter stays open (de3361a6)
Staff's question ran on the applicant's thread (c4)#23348: a staff question never runs on the applicant's thread✓ applicant thread untouched; the on-file answer to staff held on the team-ask email thread (4e956067)
Free text under a held text card in Slack never reached the reader (case 13)#23333: it now goes through the readerNot exercised; fails safe. The test Slack user isn't a workspace member, so the reviewer check could not match it; the card said "something went wrong reading this reply" and nothing was done (252be5fc, 959f9547, fa418c2f rejected). Not a defect and not a missing Slack permission: a cofounder's typed approvals resolve in production (Oct 21).

Controls: 0 texts to the test applicants, 0 from either Clara line, 0 new AppFolio outbox rows at either company, 0 emails outside the test mailbox, 0 knowledge changes.

Still open

Every real staff reply, replayed (Oct 28)

69 real staff replies in production history (Aug 14 to Oct 28): 55 from customer staff (Western Slope 53, Situs 2) and 14 from PropFlow's own team on real-company matters. JP&Co staff have never replied to Clara. 15 of the 69 went through the code the fix changed. On those 15, old code wrong: 3, new code wrong: 4, regressions: 0. All 4 new misses are "unclear" on replies from our own team: nothing goes to the person either way, and staff get one "I couldn't tell" line instead of an answer to their question.

Where the 69 replies came inRepliesRuns the new reader?What changed
Email reply to Clara's question (customer staff)9yesold wrong 3, new wrong 0
Email reply to Clara's question (PropFlow team, at Camellia)6yesold wrong 0, new wrong 4 (all "unclear")
Closing notes PropFlow typed onto a matter4yes, but never read by the old codeshown for information
Texts to the staff desk (tour check-ins)38nonothing
Replies to callback, alert and follow-up emails8nonothing
Staff texting the person in AppFolio3nonothing
Slack thread under a held draft1nonothing

Also in the corpus and left out: 128 replies at the two test companies, 6 desk asks staff never answered, 1 out-of-office. Two of the PropFlow-team emails are the same words sent twice, two minutes apart.

(a) Old code got it wrong, new code gets it right

Staff words (scrubbed)OldNewMatter
"I just responded to [person], thank you for following up." (plus signature)texted the applicant "I've already reviewed the RFTA packet and responded to [person] directly"handled: closed, nothing sentmatter_41896f45 (Situs, Oct 27)
"That sounds perfect, Clara, thank you! - [staff member]"sent a second update the person already had (a teammate had sent one by hand)note, nothing sentmatter_f612925a
"…You can let [person] know that Yes we manage all of the units at [street address]…" (long answer with floor plans)chatted back with staff; the person heard nothing until a second replyanswer, 8 fact rowsmatter_f612925a

PropFlow closing notes, for information (the old code never read these): "Closed administratively… no relay to the resident" → handled; "PM judgment… prospect poking the bot" → note; "not interested… no outbound" → note (matter_73b3db5d, matter_a225d287, matter_da2bfab9). A fourth, the voucher decision on matter_8fde2f40, reads as an answer with two clean fact rows and no staff name.

(b) Both right

Staff words (scrubbed)OldNewMatter
"It comes with 1 covered and 1 uncovered."relayedanswer, 1 factmatter_95ceb2ca
"Yes it is still available and it's ready now."relayedanswer, 2 factsmatter_185725fe
"Yes each adult needs to fill out the application even the cosigner."relayedanswer, 1 fact, still open (no link given)matter_bca5a49a
"…please thank him for the concern… tell him we are all set with the contractor"relayedanswer, 2 factsmatter_7b8c5a52
"…you can identify the property internally here as Northridge… Please let [person] know that we can absolutely add a cosigner…"relayedanswer, 3 facts; the internal line is left outmatter_9003aa4a
"I called them on 9/27 and will find out when someone will be down there…"handlednote, following upmatter_8d9a0a6f
"Do you need this info before you schedule a tour?" / "What are all of our available units?"questionquestionmatter_d9e3a934, matter_0a6ab881

Of the old code's 7 relays, 2 told the person something staff never said ("I've already reviewed the RFTA packet"; a fresh "confirmed"). The other add-ons were Clara's own tour offers, which the new draft step can add too.

(c) Regressions: new code changes a case the old code got right

None. No reply that the old code handled correctly is now sent to the person wrongly or dropped.

(d) New code is unsure where the old code was right

Staff words (PropFlow team, at Camellia)OldNewMatter
"What about it is about fair housing?"questionunclear: one line back to staffmatter_d9e3a934
"What are alll the units that are available?"questionunclearmatter_2a400e55
"We have several available and the team will show him the best option for his preferences during the tour" (sent twice)answerunclear: the reply says "him" and the reader could not tie it to this prospectmatter_d9e3a934

The cost: on 4 of 15 replies (3 different texts) staff would get "I couldn't tell what to do with your last reply" instead of an answer to their question, or the prospect would wait for a second reply. Nothing would reach anyone wrongly.

Paths the fix did not touch (50 replies)

None of these ran the old phrase list or the new reader, so old and new behave the same: 47 sent nothing to the person, and in 3 the staff member texted the person directly. Run through the reader anyway, it reads 46 of 50 correctly. The 4 misses, which would matter only if these paths were wired:

Staff words (scrubbed)PathReader saysShould be
"Hi [person], we are still doing the turn. It should be soon though."staff texted the person in AppFolioanswer, with "It should be done soon" (not quite what staff said)handled
"Good morning [person]… Just checking in to see if you were interested in either home…"staff texted the person in AppFoliounclearhandled
"…are you guys not going to move forward with [street address]? I am going to have to list it…"staff texted the person in AppFoliounclearhandled
"Good, please send an application"staff desk textunclearanswer

Gap: on a staff text mirrored from AppFolio, the reader has no idea that staff already texted the person. Today that only settles the matter, so nothing leaks.

The nine Situs texts (Oct 19 to 21) and the Oct 27 message

None of the nine is a staff reply. All are Clara's own texts from the Situs line, so this fix does not touch them:

TextsWhatNow
SM5abe8e85, SMf825831c, SMa91d5b6b, SM57941f86, SMef32062e"Work order … created"Held for approval since Oct 28 (one seen, eschold_mr_da2edba4, approved); Fede asked to stop these texts
SM26a71ff2Clara's reply about the boiler closetNot checked in this replay (a Clara reply; the company-wide Supervised rule is meant to hold it)
SMdd2e6cedRating request after a closed ticketNot checked in this replay
SMdb8a9e08 (copy SM9dd12f9f)Spanish tour confirmationNot checked in this replay
SM98792a22 (Oct 27)The staff-words relayReplayed above: handled, nothing sent

How this was run

How the gate works: the three send doorsWhat each door checks

How the gate works

Every text, email and call goes through one of three "doors". Each door checks Off. Each has a Supervised backstop that refuses an unapproved send, but only when the sender names its module and plays by the door's rules. AppFolio writes and calendar writes have no door.

DoorOffSupervised backstopCompany liveSkips everything when
Textyesyes, by default for residents and prospects. Vendor and staff texts only if the sender opts in (none do)nosender says "none", emergency, team alert, staff reply or sign-in code
Emailyesonly if the sender opts in (almost all do)yessame list, or the email is marked staff-only
Voiceyesonly when calls go to the team (the default)nosame list
AppFolio writesyes, at each writerno (turnover and new-lease writes excepted)assign and first email onlyno door
Calendartools onlynonono door; no customer is ever an attendee, so nobody outside is emailed

Ordinary Clara replies name no module at the door; they are held one step earlier. A bug there has no backstop.

File references

Text door src/lib/integrations/twilio/client.ts:1040-1170 · exempt list src/lib/domain/modules/always-on.ts:49-63 · email door src/lib/integrations/email/company-live-gate.ts:300-470 (opt-in at :443) · voice door src/lib/integrations/voice/initiate-outbound-call.ts:438-520 · reply hold src/lib/domain/modules/supervised-reply-hold.ts · leasing hold src/lib/domain/modules/leasing-supervised.ts:25-33 · AppFolio writer Off check src/lib/domain/pms/registry.ts:328-358 · drain Off check guest-card-outbox-drain.ts:3319, Supervised counts as on resolve-module-entitlement.ts:289-293 · calendar calendar/engine/apply-calendar-event.ts:740 · reply module "none" agents/clara/lib/messaging/process-envelope.ts:941, reply-module.ts:63-90

Leasing: every path31 paths · 13 🔴

Leasing

Every prospect message Clara starts on her own is held. The leaks are AppFolio writes, AppFolio's own application email, and three side doors.

What happensToAt OffAt SupervisedRating
First email to a new guest cardprospectrefusedheld✅
Follow-ups, re-engagement, first-reply textprospectrefusedheld✅
Tour reminders, nudges, check-ins, post-tour, self-guided codeprospectrefusedheld✅
Application link Clara writesprospectrefusedheld✅
Clara's text and email replies (3 email lanes)prospectsends (open ruling: dark still answers)held, no door backstop⚪ / ✅
Staff-desk tour confirmation; new-lease approved messageprospectrefusedsends (a person started it)⚪
Assign the card to ClaraAppFoliorefusedwritten; stopped today only by the building switch and no Clara seat🔴 (fix in progress)
AppFolio sends its own applicationAppFolio → prospectrefusedAppFolio emails the prospect, if the company uses AppFolio applications🔴
Tour booked → card create; showing create/move/cancelAppFoliorefusedwritten while the reply waits🔴
Listing-site email lead → card create and noteAppFoliorefusedwritten even though the first email is held🔴
"Not interested" → card marked inactiveAppFoliorefusedwritten🔴
Post-call card, hand-off note, self-guided noteAppFoliorefusedwritten🔴 low
Listing publish on notice to vacateAppFolio → public sitesrefusedpublished if the building's publish switch is on🔴
Staff answer relayed (Clara rewords it)prospectsendssends⚪ ruling, but its premise ("the staff member's words") no longer holds
Staff answer to a team note → a full Clara replyprospectsendssends Clara's own text unreviewed🔴
AI callback with the staff answerprospect or residentdialsdials🔴 at Off · ⚪ pending at Supervised
Bounced email → text fallbackprospectrefusedsends; may pick a newer held draft🔴 low
Line's company lookup fails; hold check throwsprospectsendssends live🔴 rare (throw: suspicion)
Calendar event for a tourhost's own calendarrefusedwritten, no attendees⚪ Gera 10-01
Staff notices (tour emails, lost slot, review cards)staffsendssends⚪ Fede 10-16
Staff text desk: card, note, showingAppFoliorefusedwritten (staff started it)⚪? needs Fede
File references, leasing

First email clara-guest-card-first-email-send.ts:759, :1124 · cadence leasing-prospect-text-review.ts:85/160, prospect-outreach.ts:1139, :1559, prospect-reengagement.ts:1165 · tours rerun-text-review.ts:94, tour-confirm-nudge.ts:770, post-tour-chase.ts:1229, tour-email-actions.ts:256 · app link send-application-link.ts:782-839, :1239 · replies process-envelope.ts:530, :941, process-inbound-email.ts:2036/2065, lambda/inbound-processor/handler.ts:735, :1008-1028, process-pms-email.ts:287-290, :415-470 · staff-desk staff-booking-confirm-prospect.ts:242/315/335 · assign clara-guest-card-assign-consumer.ts:162/348/355, guest-card-note-intent.ts:181-201, seat guest-card-outbox-drain.ts:2321-2340 · AppFolio application guest-card-send-application-intent.ts:118, send-application-on-ask.ts:35-70, drain :3324 · card create guest-card-create-intent.ts:62, apply-tour-intent.ts:1272/1826 · showing showing-sync-stream-consumer.ts:142/157, drain :3329 · email lead email-lead-engine-reply.ts:285 → guest-card-from-email-lead.ts:397 · inactive prospect-summary.ts:1063 · post-call postcall-guest-card-single-writer.ts:315 · notes open-matter.ts:1328, self-guided-callback-card-note.ts:34 · publish auto-publish-listing-hook.ts:170/186 · relay reply-release.ts:2684/2761; team-note turn reply-release.ts:1512, team-notify/clara-turn.ts:219; exemption text always-on.ts:26-33 · callback voice/escalation-callback-call.ts:189-201 ← voice-callback.ts:151 · bounce agents/clara/lib/email/ndr-bounce.ts:485-521, :586 · fail-open process-envelope.ts:415-428, :455; conversation-manager.ts:16246-16254 · calendar apply-tour-intent.ts:1361-1599, event-types.ts:321-333 · inbound calls in Supervised: prompt-only guard leasing-module-route.ts:51, tools refuse Off only leasing-entitlement.ts:82

Maintenance: every path25 paths · 6 🔴

Maintenance

Every message to a resident is held, emails included. The work order itself is not: Clara's AppFolio writes run in Supervised. That follows Gera's 10-01 ruling ("only the message waits"); Fede's 10-16 ruling on the assign makes it a leak.

What happensToAt OffAt SupervisedRating
Clara's text and email replies; status updates; rating askresidentrefusedheld✅
Handyman page, relays, stand-downvendorrefusedheld by the sender only (the door never checks vendors)✅
Turnover vendor email; vendor scheduling callvendorrefusedheld / person approves each call✅
Turnover writes, move-out charges, inspection draftAppFolioskipheld✅
Work order create, note, edit, complete, cancelAppFoliorefused (a failed settings read still writes)written at once🔴
Handyman assigned as the AppFolio vendorAppFoliorefusedassigned while the page waits; kept if rejected🔴
Clara's reply to a vendor who texted invendorrefusedsent unreviewed when the topic reads "general"🔴
Notice to vacate → listing publishedAppFolio → publicpublished (Maintenance Off not read)published🔴 (per-building switch)
Turnover auto-releaseAppFoliorefusedwritten🔴 (company switch)
Emailed notice → move-out draftAppFoliorefusedwritten (its sibling hook holds)🔴 (env switch)
Emergency and "high" priority textsresident, vendorrefusedsent⚪ emergency (Gera); "high" added by a session ruling
Vendor work-order, quote, chase emailstest inbox todayrefusedsent🟡 latent: one constant away from vendors, no backstop
Admin dev-tools AppFolio writesAppFolionot refusedwritten🟡 admin only
PM pages, outcome and PO noticesstaffPM page still firessent🟡 at Off · ⚪ Fede 10-16
Calendar (inspection, work order, vendor visit)own calendarclose path writeswritten, no attendees🟡
File references, maintenance

Writer src/lib/domain/pms/registry.ts:328-358, work-order-writer.ts:169-424; tools handle-create-work-order.ts:1032, handle-add-note-to-work-order.ts:267, handle-edit-work-order.ts:74, handle-vendor-completion.ts:541, voice-postcall-intake.ts:874-898 · assign tech-sms-dispatch.ts:880, :1308-1316, maintenance.ts:233 → :778 · vendor reply process-envelope.ts:485, :816, :926-998 · publish auto-publish-listing-hook.ts:170 · auto-release release-held-turnover-work-orders.ts:66, gather.ts:202 · NTV draft email-ntv-move-out-draft-hook.ts:126/160 vs sibling inspection-complete-move-out-draft-hook.ts:268 · carve-out approval-carve-out.ts:18; high-priority release conversation-manager.ts:16212-16225 · vendor emails send-vendor-dispatch.ts:441-453, send-vendor-quote.ts:211-225, vendor-chase.ts:447, redirect vendor-poc-recipients.ts:15, pass-through email/client.ts:1806 · vendor texts tech-sms-dispatch.ts:1349/1378/1397/1437, review rerun-text-review.ts:94 · turnover email send-turnover-vendor-dispatch.ts:612, :689, :907 · vendor call dial-approved-session.ts:207, initiate-vendor-call.ts:379-395 · replies process-inbound-email.ts:2021-2066, process-pms-email.ts:281/420 · PM page page-property-manager.ts:482, :731

Renewals and Collections: every path21 + 9 paths · 7 + 1 🔴

Renewals

Every text, letter and email to a resident is held. The AppFolio renewal offer is not. Where the company lets Clara send paperwork, AppFolio prepares the offer (and likely emails the resident to sign) while Clara's text waits for review.

What happensToAt OffAt SupervisedRating
Offer text, letter, reminders, lease-ending warning, recapresidentrefusedheld✅
No-phone offer emailresidentrefusedheld✅
Clara's renewal repliesresidentrefusedheld (a "general" reply sends, per Gera 10-04)✅ / ⚪
Auto-start, workflow and retry → prepare the AppFolio offerAppFolio → residentrefusedwritten before review where paperwork is Clara's🔴
Switching a company to Supervised starts a renewal scantriggers the row above—fleet-wide, not a dry run🔴
Resident asks for a different term → cancel and re-prepare the offerAppFolio → residentrefusedwritten; the confirmation text is held after the fact🔴
Holdover: end lease, set month-to-month rent, cancel offerAppFolio billingrefusedwritten (needs three switches)🔴
Day-0 renewal callresidentrefusedrefused by default; dials when calls are set to Clara🔴
Staff click "Send" or "Approve"AppFoliorefusedwritten (the click is the approval)⚪
Office notices, escalation, PM reminders, monthly liststaffmixedsent⚪ Fede 10-16
Key pickup bookingstaff calendarrefusedbooked, no attendees⚪

Collections (short pass)

Reminders wait for review in Supervised and only go out on Live. One narrow 🔴: a reminder approved by the system overnight still sends in the morning even if the company moved from Live to Supervised in between. The eviction email to escalationOwnerEmail is 🟡, or 🔴 if a customer typed a real owner's address there (not read). Collections never writes to AppFolio.

Company not live yet: no renewal or collections path checks it, and neither do the text or voice doors. A company that is not live but has Renewals on would still get renewal calls, texts and AppFolio writes. Only the renewal email is held.

File references, renewals and collections

Prepare chokepoint src/lib/integrations/sqs/agent-jobs.ts:255-287 ("Only a stored OFF refuses"), lambda lambda/agent-runtime/src/handlers/renewal.ts:90-105 · auto-start renewal-auto-start.ts:2333 · workflow renewal-workflow.ts:3326-3360 → activities/renewal.ts:2900 · retry domain/renewals/retry-failed-prepare.ts:506 · flip scan admin/modules/route.ts:247, write-building-module.ts:286, temporal/client.ts:1249-1262 · term change agents/clara/lib/agent/tools/renewal/handlers.ts:1786, :1850, :1891-1900, :1979, lambda renewal-change-term.ts:132/142 · holdover renewal-holdover.ts:149/165/181/194 · call renewal-workflow.ts:3778 → initiate-renewal-call.ts:88-104, call state supervised-call-state.ts:46-55 · texts send-renewal-letter-mms.ts:293/345, recap-sms.ts:132, mtm-warning-send.ts:204 · email send-renewal-email.ts:313/324 · escalate handlers.ts:74 · PM pages renewal-notify.ts:252-283, :331, :465 · collections sweep approved-dun-send.ts:684-720 via cron/escalation-hold-sweep/route.ts:78; live send collections-live-send.ts:66-73; owner email owner-escalation.ts:244-258, uncured-sweep.ts:448-455

Tests: what goes red, and what locks a leak inCoverage per module

Tests

ModulePlanted unapproved message at Supervised → redPlanted send or write at Off → redPlanted AppFolio write at Supervised → red
Leasingyes, texts and reply lanes; no Leasing case at the email dooryesnone
Maintenanceyes, texts and emailsyesnone (turnover only)
Renewalsyesyesnone; one test asserts the leak
Callsvoice door only, with calls to the teamyes—

Nine tests assert a leak as correct. Each fix below has to flip one of them, so name it in the PR.

Structural checks. A ratchet counts every "none" sender and goes red on a new one. Nothing checks who an exempt send goes to: a new "team alert" or staff-only email to a prospect passes every check today.

What to fix, as small one-concern PRs (none opened)Each is a fix: no switch, live everywhere on merge

What to fix

Rule for every row: it is a fix, so it ships with no switch, live at every customer on merge. Proof is a Supervised run at the Fairhaven or Willows sandbox showing the write parked and nothing delivered. Rows 2 to 4 wait on decision 1.

#PR (one concern)Test it flips
1Assign step skips in Supervised (in progress today)—
2Guest-card drain parks every Leasing write in Supervised: card, note, status, showing, AppFolio applicationemail-lead-engine-reply:213
3Work-order writer and vendor assign wait in Supervised, released with the approved messagetech-sms-dispatch-supervised:409, assigns-paged-vendor
4Renewal prepare, term change and holdover refuse in Supervised; the switch-flip scan becomes a dry runsupervised-paperwork.permutations:324, renewal-auto-start:2161
5Listing publish and emailed move-out draft check Supervised, like their sibling hook—
6Team-note answer turn goes through the reply hold, not the staff-reply label—
7AI callback declares its thread's module (gets the Off and Supervised checks)—
8Bounce text declares its module; never picks a held draft—
9Vendor replies are held in Supervised whatever the topic; vendor texts opt in at the doorprocess-envelope-maintenance-drafts:399, module-gate-sms-drafts-backstop:142
10Fail closed: a failed line lookup or a hold check that throws holds the reply—
11Collections morning sweep re-reads the mode before sending—
12Fence: every exempt send must go to staff; text and voice senders must opt in to the Supervised checkemail-supervised-backstop:164, outbound-call-supervised:166
Five yes/hold lines for Fede

Decisions

  1. Supervised holds every AppFolio write, not only the assign? Declined for now (feature switches hold writes). Recommend yes. This replaces Gera's 10-01 "actions happen right away". Unblocks PRs 2 to 4.
  2. Supervised covers calls Clara starts (renewal day-0 call, AI callback)? Parked. Recommend yes. A call Clara starts can wait; an incoming call cannot.
  3. "High" priority work orders keep skipping Supervised? Parked. Recommend hold to emergency only. "High" covers no AC or a dead fridge, and was added by a session, not by you or Gera.
  4. The staff-reply label may carry text Clara wrote? Yes, in progress. Recommend no. Today it carries Clara's rewording and, in one path, a full Clara reply.
  5. At Leasing Off, Clara still replies to prospects: the tools refuse, the words do not. Keep, or disclose and stop like Maintenance? No (Sept 13 rule). Recommend stop. The ruling that lets her answer was written for Maintenance and is still open in code.
Corrections to earlier pages

Corrections

Method, limits, why a new page

Method and limits

Why a new page: the audit spans Leasing, Maintenance and Renewals, and no existing page covers them together. The Situs inspection and the Oct 3 evidence page cover Leasing and the build.

Method: four independent Opus passes over main (Leasing, Maintenance, Renewals, and raw send calls across all modules), each walking from the send or write back to its trigger. Counts are paths per module; the 🔴 list counts kinds. The listing publish appears under both Leasing and Maintenance.

Not verified:

PropFlow Docs