Tenant reply leaks: Western Slope incident, 2026-10-23

A former tenant texted the AppFolio inbox about her deposit; Clara replied from her own line. Read-only inspection of every path where a tenant, former tenant, or unknown person can reach Clara, and the fixes.

Inspection done; when-to-reply re-architecture is Phase 3 of clara-lane-order-2026-10 (Fede's lane since Oct 23) · audit of main at 06c73d2e5f · 2026-10-23 (MT morning) · session 001

Rule going forward: Clara speaks only to a positively identified prospect or applicant, or to a tenant at a company whose tenant switch is on. A former tenant is a tenant. Unknown role means silence: no reply, no action, no team note.

What happened

8:57 AM MT: a former Western Slope tenant (Pomona Park, lease ended Aug 31) texted the company's AppFolio texting inbox about her deposit. Our copy of that inbox woke Clara. Clara replied from her own number (not AppFolio), emailed the leasing team a "could not answer" note, and closed the thread. Nobody turned tenant comms on. Kat answered the tenant in AppFolio at 9:31. The tenant is muted on every channel at Western Slope (undo: unmute, same identifier). The mirror fix is in a PR.

Fede's rulings (2026-10-23)

Why Clara answered, blast radius, exposure, every path (open)

Why Clara answered

3AppFolio people on her phone number: her (tenant, past), her old applicant record, and an unrelated prospect
2role labels exist in the mirror: tenant or prospect. No "unknown"
0paths read the lease end date. Her tenancy record still says active

Blast radius

Exposure (prod, read-only, 2026-10-23)

Western SlopeSitus
Tenancy records384562
People whose lease ended before today8118
… still marked active (the incident shape)7215
… already marked former103

Some stale-active rows may be real month-to-month holdovers. A check against AppFolio's own status was not done.

Every path, today's behavior

"Tenant" below means status-based (active or month-to-month). No path reads the lease end date.

PathFormer tenant todayUnknown role todayLeak
Text to Clara's line, maintenance step
inbound-dispatcher.ts:2121, property.ts:1496, 1714-1747
Stale-active main tenant: work order + reply. Co-tenant or former: falls to the default laneClara conversesyes
Text, default lane
inbound-router.ts:783-806, identity/resolve.ts:918-1066
Unknown (the incident shape)Reply, by design: unknown = prospectyes
Mirrored AppFolio texting
pms-mirror/file-message.ts:205-209, 303, 329
Prospect, when anyone else shares the numberReply (no "unknown" label exists)PR in flight
Phone call, caller lookup
handle-identify-caller.ts:283-400, 830-883
Stale-active former co-tenant with name + unit = verified tenant, can file a work orderReply + action + team noteyes
Phone call, caller context
voice/personalization/route.ts:949, 1925, 2866, 3252
Stale-active gets full tenant contextMessage desk takes a noteyes
"How did it go?" rating text
maintenance/rating.ts:773
Texted when their work order closesn/anarrow
Renewal / month-to-month noticesNot verifiedn/amodule off at both
Leasing or company mailbox, questions
process-inbound-message.ts:2344-2365, email-reply-scope.ts:65-105
"Former" counts as prospect: full leasing replyReply as prospect; a failed lookup = prospectyes
Same mailbox, repair email
resident-repair-email.ts:51-74
Stale-active = verified resident: reply + work orderSilent, filedif switch = door
Mail to Clara's own address
process-inbound-email.ts:1257-1401
Skipped unless the building is switched onSameno
Mirrored AppFolio email
guest-card-message.ts:515-568, 958-971
"Former" with any old inquiry: woken as prospectSilentyes
Listing-site / relayed leads
process-inbound-message.ts:2124
Full prospect reply, no gateReplyyes
First email to a new guest card
guest-card.ts:3384
Skips the known-tenant guard; appears to get the emailCard = prospectlikely, not verified
Text asking for a tenant's email
handle-tenant-email-capture.ts:294-353
Stale-active: captured + replyFalls to Claranarrow
"Could not answer" team note
conversation-manager.ts:16295-16346
FiresFires (no role check)yes

Which line the reply uses. By design, any tenancy (past included) and any unknown thread keep the text on Clara's own line; the AppFolio door is leasing-only unless maintenance.pms_inbox_tenant_door is on (absent at both companies). Email has no door. Once tenants and unknowns are silent, this only matters where the switch is on.

Airtight: one check, one matrix, one controlled test

Fede, 2026-10-23: "this needs to be extremely air tight to never reply to tenants by accident, and we are going to be doing controlled testing of that (when to reply, when not)." Also: a guest card a staff member typed by hand never gets Clara (fix PR in flight).

One check. Every path (Clara's text line, mirrored AppFolio text, leasing mailbox, mirrored AppFolio email, phone call, listing-site lead, team note) asks the same function "may Clara speak to this person here?" before it wakes Clara. No path keeps its own answer. The matrix below is that function's spec and its test.

Who reached us (one shared read)Tenant switch offTenant switch on
Current tenantsilent file onlyreply on the tenant door
Former tenant: ended lease, "former" status, or co-tenant on an ended leasesilentreply same as tenant
Number shared by a tenant record and a prospect/applicant recordsilent tenant winsreply as tenant
Prospect from a listing site, website or phone intake (system-created card)replyreply
Prospect from a card a staff member typed by handsilentsilent
Applicantreply leasing lanereply
Unknown: no record, or the lookup failedsilentsilent
Staff member, vendor, muted personsilent (vendor: vendor lane)silent
Any of the above, a teammate spoke last on the threadsilentsilent

Situs adds its listed lead sources on top of the prospect rows; Western Slope replies to every source. D2 (former tenant sending a real listing lead) stays open.

The controlled test, three rungs, in order.

  1. Replay, read-only: every inbound text, email, mirrored message and call at Western Slope and Situs since each went live, scored cell by cell: what the matrix says vs what Clara actually did. Published here as a count table with every disagreement listed. No sends.
  2. Sandbox company in Western Slope's shape: one bench person per row of the matrix (tenant, former tenant, shared number, system card, staff card, applicant, unknown, staff, muted, teammate-last), each sent through every channel, switch off then on. Evidence per cell: the record id and the message that did or did not go out. Never a real Western Slope or Situs person.
  3. Planted-defect control: flip one cell in the check and the suite goes red; without this the suite does not count.

Decisions for Fede

#QuestionOptionsRecommendation
D1What is "the tenant switch"?(a) the existing AppFolio-door setting · (b) the Maintenance module · (c) one new company setting "Clara talks to residents" that every path reads(c): one switch, visible on the company page, default off. The door setting stays only for which line the reply uses.
D2A former tenant sends a real listing-site lead (Zillow)(a) returning prospect, reply · (b) silent, team holds it(a), on the lead path only: a listing inquiry is a positive prospect signal.
D3Build orderone PR per fix belowas ranked
Fixes, ranked, one PR each (open)

Fixes, ranked, one PR each

  1. Mirrored texting: three-way role; unknown never wakes; any tenancy = tenant. PR in flight
  2. One shared "has any tenancy here" helper (current, ended, co-tenant, former), used by every audience check.
  3. Texting on Clara's line: one "may Clara speak" check on the incoming turn, replacing unknown = prospect (inbound-router.ts:783, inbound-dispatcher.ts:2095-2122).
  4. The "could not answer" team note gets the same check (conversation-manager.ts:16295).
  5. Email: "former" counts as tenant (email-reply-scope.ts:65-69, 103); the repair lane reads the switch (resident-repair-email.ts:63).
  6. Relayed leads and mirrored AppFolio email check the sender against all tenancies before replying (after D2).
  7. Phone: unknown-caller intake files nothing and forwards nothing while tenant comms are off; the name + unit fallback is tenant-gated.
  8. Rating text skips anyone whose tenancy is not current.
  9. Data: why move-outs flip only some co-tenants to former (rent-roll/apply-entity-sync.ts:232-348); then reconcile the 87 stale-active records against AppFolio, read-only.
  10. First email to a new guest card: a staff-created card never gets Clara (Fede ruling 10/23). PR in flight

Not traced: gated-inbound-team-notice.ts, the full forward-to-team path, the sender address on the Clara-address email lane.

PropFlow Docs