Western Slope leak call: the rebuild plan

What went wrong on Trina H.'s leak call on October 18, and the plan to fix it properly: emergencies always escalate, one address lookup for every channel, and every message to the team shows on the conversation.

Written 2026-10-19 from four read-only investigations of the code and production records. Gera's direction: "real good fix now, no quick fix. Design and plan now, we want best solution." Revised the same day after an independent review by Fable, which corrected the after-call diagnosis, reordered the work, and added the guardrails in section 7. Updated again on the evening of 10-19 after Gera and Fede reviewed the call together: their three stacked failures, the new slices, the decisions they settled, and the PR status are in section 2.

The short version. The emergency alert never fired. MCC got the leak only because Clara forwarded the call. The team got a plain "callback" email, our page never showed that email, the team's replies went missing, and one automated note told Jay "no leak report appears in this thread." Four fixes, planned as small changes, below.

After Gera and Fede's review (10-19): three things stacked up: we had only one of the tenant's four phone numbers, we only knew the building's address and not the unit's, and Clara wouldn't escalate without an address. Fede agreed to drop "no home, no page." The fixes for all three are in progress (section 2).

  1. What actually happened
  2. After the Gera and Fede review (10-19)
  3. A. Emergencies always escalate
  4. B. One address lookup for every channel
  5. C. Team messages show on the conversation
  6. D. How Clara handles team replies
  7. Guardrails and gaps
  8. Order, and the decisions we need
1. What actually happened
  1. 3:48 PM MT, Sunday. Trina H. calls the Western Slope main line about a garbage disposal leaking a lot of water at 1456 Bunting Ave, Apt 4.
  2. Clara tries the address three times and can't find it. That address is Lincoln Apartments unit 1456-4. Its building record says 1303 North 15th Street, and the phone lookup only checked building addresses. Fixed 10-18 Fede's #20946 now finds it for phone repair calls. It merged without review, so it gets checked by replaying "1456 Bunting" (slice M6).
  3. Clara's rules then held back the emergency. The call script says no confirmed home means no emergency page. That follows a ruling from 10-09. She also never asked how bad the leak was, and filed it as "medium."
  4. 3:50 PM. Clara emails the leasing inbox "Trina H. — callback", posts in Slack, and forwards the call to MCC. MCC reaches Ryan, and AppFolio work order 6562 goes out around 4:02.
  5. The after-call safety check also missed. It tried to open a work order, was refused because it didn't know which home, and the on-call calls never started. Why is still unknown. Production logged every step of the call, with no switch-read failure and no scope refusal, and the on-call start was never reached because the check didn't call it an emergency. Replaying the exact production text with Western Slope's emergency list gives "emergency" 20 times out of 20, so it was either model variance or something we can't see: the check's verdict was never recorded. Retracted 10-19 An earlier version of this page said the company's emergency list never reached the check; a replay of that read against production loads the list fine, so that explanation was wrong (slice A0).
  6. The "callback" email is never saved on our conversation page, so nobody on our side could show that we'd told the team.
  7. The team's replies went missing. One copy closed our notice and its text was thrown away. Another copy landed on an unrelated renter's record, because that record wrongly claimed the leasing inbox's address. Clara treated the reply as a prospect writing in. Her two draft replies to Jay were blocked before sending, but a note to the team went out saying "no leak report appears in this conversation thread." That was false. Fixed 10-19 Fede's #20949 is live in production, and the wrong claim on the leasing inbox was retired the same day.

What to tell Jay: Clara forwarded the call to MCC, which is how Ryan got it. Because of an address bug (fixed that evening), she didn't treat it as an emergency, so our urgent alert and email never went out. We're changing it so an emergency always alerts, even when the address isn't confirmed. Sent 10-19 Gera emailed Jay this correction.

Blast radius

WhatWestern SlopeSitusNote
Units whose own street differs from their building's57 units / 4 buildings246 units / 13 buildingsJPCO: unknown, because its unit streets are never saved
Text messages that can find those units today6 of 632 of 264Texts don't look at units at all
Prospect calls that can find them6 of 633 of 264The street is thrown away before matching
Real emergencies that fell to a plain callback, last 30 days1 (this one)02 other leak calls gave an address we don't hold
Team emails missing from their conversation210JPCO 3. All 24 can be re-filed from our own records
Maintenance calls where we already know the caller's unit11 of 39 (28%)—Situs has no maintenance calls in the data yet
2. After the Gera and Fede review (10-19)

