Monday production-readiness audit — The Willows go-live

Target: Monday 2026-08-03 · property appfolio-45 (The Willows, org_sandbox) · prod reads current as of 2026-07-31 16:30 MT · 134 adversarially-verified findings, deduped to 117 distinct defects.

NO-GO as it stands

Five P0 defects stand confirmed (P0-2, P0-3, P0-4, P0-7, P0-8) and none was closed by the four in-flight fix branches. All four branches merged to main today and fixed what they were written to fix — but they addressed the symptoms found on the demo floor, not the failures underneath. Two of them made a P0 worse: the vendor arm gates were deleted (making the unvalidated /review approve the last line of defence), and the voice tool was published to the live phone agents ~35 minutes ahead of the app that serves it.

Conditional GO is reachable — the gate list is below. Recommend a go/no-go re-check Sunday evening against that checklist.

Update, late 2026-08-01: all five confirmed P0s are fixed, deployed and prod-verified (P0-2 #5183/#5188 · P0-3 #5186 · P0-4/5 #5187/#5190 · P0-7 #5184 · P0-8 #5185/#5189 — details on each card). The NO-GO stamp reflects the audit as published; the Sunday re-check should now weigh the open P1s and the three deliberate decisions (dispatch posture, workflow arming, held sentinel repairs), not these P0s.

AMENDED — 2026-08-01 evening

This report originally published nine P0s. Re-checking each against the audit's own adversarial verification layer (135 verify verdicts) and fresh production evidence corrected four of them. The verdict remains NO-GO, resting on the five confirmed P0s — every one of which has a fix PR in flight tonight.

Live mitigations already applied tonight: the turnover-cohort-walker schedule is paused (kills the P0-2 retry loop; unpauses when the cap fix deploys); the two /review rows pointing at deleted work orders are rejected (P0-4 data leg); Willows renewalContactPhone and renewalContactEmail now point at real recipients (P0-3 config leg — propertyEmail sink retained pending a deliberate call). Fix PRs for P0-2, P0-3, P0-4/5, P0-7 and P0-8 are in flight with merge-on-green.

0
P0 confirmed open
42
P1 open (as published)
65
P2 open (as published)
5
P0s fixed + deployed 08-01
2
published P0s refuted

What the four in-flight branches actually cover

All four are on origin/main as of 16:23 MT. Verified by reading the merged code, not the PR titles.

Branch / PRKnown bug it closesStateP0 coverage
fede/postcall-outcome-chain
merged in #5178 4eab6bffc
10 — no PM notification on any call terminal state merged + deployed None — and it inherits P0-3. notify-pm-outcome.ts:677 on main still reads .then(() => 'email' as const), and its new resolvePmContactEmail falls back to propertyEmail, which at The Willows is a @test.local sink. The branch's new "every terminal state notifies" test goes green while nothing is transmitted.
fede/callback-reschedule-tool
merged in #5176 36d4be590
11 — vendor callback reschedule had zero tools merged + deployed None. Publishing it created a ~35-minute live split-brain (now closed, see below), and the same commit set removed the two-factor vendor arm gate — which is what promotes P0-4 from "a gate behind a gate" to "the only gate, and it validates nothing".
fede/router-staff-before-vendor
on main, inbound-router.ts:264-288
1 — SMS vendor-walk-before-staff-walk hijacked PM texts merged None. Narrow by design — pm-only, non-voice. The identical precedence inversion on the voice path is untouched and is P1-14 below.
fede/turnovers-scoped-fetch
merged in #5170 bd711fef0
7 — turnovers page unscoped fetch stripped test properties merged None.
Known bug 4 (dispatch dedupes against CANCELLED work orders) is not fixed anywhere. Verified on origin/main today: handle-dispatch-work-order.ts:195 still keys the dedupe on workOrderId presence + label equality with no status term. Listed as P1-27.

The nine P0s

Plain-English headline first. Every claim below has a code reference or a production record behind it; the live numbers were re-read at 16:30 MT today.

P0-1 Inbound email goes dark tomorrow — at the demo property and at the live customer REFUTED 2026-08-01

AmendmentFalse alarm — a working production renewer exists at lambda/outlook-subscription-renewer/handler.ts:292 (missed by the original src/ scripts/ grep): Lambda propflow-outlook-subscription-renewer-prod Active, EventBridge rate(30 minutes) ENABLED per config/automations.toml:1015, 0 errors over 5 days, CloudWatch shows successful renewals of both subscription ids on 07-27/07-29/07-30, and the 22:33Z tick reports both healthy. The quoted expiry timestamps are the result of the latest successful renewals — the renewer refreshes ~90 min before expiry (RENEWAL_THRESHOLD_MINUTES=90). ADR-0003 mandates EventBridge, which is why no Vercel cron exists. No action needed; a secondary observability defect (renewer reports green on failed runs, alerting=false) is tracked separately for Gera.

The Microsoft Graph mail subscriptions expire Saturday 2026-08-01: Camellia at 14:03 MT, The Willows at 16:38 MT. Nothing in production renews them. renewSubscription() and needsRenewal() are exported and have zero non-test callers; none of the 27 Vercel crons touches Graph; there is no polling fallback. From Saturday afternoon, Microsoft stops POSTing and PropFlow receives no inbound mail for either mailbox — email-borne notices to vacate, tour replies, vendor replies and lease/turnover intake all stop. The failure mode is total silence, because the only code that would notice is never called.

Evidence — src/lib/integrations/email/graph-subscription.ts:154; DDB PROP#appfolio-45/META.emailIntegration.subscriptionExpiresAt = 2026-08-01T22:38:01Z, PROP#1773625953462 → 2026-08-01T20:03:01Z (both re-read 16:30 MT today).

Fix directionRe-subscribe both mailboxes with the operator script before Saturday 14:00 MT, then add a daily cron that calls needsRenewalrenewSubscription and alerts on failure.

P0-2 No turnover work order at The Willows can reach AppFolio — and the retry loop is running right now FIXED · DEPLOYED · VERIFIED

Status 2026-08-01 — closedPRs #5183 + #5188 deployed (worker task-def :1466): cancelled WOs excluded from sync candidacy (cancel-mirrors with a pmsId preserved), hard cap of 6 mirroring the charge lane, permanent failures short-circuit on the first attempt, and the throw path now escalates. Walker unpaused and observed over 5 ticks: exactly one final sync attempt, a terminal_failure stamp, cancelled WOs frozen, and one honest critical ("sync ABANDONED — manual filing required") vs the prior 17 criticals per 75 minutes. The stamp covers all 8 children of the bench turnover — unit "12" does not exist in AppFolio, so none can sync until the unit mapping is corrected and the stamp cleared.

Unit "12" does not exist on AppFolio property 45, so every child work order fails to create. The sync lane has no attempt cap — the sibling charge lane got one after an incident where the same uncapped pattern drove a full MFA login per attempt for seven days, rotated the shared AppFolio session and broke PDF fetch fleet-wide. The candidate finder also never excludes cancelled work orders, so 45 dead rows pin this turnover as a candidate forever. syncRemediationAttempts was 22 when the audit opened and reads 54 now: roughly one real AppFolio browser-agent call per work order every three minutes, indefinitely, plus a critical alert on every tick. Separately, when the sync throws the function returns before the escalation block, so that entire class alerts nobody at any attempt count. The demo's headline claim — "the work order lands in AppFolio" — is false at The Willows and has been since 13:49 MT today.

Evidence — src/lib/domain/turnover/turnover-wo-reconciler.ts:392 (escalation) and :385 (throw path returns first); turnover-cohort-walker.ts:357; DDB TURNOVER#trn_78537941-45d5-47f2-84e1-f810e3020310syncRemediationAttempts=54, updatedAt 2026-07-31T22:30:15Z. Also armed on Camellia (autonomousTurnoverEnabled=true).

Fix directionAdd SYNC_ATTEMPTS_HARD_CAP + a terminal sentinel mirroring CHARGE_ATTEMPTS_HARD_CAP; exclude cancelled work orders from findUnsyncedWoCandidates; fall through to escalation on throw; and fix the WLH-12 / WLH-17 unit mapping (P1-22).

P0-3 Every PM notification at The Willows is thrown away — and the code records it as delivered

propertyEmail is willows-sink@test.local (still, at 16:30 MT) and there is no renewalContactPhone, so both legs of every PM page die at the test-recipient gate. The deeper defect is that no sender checks the result: sendEmail is total and non-throwing for eight suppression reasons plus hard bounce, and pagePropertyManager discards the outcome and returns emailDelivered: true unconditionally. So the gas-emergency path tells the tenant "your property manager has been paged", writes a pm_page_email row onto the thread asserting it, and the loud "page NOT delivered" alarm — the one signal built to catch exactly this — never fires. Prod EMAIL_LOG#2026-07-31 shows 87 of 100 rows blocked to that sink, including [PropFlow GAS EMERGENCY] — The Willows Unit TEST-204, and a real conversation already carries a recorded URGENT flooding page that was never sent. The same discard is in the vendor-outcome and purchase-order-required notifiers. This is property-independent: it also fires silently on any real property whenever emails are disabled, shadow mode is on, or the PM's address is bounce-suppressed.

Evidence — src/lib/domain/maintenance/page-property-manager.ts:138, notify-pm-outcome.ts:677, notify-pm-po-required.ts:155; the correct predicate emailDeliveredToRecipient() already exists at email/client.ts:382 and four other senders use it.

Fix directionGate all three sites on emailDeliveredToRecipient(outcome) and move the audit-row write inside that gate; point propertyEmail at a real inbox and set renewalContactPhone. Both halves are needed — the code fix alone still delivers nothing.

P0-4 One click in /review places a real vendor call about a cancelled job FIXED · DEPLOYED · VERIFIED

Status 2026-08-01 — closedPRs #5187 + #5190: every dial now revalidates the work order (exists, not archived, open status) and the vendor (exists, not archived) — refusal cancels the session with a machine-readable reason and pages the PM, never a silent revert. Verified read-only against live rows: the staged demo session (51be5319 → WILL-002178, dial target = the PM's cell by design) passes; five stale cancelled/missing WOs refuse. The two stale queue rows were rejected earlier tonight. Correction to this card's evidence: WILL-002181/002182 were not deleted — their displayIds were upgraded to WILL-1982/WILL-1983 (one dispatched, one cancelled); the genuinely missing rows are WILL-002088/002119/002120. P0-5's fixes (sessions close with their work order; the 14-day rebind rung now status-filtered) shipped in the same PRs.

dialApprovedVendorSession fetches the work order but never checks its status, never refuses a missing row, and never checks whether the vendor is archived. The decision route checks only that the session is awaiting_approval. Three rows are sitting in the queue right now: f3ecb71d points at WILL-002182, status cancelled, and 51be5319 would dial +14042859387 — the PM's own cell, which is registered as Chapa Cleaners' number and is also the property's published office and emergency line. Because the vendor arm gates were deleted today ("the review queue is the only gate"), this approval is the last line of defence, and it validates nothing about the job. The queue rows are visually indistinguishable from good ones.

Evidence — src/lib/domain/vendors/dial-approved-session.ts:175; api/vendor-outreach/[id]/decision/route.ts:47; DDB PROP#appfolio-45/VENDORCALLSESSION# (3 awaiting_approval, re-read 16:30 MT) cross-referenced against WO#WILL-002178/002181/002182.

Fix directionRe-validate work-order existence, status and vendor archivedAt inside dialApprovedVendorSession; refuse and surface the reason in /review rather than dialling. Before Monday, cancel the three stale rows by hand.

P0-5 Cancelling a work order leaves its vendor call session live, and the vendor is rebound to the dead job for 14 days DOWNGRADED P2 FIXED

AmendmentReal defect, but the audit's own verify verdicts scored it P2, not P0 ("confirmed as a real defect… downgrade"). Fixed in PRs #5187 + #5190: sessions now close from closeWorkOrder, the PATCH open→terminal transition, and DELETE; the recent_outbound rung carries the same status filters as priorities 2/3.

cancelOpenVendorSessions is called from two agent tools only — never from closeWorkOrder (the single closure primitive every cancel path funnels through) and never from PATCH /api/work-orders/[displayId]. Inbound vendor binding then falls to priority 4, recent_outbound, which filters only on direction and a 14-day recency window: no session-status filter and no work-order-status filter, while priorities 2 and 3 correctly filter on open statuses. Twelve stale Willows sessions sit inside that window pointing at cancelled or hard-deleted work orders. A vendor who calls the Willows line back gets greeted about a job cancelled days ago, and any tool call keyed on that id writes to a cancelled work order or 404s mid-call.

Evidence — src/lib/domain/vendors/inbound-binding.ts:199 (vs the correct filter at :171); src/lib/domain/maintenance/close-work-order.ts (no vendor-session reference).

Fix directionCall cancelOpenVendorSessions from closeWorkOrder and the work-order PATCH route; add open-status and session-status filters to the recent_outbound rung.

P0-6 Clara phones vendors about jobs that were never sent to them REFUTED AS FRAMED

AmendmentThe verify layer refuted this thesis on six counts: the double-disarm is the ADR-0112 safety architecture working as designed, not an outage. The confirmed sibling is a P1: TurnoverDetailClient.tsx:613 toasts "Work approved — work orders sent to the vendor" on any non-error response even when dispatch is disarmed and nothing was sent — which happened during today's demo. Whether to arm external dispatch for Monday (so voice and email lanes agree) remains a deliberate product decision, not a defect fix.

Vendor voice calling is on at The Willows (vendorVoiceCallingEnabled=true, 64 sessions dialled, newest today), but external dispatch is disarmed twice over: VENDOR_DISPATCH_AUTONOMOUS_SENDING is absent from both the Vercel production environment and the ECS task definition, and autonomousVendorDispatchEnabled=false on the property. The dispatch gate declares no armField, so unlike turnover sending there is no durable-row escape hatch — flipping the property flag alone will not fix it. Net effect: dispatchExternalTurnoverWorkOrders returns not_allowed on its first line and logs at INFO, so the vendor never receives the dispatch email with scope and photos, while the voice lane dials them anyway. The allowlist that would backstop a wrong send (VENDOR_DISPATCH_RECIPIENT_ALLOWLIST) is also unset and fails open.

Evidence — src/lib/temporal/vendor-dispatch-gate.ts:31; external-dispatch.ts:162; vendor-dispatch-allowlist.ts:14; DDB PROP#appfolio-45/META re-read 16:30 MT.

Fix directionPick one lane for Monday. Either populate the allowlist and set both dispatch arms, or turn vendorVoiceCallingEnabled off so the two lanes agree — and do not claim on the demo that the vendor received scope and photos.

P0-7 The durable turnover workflow sleeps until the move-out date, so nothing it owns runs after the PM approves TIMER FIXED · DEPLOYED

Status 2026-08-01 — timer race closedPR #5184 deployed (worker :1467): the move-out wait now races the inspection signal, with a properly-placed patch marker and a replay corpus covering the entire in-flight prod cohort (both runs replay clean post-deploy). Dispatch deliberately still waits for move-out — its copy is date-less and racing it early would message occupied units. The stuck run 019fb9a3 was deliberately not reset: its turnover is already scoped, so a reset would file a real AppFolio move-out draft three weeks early and then close the workflow — it is inert and self-drains 08-21. Remaining substance, tracked separately: with inline-minted WOs (today: every turnover) the workflow wakes and exits at done before sync and both dispatch lanes — that fix wants its own patch marker and review. TURNOVER_WORKFLOW_ENABLED remains unset; arming is a deliberate decision.

The workflow parks on await condition(() => false, moveOutWaitMs(...)) before it reaches the inspection hold. Signals still latch — so inspectionCompleted and chargeConfirmed set their flags while the body is blocked. Verified in production Temporal: run 019fb9a3… for today's demo turnover started a timer of 1,744,490 seconds (20.2 days, firing 2026-08-21), received both signals, and emitted zero commands afterwards. Because notice is normally filed 30-60 days ahead, this reproduces for every ordinary NTV. The conversational approve path — the one the demo uses — mints work orders inline but does not sync or dispatch; the workflow owns AppFolio sync, in-house dispatch, external dispatch, the cleaning-last release, ready-for-listing and re-list, and it is asleep for all of them. The cohort walker only backstops mint, sync and charges. Compounding: TURNOVER_WORKFLOW_ENABLED is unset in both runtimes, so the durable workflow has never started for a single Willows turnover — easy to mistake for "on", because the turnover send arm is armed.

Evidence — src/lib/temporal/workflows/turnover-workflow.ts:539 (timer) and :591 (inspection hold); src/lib/domain/automations/handlers/ntv.ts:24; prod Temporal run history, namespace propflow-renewal-prod.

Fix directionMove the move-out timer below the inspection hold. For Monday, demo a turnover whose move-out date has already passed, or set TURNOVER_E2E_MOVEOUT_LEAD_MS.

P0-8 The demo PM's phone number cannot be claimed in any org — identity is held together by a hand-written database row FIXED · SANDBOX REPAIRED

Status 2026-08-01 — code fixed, org_sandbox repairedPRs #5185 + #5189: the deprecateClaim silent fallback is gone (cancelled transactions re-read and re-decide, throw rather than orphan), a second unreported leak (normalization-drift hiding a row's own sentinel) is fixed, and upsertPrimaryPhoneClaim now mints-before-deprecates and refuses to clear a verified claim with a plain-English 409. An exhaustive scan found 22 orphaned sentinels, not 14. The 2 org_sandbox partitions are repaired and verified (addClaim on the demo contact now returns created; inbound resolution still picks the PM person). 20 orphans are listed and held for a deliberate go: 8 in org_jpco (Camellia — read-only), 10 in org_unrouted, 2 in org_propflow_staff (including jose@propflowai.co). Ledger: BACKFILL_AUDIT#sentinel_repair_0bea2649-0b6.

deprecateClaim deletes the dedup sentinel inside a transaction, but when that transaction cancels it silently falls back to writing the claim row alone and leaves the sentinel alive — no log, no throw. Fourteen production (org, type, value) partitions now hold a sentinel pointing at a deprecated or missing claim. Three of them are +14042859387: in org_sandbox addClaim returns reused_deprecated pointing at a parked prospect, and in org_jpco it throws outright. Someone already worked around it — claim_demo_pm_fede_cell_20260731, hand-written at 10:28 MT today — and because that id does not match the sentinel, deprecateClaim can never repair the partition. Any legitimate write (Admin ▸ Users, email resync, AppFolio sync, an identify_caller merge) removes that row and cannot replace it. Compounding: upsertPrimaryPhoneClaim deprecates the old phone before adding the new one and discards addClaim's result, so saving a colliding phone in Admin ▸ Users leaves the person with zero active phone claims and returns HTTP 200 with a success toast.

Evidence — src/lib/data/dynamo/persons.ts:560; src/lib/domain/identity/upsert-phone-claim.ts:96; DDB CLAIM#org_sandbox#PHONE#+14042859387/META → deprecated claim_bdc3f54b.

Fix directionRepair the 14 sentinels (repoint at the live claim or delete); make the deprecateClaim fallback fail loud; mint-then-deprecate in upsertPrimaryPhoneClaim and branch on the result. Until then: do not edit that user's phone in Admin ▸ Users.

P0-9 Do not flip vendorJobReferenceMode back to purchase_order DOWNGRADED P2 — ADVICE STANDS

AmendmentThe verify layer confirmed the infrastructure half (the PO-sync EventBridge rule was never created — lease-states-history-schedule is also missing, corroborating a pattern) but refuted the "grounds ALL outbound calls" failure chain as overstated. The operational advice is unchanged: stay on work_order through Monday; fix and prove PO sync before any flip.

All nine Willows purchase-order rows are hand-seeded (syncRunId=demo-seed) and cross their staleAfter at 2026-07-31 22:15 MT — tonight. POSYNC#WATERMARK has no lastSuccessAt attribute at all, so the second freshness witness fails too and every row reads stale from tonight onward. It cannot self-heal: the AppFolio purchase-order report genuinely returns zero rows for property 45, the sync therefore refuses to mass-remove and errors, and its EventBridge rule was never created in production anyway. In purchase-order mode, every PM-approved vendor call then resolves po_required, refuses to dial and bounces silently back to awaiting_approval. Secondary: isExternallyDispatchable inverts under that mode and starts requiring unitNumber — 28 of 29 Willows unit rows carry an empty one.

Evidence — src/lib/data/dynamo/purchase-orders.ts:378; dial-approved-session.ts:339; DDB PROP#appfolio-45/POSYNC#WATERMARK (consecutiveFailures=3, no lastSuccessAt). Current mode re-read 16:30 MT: work_order — correct.

Fix directionLeave the mode on work_order through Monday. Before any future flip: create the EventBridge rule, re-seed the purchase orders against a property that actually has a PO book, and require a real lastSuccessAt on the watermark.

CLOSED MID-AUDIT  Live voice split-brain — reschedule tool bound ahead of the code

At 15:41 MT the ElevenLabs workspace had reschedule_vendor_visit bound to the Triage agent — which fronts three live numbers including Camellia's — while production served a build with no such route, returning HTTP 200 carrying {"error":"Unknown tool"}. Production moved to 611f07880 at 16:16 MT, which carries both the route mapping and the handler, and CI is green on 14d56c5d7. The window closed; the cause did not. sync-specialists.yml is a bare push: branches: [main] trigger with no CI gate, while Vercel, Lambda and ECS all gate on CI — so the voice config plane publishes ahead of the app on every tools-touching merge, and unboundedly whenever main is red. Tracked as P1-42.


The gate — nine items between here and a GO

Five are config or ops actions. Tick them here; the state is shared across everyone on docs.propflowai.co.

  1. Re-subscribe both Graph mailboxes before Sat 14:00 MT — VOID: P0-1 refuted The EventBridge-scheduled renewer Lambda already does this every 30 minutes, with a 5-day zero-error record. No action. (P0-1 amendment)
  2. Point PROP#appfolio-45.propertyEmail at a real inbox and set renewalContactPhone Half done 2026-08-01: renewalContactPhone and renewalContactEmail now real (PM notices deliver via the #5178 resolution chain). propertyEmail sink retained pending Fede's call. (P0-3)
  3. Gate the three PM senders on emailDeliveredToRecipient Life-safety page, vendor outcome, PO-required. Small diff; without it the fix above is unverifiable. (P0-3)
  4. Cap the turnover sync reconciler and exclude cancelled work orders DONE 2026-08-01: PRs #5183/#5188 deployed, walker unpaused, loop terminated with one honest escalation — verified over 5 live ticks. (P0-2)
  5. Re-validate the work order inside dialApprovedVendorSession, and clear the queue Status + existence + vendor archivedAt — PR in flight. Stale rows rejected 2026-08-01 (51be5319 stays: staged demo session). (P0-4)
  6. Close vendor sessions from closeWorkOrder and the WO PATCH route Plus a status filter on the 14-day recent_outbound rebind. (P0-5)
  7. Make the vendor voice and vendor email lanes agree Either arm dispatch (with the allowlist populated first) or switch vendor voice calling off. (P0-6)
  8. Choose the demo turnover so the workflow is not parked Past move-out date, or set the lead env. Fix the timer ordering after go-live. (P0-7)
  9. Leave vendorJobReferenceMode on work_order, and freeze the demo PM's identity row No Admin ▸ Users edits to +14042859387 before Monday. (P0-8, P0-9)

P1 — 42 open

Serious, not go-live-blocking on their own. Grouped by area; the reference is the file and line the fix lands in.

Overnight verification 2026-07-31→08-01All 42 P1s independently verified against code and prod. 20 of 42 (48%) were wrong, overstated, or already fixed — matching the P0 section's 44% error rate. The roll-up's habit of publishing over its own verifiers' negative verdicts is systematic; treat every unverified finding in this report as a claim, not a fact.

Vendor calling and the /review queue

#What breaksReferenceFix direction
1/review never refreshes. One fetch on mount, no polling. An approval Clara raises mid-demo never appears, and an approved row is frozen at "Approved — calling shortly" for the rest of the session: real outcomes land 40s–3.5min later, the page's last refetch is at +1.2s. Every sibling operations page polls. FIXED 2026-08-01
Verified 2026-08-01Fixed in PR #5209, merged e4d4468c8 — review-queue polling refresh.
review/page.tsx:190usePolling(load, POLLING_INTERVAL_MS)
2A failed /review fetch renders an empty queue with no error, no banner and no retry — "nothing needs me" is byte-identical to "the API broke". The vendor half is dropped silently on any non-2xx. FIXED 2026-08-01
Verified 2026-08-01Fixed in PR #5209, merged e4d4468c8 — explicit dead-API error state.
review/page.tsx:182Error state + banner; keep last-good rows
3Approve toasts success before any gate runs. Five of seven refusal reasons then say nothing and strand the row at approved with no Approve button and no redial cadence — and the refusal text tells the PM to "approve the call again", which the API 409s.dial-approved-session.ts:406Mirror the po_required branch: patch back to awaiting_approval + notify
4Approval requests have no nudge, no expiry and no push. The only signal is one sentence inside the conversation. Sessions sat 66 hours before a human bulk-cancelled them. ADR-0111 specifies "approval expires with the business day"; not implemented.handle-dispatch-work-order.ts:396Business-day TTL + PM reminder
5A Thursday-evening or Friday voicemail gets zero redials. All three touches fire inside the promised-callback window, the cadence completes silently with redialsPlaced: 0, and nobody is told. Touches 1 and 2 are structurally dead for every voicemail.coordination-session.ts:44Anchor touches after the callback window; notify on exhaustion
6The outbound Vendor Calling agent is outside the sync system. No repo config governs it, tool_ids is empty, and it runs claude-sonnet-5 at temperature 0.3 on an 82k-char prompt while every sibling is sonnet-4-6 at 0.0. Any tool a branch adds can never reach it.specialists/registry.ts:196Add to SPECIALIST_AGENTS + unified sync
7Approve→dial has no org boundary. getUserPropertyScope returns null (= all properties) for org_admin, and the decision route checks only that — the sibling read routes add the org check. CONFIRMED · HELD
Verified 2026-08-01 — confirmed, heldA real authorization gap, with the verbatim guard sequence captured. Sits on the repo's dangerous-diff hard floor (auth/security changes), which no standing grant waives, so it was not shipped overnight. Exposure is bounded: the user table contains no org_admin rows.
vendor-outreach/[id]/decision/route.ts:43Use isPropertyScopeDenied
8Two work orders were dispatched to a vendor archived and removed from AppFolio three days earlier. Nothing on the dispatch path reads archivedAt or pmsStatus. REFUTED 2026-08-01
Verified 2026-08-01 — refutedDispatch has filtered archived vendors since #4889, and the dial lane is covered by #5187. The two cited work orders were test-harness seeds — no product path produced them.
VENDOR#vendor_appfolio_9945Filter archived vendors at dispatch
9Walk-path vendor matching accepts archived companies — 11 of the demo org's 15 roster entries are archived, and the confident tier auto-assigns with no recap callout. The dispatch path filters them; the scope path does not. CONFIRMED · FIXED
Verified 2026-08-01Confirmed and fixed in PR #5216, merged 34775d8f9 — the walk path no longer auto-assigns retired vendors.
resolve-scope-vendors.ts:420.filter(v => !v.archivedAt)
10Clara cannot resolve the vendor phrases her own prompt tells her to send. "the handyman", "a plumber", "Chapa Cleaning" all return no match; since #5114 a no-match suppresses auto-assign, so the work order is created unassigned and Clara reports the property's own crew is not on the vendor list.handle-dispatch-work-order.ts:100In-house synonyms + trade stemming
11The walk path and the dispatch path give opposite answers to the same spoken company name, with inverted safety postures — one auto-assigns "Chapa Cleaning" at 1.00 confidence while the other says it does not exist. PARTIAL · FIXED
Verified 2026-08-01 — partially refutedThe audit's example was fabricated. The underlying walk/dispatch name divergence was real, and is fixed in PR #5216.
resolve-scope-vendors.ts:220One shared matcher
12Five of nine Willows purchase orders have no resolvedVendorCompanyId despite the vendor rows existing, so a vendor citing PO 90591 gets a po_required refusal against a PO sitting in the table, approved, for exactly them. REFUTED 2026-08-01
Verified 2026-08-01 — refutedThe po_required lane is dormant: 0 of 17 properties run in purchase-order mode. Every causal claim in this finding fails.
PO#demo-16Backfill the binding + GSI rows
13Vendor discovery mints memberships in only one org per AppFolio connection. The Willows can reach 4 of 826 vendors, and because the sync takes the "known" branch it can never repair itself — the only fix path is the admin API.lambda/appfolio-sync/handler.ts:984Mint memberships on the known branch too

Identity and routing

#What breaksReferenceFix direction
14Voice recognition has no staff rung — an exact-dial vendor match outranks a verified PM unconditionally. Three sessions dialled numbers carrying an active pm role, including the property's own published office and emergency line. The in-flight staff-before-vendor fix is SMS-only by design. CONFIRMED · UNDERSTATED · FIXED
Verified 2026-08-01 — confirmed, understatedSix sessions, not three. Fixed in PR #5218, merged 77f7bc04b — a PM is no longer greeted as a vendor.
vendor-callback-recognition.ts:50, personalization/route.ts:1108Add a pm/staff rung above exact-dial
15The Conversation is never anchored to the PM's Person, so capturedBy on the move-out condition report — the deposit audit chain — names the wrong person. Today's Willows walk is stamped with a vendor-membership Person.personalization/route.ts:658Thread callerPmPersonId into the Conversation write
16A live resident's spine is shadowed by an empty duplicate. Both her phone and her email resolve to a twin with zero occupancies while the real spine holds two active Willows occupancies. One of six uniqueness-sentinel violations in org_sandbox.claim-selection.ts:77Merge/deprecate the twin
1755% of production Person rows have zero claims. Two Camellia co-tenants minted a ghost Person every 15 minutes for three weeks because the Person row is written before its claims and nothing compensates; both co-signers still have no co-tenant occupancy.person-stamp.ts:956Compensate on mint failure; repair the two dangling sentinels
18Saving a phone in Admin ▸ Users can wipe the claim and return 200. It deprecates the old claim before adding the new one and ignores the result — on any of the 15 blocked partitions the person ends with zero active phone claims and inbound calls land as unknown. ALREADY FIXED
Verified 2026-08-01 — already fixedClosed by PR #5185 before triage began.
upsert-phone-claim.ts:96Mint first, branch on result, 409 on collision
19+17206371205 is split-brain — mapped to The Willows, SMS routes to production Clara, voice routes to a bench ElevenLabs agent. One number, two incompatible agent graphs, one property attribution.phone-lookup.ts:41Repoint or retire the number
20A warm turnover binding overrides an explicit unit label and skips the intake-stage gate, so "unit 204 — carpet is destroyed, charge the tenant" can be appended to unit 101's condition report. This inverts the precedence the module itself documents.resolve-turnover-context.ts:140Honour the explicit identifier before the stamp

Turnovers and work orders

#What breaksReferenceFix direction
21TURNOVER_WORKFLOW_ENABLED is unset in both runtimes — no Willows turnover has ever started the durable workflow; all 20 rows lack a workflowId. Easy to read as "on" because the send arm is armed.handlers/ntv.ts:24Set it, after P0-7's timer fix
22Turnover work orders point at units that do not exist (WLH-12, WLH-17 have no UNIT row) and 28 of 29 unit rows have an empty unitNumber. Two rows disagree on how to derive it from the same id shape — the direct upstream of P0-2.TURNOVER#trn_78537941…Repair the unit mapping and backfill unitNumber
23No Willows unit carries af.unitId, and no sync job stamps it — the only writer is a manual CLI nothing runs. A Willows guest card already sits in production with an empty targetUnitId; a rental application there would be silently skipped.writers/occupancies.ts:334Thread UnitId through rowToRentRollUnit
24childWorkOrderIds is dangling and lossy — six ids point at rows that do not exist, and 49 work orders claim a turnover that does not list them (43 on the demo turnover alone). "What is left on unit 12" returns a wrong, short answer.TURNOVER#trn_78537941…Reconcile both directions; stop appending without dedupe
25Move-out charges on today's demo turnover are stamped terminal_failure — the reconciler has permanently given up and the row is excluded from future sweeps. No automated path will ever file them and no PM surface says so.turnover-wo-reconciler.ts:68Surface terminal charge failures to the PM
26The approve toast claims "work orders sent to the vendor" when external dispatch is gated off at all 17 properties, so no email leaves — and the work tracker still labels those work orders "Dispatched". 26(a) ALREADY FIXED 26(b) FIX BUILT · HELD
Verified 2026-08-01 — split in two(a) The toast was already fixed by PR #5193 and hardened by #5200. (b) The tracker label has a fix built and deliberately left unmerged: PR #5217 is held because status='dispatched' is PMS-owned and a default arm of mapAfStatus, so the built fix would have printed a false "Not sent yet" over live Camellia row CAM-1260. An honest fix must scope to PropFlow-owned rows; the prod census is attached to the PR.
TurnoverDetailClient.tsx:612Report the actual dispatch outcome, not the 201
27Known bug 4 is not fixed. The dedupe still ignores work-order status, so redispatching the same task to the same vendor after a cancel returns "That work order already exists" and creates nothing. The cancel path's status flip is inert because nothing reads it. CONFIRMED · FIXED
Verified 2026-08-01Confirmed and fixed in PR #5212, merged 9e823c35d — the dedupe no longer counts cancelled work orders, so cancelled work can be re-sent.
handle-dispatch-work-order.ts:195Exclude closed/cancelled from the dedupe predicate
28The 1-minute AppFolio mirror wipes any locally-booked visit window. scheduledTimeSlot is forced to null on every merge with no local-ownership guard, so a window booked by Clara or the PM is erased within 60 seconds. Masked today only because the Willows creates are failing. CONFIRMED · CAUSE CORRECTED · FIXED
Verified 2026-08-01 — confirmed, cause correctedThe defect is real but the audit's cause is wrong: it is live for every AppFolio-linked work order including Camellia, not masked by the failing Willows creates. Clobber site work-order.ts:963-964. Fixed in PR #5223, merged 3686117ca. Measured blast radius was latent, not live — 2 exposed rows fleet-wide, 0 at Camellia. The ownership-claim guard is deployed to the sync Lambda and prod-verified: 30/30 schedules intact over ~6 post-deploy passes.
writers/work-order.ts:963Preserve local schedule while the schedule dirty bit is pending
29Two rostered turnovers are permanently un-walkable. The lease-anchor guard refuses start_inspection and fires a founder-paging fatal on each attempt, while the roster keeps offering those units by name. Fired four times in production today.handle-start-inspection.ts:264Gate roster eligibility on inspectability; soft-fail when there is no lease at all
30The turnover-intake voice agent is a dead end. Its prompt promises a transfer back to triage and offers to transfer the caller to a human — it has no transfer tool of any kind, and its four business tools all answer NOT_AUTHORIZED to a non-PM.voice-agents/turnover-intake.ts:29Add transfer_to_agent + transfer_to_number and sync

Messaging, suppression and safety

#What breaksReferenceFix direction
31Texting START restores SMS but not email. The STOP fans a revocation out to the person's email; the START never reverses it, so a tenant who opts back in is email-suppressed forever while the UI shows consent granted. Six consecutive nights of drift in production. CONFIRMED
Verified 2026-08-01 — confirmed, not fixedProd-proven over 6 consecutive nights. Verification also surfaced a fan-out landmine: a phone START would reinstate an email the person had unsubscribed via another channel. Not fixed overnight — suppression-store writes were held for daytime review.
twilio/webhook/route.ts:236Pass organizationId (and normalize) exactly as STOP does
32There is no STOP button. The only fleet-level SMS lever is global — it mutes Camellia and Yale as collateral, is not on the Arms page, and is bypassed by 16+ direct send sites. Its own settings copy claims coverage it does not have. No per-property SMS brake exists.twilio/client.ts:265Register it, move the check into sendSms, correct the copy
33One LLM failure freezes that phone number's whole SMS lane for 6-18 minutes, then the message dies in the DLQ unanswered — no apology ever sent, and Twilio reports delivery throughout. Retries are disabled in production, so a routine 529 is enough.twilio/webhook/route.ts:480Catch around dispatchInbound, canned fallback, ACK
34A photo over ~185KB bypasses the queue and runs the whole agent turn inside a 60-second function with no retry, no DLQ and no alarm — and the 120-second agent abort can never fire. The repo's own ADR bans this pattern after an incident that lost 34 of 35 signals.twilio/webhook/route.ts:486Raise maxDuration; S3-offload attachments so photos stay on the queue
35Voice promises "the work order will be filed after the call" and the post-call refusal is a logInfo — no PM notice, no tenant follow-up, and the forensics row is written ok: true. Fifteen refusals in two weeks, including a flooding toilet and a worsening kitchen leak. Zero work orders have ever been filed from a voice call at The Willows. CONFIRMED · ROOT CAUSE CORRECTED
Verified 2026-08-01 — confirmed, root cause correctedidentityMismatchResponse returns no success field, so result.success is undefined and the sweep takes the log-only branch. The deep fix is the identity spine, which is on Fede's hold list.
voice-postcall-intake.ts:269Escalate the refusal, mark the record failed, notify the PM
36A multi-issue call files only the first issue. The rest are dropped with no log line of any level and no forensics row — unrecoverable even in an audit — while the mid-call tool tells the model the transcript sweep captures every issue.voice-postcall-intake.ts:224Act on every decision, or record the drop
37The anomaly review gate is off for every PM-sender turn — not just the holding message, but the parked draft, the #alerts page and the review record. Monday's demo persona is a PM.conversation-manager.ts:5862Split the condition: suppress only the customer-facing hold copy
38A deduped hold sends nothing and alerts nobody. Second failure of the same cause within an hour: the customer gets silence mid-conversation and no new page fires. Voice already handles this correctly; SMS was never back-ported.anomaly-review-gate.ts:268Emit a distinct second-turn line; re-page after N deduped turns
39Parked guard drafts render as normal Clara messages carrying internal vocabulary ([HELD-FOR-REVIEW …], [GUARD-BLOCKED …]) — 42 live rows in production, two written today — and they replay into the model's context, so Clara re-asserts the claim the guard blocked.conversation-manager.ts:5910Dedicated kind, internal-notice rendering, exclude from context

Auth and release engineering

#What breaksReferenceFix direction
40POST /api/turnovers performs zero authorization — any active session can create a turnover on any property in any org, and one bogus row permanently suppresses the automatic declined-renewal turnover for that unit. The four sibling routes all check. CONFIRMED · HELD
Verified 2026-08-01 — confirmed, heldA real authorization gap, with the verbatim guard sequence captured. Sits on the repo's dangerous-diff hard floor (auth/security changes), which no standing grant waives, so it was not shipped overnight. Exposure is bounded: the user table contains no org_admin rows.
api/turnovers/route.ts:51Add isAccessDenied + isPropertyInOrgScope
41POST /api/simulate/sms is gated by requireUser alone — any active user of any org or role can impersonate any phone number and drive real production side effects: work orders, conversation writes, stopped outreach cadences. ADR-0120 names this route as its proof case. CONFIRMED · HELD
Verified 2026-08-01 — confirmed, heldA real authorization gap, with the verbatim guard sequence captured. Sits on the repo's dangerous-diff hard floor (auth/security changes), which no standing grant waives, so it was not shipped overnight. Exposure is bounded: the user table contains no org_admin rows.
api/simulate/sms/route.ts:61Platform-admin only, or exclude from the production build
42The ElevenLabs sync is push-triggered while every code surface is CI-gated, so the voice config plane publishes ahead of the app on every tools-touching merge — unboundedly whenever main is red. This caused today's split-brain. MITIGATED
Verified 2026-08-01 — mitigated, not closedThe sync remains push-triggered by design. PR #5211, merged f8d176c00, added a 6-hour live drift check across all 11 voice agents, so a split-brain now gets noticed. First run measured 11/11 in sync.
.github/workflows/sync-specialists.yml:57workflow_run: [CI] with a success guard
The remaining P1sEvery P1 not called out above was verified and classified in the same overnight pass: 16 in total were materially overstated or partially refuted; the rest were confirmed as written and are to be addressed later.

Won't fix by Monday — accept or mitigate

Each of these is real and each has a way to survive the demo without pretending it is fixed.


P2 — 65 open

Real, verified, and none of them blocks Monday. Kept here so nothing has to be rediscovered.

Calendar, UI and turnover mechanics (18)
Cancelling or closing a work order never removes its Outlook vendor-visit mirror — 21 phantom visits verified live on the demo calendar.work-orders/[displayId]/route.ts:433
Tour reschedule can silently leave the calendar on the old time — no retry, no DLQ, unlike its vendor sibling on the same helper.sync-tour.ts:360
Move-out inspection events are create-once and never torn down; one unit already carries triplicate holds.schedule-inspection-event.ts:79
Reconnecting the leasing calendar strands every stored event id — updates then page on every reschedule and never re-create.sync-vendor-visit.ts:445
Tenant slot-pick and cancel calendar projection is fire-and-forget inside a handler the repo's own ADR says gets killed.handle-tenant-confirmation.ts:180
The turnovers list "Charges" column is permanently em-dash and its sortable header is a no-op — nothing writes totalEstimatedCost.scope/route.ts:206
Turnover detail poll-error handling is dead code via a stale closure — a persistently failing 5-second poll is invisible.TurnoverDetailClient.tsx:481
An auto_resolved child work order strands a turnover at Turning forever — three modules disagree on whether it counts as done.reconcile-turnover-core.ts:64
The reconciler resolves children by a fleet-wide scan on turnoverId instead of childWorkOrderIds, picking up orphans the reset tool deliberately detached.activities/turnover.ts:1028
Every scope/approve door mints work orders before the stale versioned turnover write; a conflict 409s the PM after vendors were contacted, and the retry duplicates.scope/route.ts:214
Approve is a check-then-act on stage and the SMS and web doors share no claim — two approvals can produce two full sets of work orders.scope/route.ts:51
patchTurnover re-reads inside itself, so its optimistic lock can never fire for the caller and a concurrently-dispatched task is silently dropped.dynamo/turnover.ts:97
POST /api/work-orders links the new work order into any turnover with no scope check, via a stale versioned write.api/work-orders/route.ts:200
start_inspection decides in-flight vs orphaned from turnover.updatedAt — a field every background writer bumps.handle-start-inspection.ts:184
The exact-match branch of unit matching has no ambiguity guard, and four of nine open Willows labels are duplicated.match-occupant-unit.ts:47
append_notes / revise_finding accept any report id in the caller's org — the conversation's pinned turnover is written but never enforced.handle-append-notes.ts:151
The voice walk hands the agent every open turnover id, uncapped and with duplicate labels — the mis-pick surface the SMS roster deliberately refuses.pm-call-context.ts:232
That same list is built with a fleet-wide N+1 read on the ring path of every PM voice call.pm-call-context.ts:200
Vendor, dispatch and purchase orders (11)
"Held" does not hold for external vendors — suppression is conditioned on the vendor being in-house, so a held task still fires the quote fan-out and pages the in-house tech.scope-turnover-work-orders.ts:307
External dispatch has no work-order status filter, so a cleaning-last held work order is emailed at scope time; the later release is a silent status flip that emails nobody.external-dispatch.ts:188
Cross-org contact leak in the scope-path vendor label — another organisation's contact name renders into the turnover timeline.resolve-scope-vendors.ts:354
Unattended redials have no business-hours or weekend gate, though the quiet-hold activity is registered on the same task queue.vendor-redial-workflow.ts:16
Outbound vendor voice calls have no suppression gate at all — the one channel where an opt-out is honoured nowhere.dial-approved-session.ts:426
payload_rejected strands the session at approved forever, and the refusal text instructs the PM to do something the API forbids.dial-approved-session.ts:406
Sessions with no vendor phone are unapprovable by construction, and two vendor test dials reached a Twilio demo greeting — so "successful dial" from that number is not evidence.PROP#appfolio-45/VENDORCALLSESSION#
Seeded demo purchase orders contradict themselves on unit, breaking the vendor+unit single-match fallback the dial depends on.PO#demo-24
The vendor-email recipient allowlist is unset in production and fails open.vendor-dispatch-allowlist.ts:14
The purchase-order sync has no EventBridge rule in production despite being declared in the automations registry, and is wedged by nine demo-seeded rows.lambda/appfolio-sync/deploy.sh:178
The lease_states_history job has never run — rule never created, and absent from the registry so the declared-but-not-reporting detector cannot catch it.lambda/appfolio-sync/deploy.sh:120
Post-call, messaging and suppression (14)
Carrier opt-outs (Twilio 21610) write only the legacy consent row, so the cadence escalates the blocked person to email.twilio/status/route.ts:91
Backfilled suppression revocations carry no person id, so the fan-out never ran — a scope-all opt-out leaves that person's email fully reachable.suppression-gate.ts:234
The transactional exemption is inert on every proactive dispatch — the pre-compose consent gate is category-blind and blocks first.messaging/consent.ts:88
The Twilio adapter never maps a gate-blocked send to skipped, so a blocked send can be recorded as delivered and can release an at-most-once claim.adapters/twilio-sms.ts:247
Dispatcher pre-empt lanes (life-safety, rating, state-first) send with a record reason asserting a persistence that never happened — neither the inbound nor the reply is stored.process-envelope.ts:263
Property→org resolution negative-caches null forever per process — one early inbound poisons that instance into silently 200-dropping all its SMS.twilio/webhook/route.ts:402
The preview inbound event-source mapping reports no batch-item failures, so failed messages are ACKed and deleted — and the deploy script can never repair it.lambda/inbound-processor/deploy.sh:720
A mid-turn LLM throw persists tool rows but skips the turn-ledger save its sibling performs, leaving orphaned tool rows with no inbound anchor before the retry replays.conversation-manager.ts:5555
No alarm can fire for a burst of LLM failures — the handler returns batch-item failures rather than throwing, so the metric the alarm watches never increments; the Vercel lane has no coverage at all.propflow-inbound-processor-prod-errors-burst
The review gate's tool set covers none of the maintenance, vendor or turnover surface — and those handlers never emit a typed fatal, so nothing could arm it there anyway.review-gate-tools.ts:42
No human-visible surface exists for any gate verdict — the "PM review item" is a Sentry message, and /review has no anomaly item type.anomaly-review-gate.ts:198
The handyman page dies at logInfo when a property has no tech phone — 15 of 17 properties — while the sibling consent-skip writes a PM-visible timeline entry.tech-sms-dispatch.ts:800
The emergency ladder logs "SMS sent" and writes an outbound record without checking whether the send returned any message ids.emergency-no-answer-ladder.ts:91
Post-call outcome application decides sync flags and status from a pre-LLM snapshot and writes them over a fresh row — a mid-call cancel can be resurrected to dispatched.postcall-extraction.ts:1073
Data integrity, identity and config drift (22)
"Who took the call" is dead code — the normalizer silently drops spokeWith, and the tests pass because they hand-build the object.postcall-extraction.ts:924
A second problem a vendor mentions is extracted and then discarded — no work order, no timeline, no PM — after Clara promised to pass it along.postcall-extraction.ts:944
211 duplicate display ids at The Willows plus a first-match lookup — a work-order URL can open an archived twin, and edits land on the invisible row.dynamo/maintenance.ts:195
Three units have two active occupancies each, two of them across different people — "who lives in TEST-312" returns two residents and the rent roll double-counts.GSI PROPERTY#appfolio-45
Tenant identity resolution is cross-org and ignores the destination property, while the prospect branch hard-gates on it.identity/resolve.ts:268
Prospect gathering uses the plural person finder for phone and the singular one for email — the email lane drops every prospect not on the trust-cascade winner.dynamo/leasing.ts:347
A person's active prospect is chosen by random UUID order, so a multi-property prospect is invisible at every property but one.dynamo/leasing.ts:317
Person merge deprecates the source claim first and silently drops a reused-deprecated result — a merge can delete a caller's phone from the spine.merge-persons.ts:197
A production claim row carries a verifier outside the union, making the primary-claim comparator return NaN.claim-selection.ts:78
A vendor contact person with zero identity claims, and a vendor membership pointing at a vendor row that does not exist.PERSON#pers_demo_chapa_contact…
Two work orders hold free-form dates the vendor calendar cannot anchor; both are cancelled, but the write path that produced them is live.WO#wo_appfolio_604…
Several turnovers carry conditionReportId as a literal boolean — the same write shape as the known boolean-true bug, on a different field. (No boolean schedule rows survive.)PROP#appfolio-45/TURNOVER#
Turnover mutation routes (PATCH, DELETE, cancel, complete, inspect) drop the org-scope check their four siblings run — and /complete fires a PMS write.api/turnovers/[id]/route.ts:56
No role enforcement on any turnover or work-order API — a read-only seat can approve work orders and dispatch vendors; module flags are UI-only too.middleware.ts:800
Turnover detail loading uses the fail-open pattern its own helper was written to replace, with no org check, and the server page calls it with no fallback.load-turnover-detail.ts:38
The reset tool restarts the workflow straight into the move-out park by default and reports "started fresh" even when the terminate failed._reset-turnover-shared.ts:197
autonomousMaintenanceEnabled=true on the Willows row is a dead flag no code reads — the one switch an operator would reach for during an incident does nothing.PROP#appfolio-45/META
The worker task definition advertises renewals as disarmed while the durable row has them armed — the natural incident check reads the opposite of the truth.propflow-renewal-worker-prod
The Willows carries no capability stage, so every domain is fully live with no shadow valve — and renewals would not honour it even if it were set.computations.ts:79
A live Outlook OAuth access token is stored in plaintext on the property row, readable by any table read.PROP#appfolio-45/META.leasingCalendar
The staff-implicit SMS binding requires exactly one open turnover, so it is dead at both The Willows (14) and Camellia (7) — a photo-only PM text falls out of turnover intake entirely.resolve-turnover-context.ts:220
Vendor sessions and inspection claims aside, nothing ages out a notice_received turnover — the stale set only grows, which is what killed the binding above.cancel-turnover.ts

Method, and what to distrust

134 findings were produced across eleven audit dimensions and each was put through a refutation pass — the reviewer's job was to kill the finding, not confirm it. Severities shown here are the post-verification ratings, not the reporter's claims: roughly 50 of the 134 were downgraded during that pass, one was upgraded, and several had their stated mechanism corrected while the defect survived. Where two findings described the same defect with different severities, the more thoroughly evidenced verdict wins. After de-duplication, 117 distinct defects remain.

Three honest caveats. First, this audit was read-only — nothing here was fixed at publication time (amended: the walker pause, queue cleanup and recipient config landed the same evening; see the amendment block up top). Second, the demo property is isTest and sits in org_sandbox, so a share of the P0 blast radius is configuration that a real property would not have; the code defects underneath are property-independent and several of them (P0-2, P0-3's discard, P1-17, P1-31) reach Camellia today. Third, prod moves: the voice split-brain opened and closed inside this audit's window, and the turnover retry counter climbed from 22 to 54 while it was being written. Re-read the live numbers before acting on them.

Fourth caveat, added by the amendment pass: the "severities shown are post-verification ratings" claim above did not hold for four of the nine published P0s — P0-1 and P0-6 were published over their own real:false verify verdicts, and P0-5/P0-9 over P2 downgrades. When reading the P1/P2 tables below, treat any finding you intend to act on the same way: check its verify verdict first.

Fifth caveat, added by the overnight P1 verification pass: the P1 section reproduced the P0 section's failure mode — 48% of findings did not survive independent verification.

Audit window 2026-07-31, ~13:00–16:30 MT. Repository at origin/main 14d56c5d7; production serving 611f07880.

PropFlow Docs