Gera and Fede went through the call together on the afternoon of 10-19. They turned down a quick patch and agreed on a layered fix. Emergencies get zero tolerance, because a few misses lose a customer's trust.

Three failures stacked on top of each other

  1. The phone match. Trina H. called from one of the four numbers on her AppFolio profile. We had only one of them, so we didn't recognize her.
  2. The unit's address isn't the building's. Lincoln Apartments is spread across several streets, and her unit's street differs from the building's. Clara only saw the building's. About 30% of Western Slope's units are like this, and Situs has the same problem. AppFolio already sends us the unit's street; we just weren't reading it.
  3. The hard block. Clara knew it was an emergency but wouldn't escalate without an address.

A fourth, from our logs: the after-call check also didn't mark the call as an emergency, and we still don't know why (slice A0). The server-side content check that catches it whatever the cause is in review as #21152.

New slices

Gera's side unless noted.

#ChangeOwnerStatus
M1Match callers on every phone number a tenant has on file. A match on a secondary number still counts, but Clara confirms the unitGeraIn progress
M2The unit detail page shows the unit's own address, not just the building'sGeraIn progress
M3An emergency is never blocked by not knowing the property; on calls the property becomes nearly optional. Remove the gate in the call script, fix the places that break when there's no property, and send an urgent "call back to confirm the location" noteGeraIn progress
M4Why the emergency Slack channel stayed silentGeraInvestigating
M5Trina H.'s lease reads as both ended in 2024 and active. Sort that out, and bring back the tenant marker on the conversation pageGeraInvestigating
M6Check Fede's #20946, which merged without review, by replaying "1456 Bunting"GeraTo do
M7The unit-address fix ships for all three customers now, including fixing JPCO's unit-street sync. Situs has 246 units with their own street; JPCO has 0 of 232 synced (slice B7)GeraTo do
M8A test call: once the address is found, does Clara go on to ask troubleshooting questions?GeraTo do
M9A live end-to-end test on Western Slope's test unit after the fixes land. It rings a test on-call list of robot numbers, never the real techs or anyone's personal phoneGeraTo do, after M1 to M3

Settled in the meeting

Fede's items

PR status (evening of 10-19)

PRWhatStatus
#21131ADR-0145: an emergency always escalatesMerged
#21133The emergency list read reports what it loaded and alarms when it fails; the check's verdict is recorded (A0b)Merged
#21132Backfill: re-file the missing team emails onto their conversations (C2)Set to merge
#21134ADR-0146: team messages and replies live on the conversationIn review
#21135ADR-0147: one address index for every channelIn review
#21152Emergency content check on calls (A2, part 1)In review
3. Workstream A: emergencies always escalate

The rule (Gera, 10-19): an emergency always goes out, even if we can't confirm the location. We use what we know (the caller's unit on file, the address they said, the line's building) and mark the location "not confirmed."

Decision change This reverses Fede's 10-09 ruling ("with no home we cannot call people or do an emergency"). That ruling is built into four places: the call script, the emergency tool on the server, the after-call check, and the address tool's instructions. Gera decided the reversal on 10-19 and ADR-0145 records it. Agreed 10-19 Fede agreed to drop "no home, no page" in the review, and ADR-0145 merged as #21131.

Target design

Slices

#ChangeProof it worked
A0Done 10-19, cause unknown Why the after-call check stayed silent. All 24 production log lines for the call exist: no switch-read failure, no "list unreadable" line, and the on-call start without a work order was never entered, because the check didn't return "emergency". Replaying the exact production text (1,266 characters) with Western Slope's list gives "emergency" 20 of 20, and loading the list against production with the same arguments returns all 11 emergency types. Both of Fable's explanations are ruled out. Retracted An earlier version of this row blamed the list not reaching the check; that was wrong. What's left is model variance or something unobserved, and it can't be settled because production never recorded the verdict. Since the cause is unknown, the fix that doesn't depend on it, the server-side content check (A2), moves up to right after A1.Answered with the log lines and replays
A0bMerged 10-19 #21133 records the check's verdict on the dispatch log, logs whether the company's emergency list loaded, and alerts ops when that read fails, so the next miss can be explainedA replayed call shows the verdict on the dispatch log; a forced failed read raises the alert
A1After the call, an emergency verdict starts the on-call calls on the line's company, carrying the address as spoken and "location not confirmed". Starts from the company and line phone with no building id. Runs without Clara having to cooperate.Replay the 10-18 call through the after-call sweep into a test company: the on-call list rings
A2Part 1 (calls) in review as #21152. Server-side content check with the classifier, on calls, texts and emails (extends #19816). Golden case: the 10-18 call, plus two false-alarm controls (a contained drip, a water-bill question)A "medium" note saying "a lot of water" pages; both controls don't
A3Every fail-closed read on the chain path alarms; a missed-emergency detector (an emergency verdict or urgent note with no chain start within a few minutes alarms)A forced read failure on a test company raises the alarm
A4During the call: remove the "no home, no page" rule from the call script and the address tool, and the server refusal for calls with no building; ask once, then best guessBench call with an unknown address and a leak: page sent, marked not confirmed
A5Confirm, don't ask, for recognized callers; a different stated place becomes a note on the work order and both places go on the alert; a two-home guess never files a work orderBench: a recognized caller names another street, and the alert shows both
A6Plain urgent subject and body on team emails, with actions already takenThe 10-18 replay produces the emergency subject above
Conflict: open PR #20742 narrows what counts as a "major leak" in the same script. That contradicts Gera's 10-19 rule (any active leak counts), so #20742 has to be reconciled with it before either change merges. Its test case that calls a pooling sink drip routine contradicts the definition above (pooling is spreading), so #20742 must be amended first. A4 rebases on it.
Code detail (file:line)
  • Script gate: agents/clara/lib/voice-agents/maintenance-tenant.ts:163,191,218,223,232; skipped severity question :170-173; recognized callers already skip the address question :234
  • Server refusal for calls with no building: handle-escalate-to-human.ts:371-389; after-call refusal voice-postcall-intake.ts:503-510; after-call fallback that already labels the address unconfirmed :448-470,819-835
  • Address tool's answer told Clara to page anyway: match-home-answer.ts:38-46
  • Routine work orders refused for "which home unclear": handle-create-work-order.ts:965-973 (life-safety already proceeds with a "Unit check" note)
  • "Callback" subject: src/lib/team-notify/compose.ts:89-98; callback hard-coded at voice-handoff.ts:147; the note tool's urgent email is skipped when the newer notice system is on (handle-capture-unknown-caller-note.ts:546)
  • Alert rows: emergency-alert-notices.ts:122-140; the chain's "off" check runs before the duplicate-protection key is written (emergency-alert-chain.ts)
  • Western Slope setup: on-call calling on, 3 people on the saved list, live on-call off, and no emergency phone on the main line
4. Workstream B: one address lookup for every channel

Today there are five separate address lookups (phone repair calls, prospect calls, texts, email and leads, staff tools). They split each address into words three different ways and load the company's homes five times. Only two of them read a unit's own street. Fede's #20946 fixed one more. A fix to one never reaches the others.

Target design

Decided 10-19 (Gera) Some units exist twice. Ember Estates' 25 units and the DU portfolio's 15 are on file both as part of a building and as separate single-family homes, and today the lookup picks one by accident. The building's unit wins. Records AppFolio marks inactive drop out of the lookup, and the duplicates are flagged to the customer at onboarding so they can clean them up.

Slices

#ChangeProof it worked
B1Create the index and move phone repair calls onto it first, the path that missed this emergency; delete #20946's separate patchReplay the 10-18 call and every address attempt since 10-09
B2Build check against side lookupsA deliberate side lookup fails the build
B3Texts onto the indexA Lincoln test that fails first; Western Slope goes from 6 to about 37 of 63 units
B4Prospect calls onto the index (keep the unit street)Lincoln goes from "several" to the right unit
B5Email onto the index (it already reads unit streets, so this is consolidation)Western Slope and Situs email replays give identical results
B6Ranges, names in the address field, "street - door" format; confidence and next step; alias tiers and the map fallbackThe five Situs buildings with a name or list in their address field resolve; a nearby-street alias resolves with "likely"
B7Duplicate-record rule (the building's unit wins; AppFolio-inactive records drop out); unit streets saved for every company through the adapter, which fills JPCO's (0 of 232 today). Best-guess auto-filing waits for thisJPCO shows unit streets; Ember units resolve to the building's unit
ING1-2propflow-ingest: alias step, plus onboarding conflict flagsA Western Slope draft lists Bunting for Lincoln, with learned aliases marked "suggested"
Code detail (file:line)
  • Phone repair calls: tools/maintenance.ts:732 → handle-match-home.ts:114 → mid-call-home-match.ts:113 → maintenance/spoken-home-resolver.ts:255 (unit street fallback since #20946 at :289-311)
  • Prospect calls: postcall-prospect-capture.ts:658-677 drops the unit street at :675, although leasing/spoken-home-resolver.ts:72-77,482 supports it
  • Texts: resolve-turn-home.ts:168 → home-match.ts:109 (one row per building only)
  • Email and leads: home-address-key.ts:332,456; org-mailbox-inbound.ts:174-180; front-door-unit-home.ts
  • Unit street is written only by org-listings-sync.ts:665-684 via listing-unit-ids.ts:341 (10-second budget). The comment at rent-roll/utils.ts:442 claiming AppFolio has no unit street is wrong
  • Alias store: data/dynamo/property-aliases.ts:26-45 (kinds street and nearbyStreet; sources pms, geo, learned, manual; statuses active, suggested, retired)
  • Ingest home for the alias step: propflow-ingest/ingest/address-aliases-step.ts, beside prefill-step.ts, run by runner.ts, output as a bridge draft
5. Workstream C: every message to the team shows on the conversation

The rule (Gera): every email, text or Slack message we send the team, and every reply they send back, appears on the conversation it's about, with a link to it.

Why it's missing today: Western Slope and JPCO run on the newer team-notification system. It sends the email and keeps its record somewhere the conversation page never reads. Gera's 10-14 fix (#19577) covered only the older path. About 40 kinds of team message exist, and most of them, including several work order, move-out and collections emails, leave nothing on a conversation.

Target design

Slices

#ChangeProof it worked
C1Notices from the newer system go onto the source conversation with the link; the older duplicate path is removed. Small and independent, so it ships early. It does nothing for Situs, which isn't on the newer system; Situs notices are covered when the older path folds into the same doorThe next Western Slope callback shows "Clara emailed the team" on the conversation
C2Backfill: re-file the 24 missing emails (Western Slope 21, JPCO 3) from our own records. Gera approved Dry run first; only adds, never deletesDry-run report, then the 10-18 call shows the email
C3Staff email replies saved on the source conversation; one row even when two copies arrive. One slice, one owner: this is the same change as the staff-reply investigation's first follow-upA reply to a test notice shows once on its conversation
C4Staff texts and their replies on the source conversationA test text shows up
C5"Posted in #channel" line for SlackThe 10-18 call shows the #western-slope post
C6Link check across every other team message (work order, move-out, collections and others)Every team message has a link that opens the right thread
Code detail (file:line)
  • Newer system records nowhere visible: src/lib/team-notify/live-deps.ts:48 (noConversationRecord); engine send engine.ts:205
  • Older path's thread write that the newer path skips: handle-capture-unknown-caller-note.ts:546,568 vs :755-779
  • Rendering already supports it: pm-internal-kinds.ts:63, ConversationThread.tsx:3974
  • Reply match only closes the notice: team-notify/ingest.ts:74-77; door staff-reply/door.ts:187, inbound-email-reply.ts:173
  • Email ids dropped on the way in: email/client.ts:943
  • Prior decisions: ADR-0079 (one writer), ADR-0119 (every send declares its record), ADR-0141 §5 (one staff-reply door)
6. Workstream D: how Clara handles replies from the team
Decision change Gera (10-19): Clara may answer team replies. That conflicted with Fede's 08-20 rule that Clara never auto-replies to staff, and with the guards Fede added on 10-19 (#20949). The review settled it the same day, below.
Settled 10-19 In the review, Gera and Fede agreed that Clara answering team replies follows Fede's shared-inbox gating design: three layers (is it for her, can she help, has nobody else handled it), about 95% confidence, and supervised mode first. Fede owns that design. ADR-0146 (#21134) now only covers recording team messages and replies on the conversation; the answering rules below are inputs to Fede's design.

Target design, built so the 10-18 mistake can't repeat

Slices

#ChangeProof it worked
D1Tag the forward-to-manager email so its replies come back through the staff doorA reply to a forwarded test email matches its conversation
D2Clara's staff-answer turn, for replies to questions only, runs with the source conversation as context (after C3)Replay of Jay's question: Clara describes the call and the MCC forward correctly
D3Prompt rule plus an outgoing check against "no record" claims without a lookupThe 10-18 "no leak report" draft is blocked
D4Questions Clara can't answer go to a person in SlackA test question she can't answer lands in the company's channel
D5Stop the company's own addresses opening leads on Clara's inbox (left open by #20949; needs Fede's view)A staff address can't create a prospect
DataRemove the test record's claim on the leasing inbox address and close its stale item. Gera approvedThe leasing inbox no longer resolves to a renter
Code detail (file:line)
  • Why the false note went out: the sent copy had no headers (inbound-email-reply.ts:111-176), passed the own-outbound gate (process-inbound-message.ts:1062) and the "addressed to Clara" exception (:1419-1513), then the matcher tied the address to a placeholder from the 9-13 rent-roll backfill
  • Draft replies blocked by the own-mailbox check: undeliverable-recipient.ts:234,261
  • The forward-to-manager email has no reply tag: handle-forward-to-property-manager.ts:1700; pattern to copy page-property-manager.ts:726-800
  • #20949 guards: :1140-1160, :1505-1513, staff-notice-thread.ts, thread-context-bypass.ts:207
  • Existing rule covering colleagues only: clara-leasing.ts:924-927
7. Guardrails and gaps (from Fable's review)
8. Order and dependencies
  1. Now, in parallel: A0 is done (cause unknown); A0b (#21133, recording the verdict and alerting on failed reads) has merged; the meeting slices M1 to M3 are in progress; C1 (ships early), then C2 (the backfill, #21132, set to merge); D1. The leasing-inbox data cleanup is done (10-19).
  2. Then A, in this order: A1 (after-call start, proven by replaying the 10-18 call into a test company) → A2 (server-side content check, the fix that works whatever the 10-18 cause was) → A3 (alarms and the missed-emergency detector) → A4 (the during-call gate and the server refusal, now slice M3; ADR-0145 has merged, and #20742 still needs amending) → A5 → A6.
  3. B, after A1: B1 (phone repair calls) → B2 (build check) → B3, B4 and B5 in parallel → B6 → B7. The ingest alias steps can start alongside B1.
  4. C3, then C4 to C6. D2 to D5 follow Fede's shared-inbox gating design.
  5. Last: the live end-to-end test on Western Slope's test unit (M9).

Each change is branched from main, kept to about 300 lines or fewer, and works on its own. At most three are open at once per builder. Already merged and live: #20946 (unit street for phone repair calls) and #20949 (staff mail no longer treated as customer mail).

9. Decisions we need

The decision records: ADR-0145 merged in #21131, ADR-0146 in #21134 and ADR-0147 in #21135 are in review.

WhoDecisionStatus
FedeADR-0145: emergencies always escalate, dropping "no home, no page"Agreed 10-19; merged as #21131
FedeClara answering team replies: follows Fede's shared-inbox gating design (three layers, about 95% confidence, supervised first). ADR-0146 only records replies on the conversationAgreed 10-19; Fede designing
GeraFallback when we can't place the caller: the last unit we know for them, flagged. Tenants treated as active get checked as really activeDecided 10-19
GeraDuplicate records (Ember Estates, DU portfolio): the building's unit wins, inactive records drop out, flagged at onboardingDecided 10-19
GeraAny active leak is an emergency; only a contained or shut-off slow drip is routine. #20742 must be reconciled with thisDecided 10-19
GeraUnconfirmed-location alerts go to the same on-call chain as any emergency, marked "location not confirmed" with Clara's best guessDecided 10-19
GeraNon-emergency call with an unconfirmed unit: file on the caller's known unit at normal priority with a "confirm location" note; no hold for staffDecided 10-19
GeraThe live end-to-end test rings a test on-call list (robot numbers, no personal phones), not the real techsDecided 10-19
GeraThe unit-address fix ships for all three customers now, including JPCO's unit-street syncDecided 10-19
FedeD5: company addresses can't open leads on Clara's inboxOpen
GeraRe-file the 24 team emails (after C1); remove the leasing inbox's fake ownerApproved 10-19; the fake owner was removed 10-19
GeraLearned aliases need confirmation firstDecided 10-19
GeraCorrection to JaySent 10-19
PropFlow Docs