One code base: re-architecture worklist

Where Western Slope (and other new clients) still run a second implementation of something Camellia's running paths already do, and the order to merge each one back so there is exactly one path, everywhere.

2026-09-21 · Proposed — pending review · Fede's ruling, 2026-09-21: one code base for every client; Camellia's running paths are the reference; a second implementation of a capability (send, receive, connect, renew, book, first contact, write-back) is a defect to merge back — no old path running beside a new one.

On this page

  1. Table A — every capability, one path or two
  2. Five most likely to cause the next miss
  3. Table B — PRs that added, merged, or removed a path (09-15 to 09-21)
  4. Proposed merge-back order
  5. Parking lot
  6. Prospect stage and tours (2026-09-21)
  7. Put Clara's tours on the AppFolio guest card as showings (2026-09-21)
  8. What Western Slope's first live day taught us (2026-09-21)
  9. Guest card = inbound message (decided 2026-09-24)

Table A — every capability, one path or two

Read against origin/main on 2026-09-21. "Corrected" rows were flagged uncertain in the first pass and verified against the live code afterward.

Capability (plain words)One path or twoWhat's different for Western SlopeWhat could go wrong for a customerMerge-back sizeStatus
Connecting a client's mailbox and keeping the login freshTwoWestern Slope's company-wide mailbox has its own connect & refresh code, separate from the original one-mailbox-per-building codeA one-time cleanup for an old, abandoned mail subscription only runs on the per-building path — a company mailbox could carry a stale subscription with nobody sweeping itMediumQueued
Watching for new mail (instant push vs. checking periodically)One, extendedA mailbox that Microsoft won't let us subscribe to (a common shape for a company-wide inbox) now gets its own periodic checker, feeding the same inbound pipeline every other mailbox usesBefore this shipped (09-15/16) that kind of mailbox had no fallback and no "it stopped working" alert; needs a check that the alert exists for it tooSmallShipped; alert parity unverified
Figuring out which building an email belongs toTwoA company-wide mailbox has to match mail to the right building by address or name; a single-building mailbox never has to guessA centralized client with more than one building that isn't in AppFolio has no real matching rule yet — mail could land nowhereSmallQueued (both already feed the same processing step)
Clara's very first email to a brand-new leadTwoThe guest-card "first touch" email still uses its own separate sending code, not the shared sender every follow-up reply usesA fix made to the shared sender (a hold rule, a template fix) silently never reaches a prospect's first emailSmallIn flight — biggest near-term risk
Deciding a brand-new lead should get a reply at allConvergingA new single "a lead arrived" signal now starts the guest-card first touchWhether every other way a lead can arrive (a listing-site email relay, a phone call) is wired onto that same signal is unverifiedMediumIn flight
Stopping the automatic follow-up texts/emails once a prospect repliesOne, shared — correctedNone found — the function that stops the cadence is called from the one shared inbound-email decision code, and both the per-building mailbox and the company-wide mailbox already run through itThe earlier read of this ("unverified whether it works for a company mailbox") does not hold up against the code — it already works todayn/aVerified working, not a gap
Calendar connect and tour bookingOne, layeredA company-wide fallback calendar sits on top of the same per-agent calendar code; not a separate implementationNone foundSmallShipped
Which phone/text line owns a conversationOneSame resolver for every clientNone foundSmallShipped
Writing the outcome back to AppFolio (assign the card, add a note)Two live — correctedA newer, safer writer exists and is already wired in for real, but every company still ships in "watch only" mode by default; the original writer is what actually writes for every real customer todayThe two writers share the same duplicate-prevention id, so turning the new one on for a company can't double-write — but a fix made only to the new writer doesn't reach a real customer until its company setting is flipped, and the two pull requests that retire the old writer aren't merged yetLargeIn flight — second-biggest risk
Company-wide vs. building-specific knowledgeOne schema—Unverified whether a company-wide fact overrides a specific building's fact when both existUnverifiedUnverified
Company-wide settings (which team books tours, who owns a lead, etc.)OneAlready consolidated 2026-09-19/20None foundSmallShipped
Engineer detail
  • Mailbox connect/refresh: agents/clara/lib/email/inbox-client.ts (getValidEmailToken) vs. src/lib/data/dynamo/channel-attachment.ts + src/lib/integrations/microsoft/fresh-channel-token.ts.
  • Subscription vs. poll: lambda/outlook-subscription-renewer/handler.ts (processProperty/processOrgMailbox) plus the new lambda/company-mailbox-poller/handler.ts and src/lib/domain/email/company-mailbox-poller.ts — both hand off to the same deliverInboundEmail seam.
  • Building resolution: src/lib/integrations/email/org-mailbox-inbound.ts (resolveMailboxScope) — both this and the per-property webhook funnel into processInboundEmailMessage in agents/clara/lib/email/process-inbound-message.ts.
  • First email: src/lib/domain/leasing/clara-guest-card-first-email-send.ts still does not import the shared src/lib/domain/leasing/send-clara-prospect-email.ts as of 2026-09-21 — confirmed by reading its imports directly.
  • Cadence stop — verified: maybeSignalProspectReplyByEmail (src/lib/temporal/leasing-reply-bridge.ts:204) is called from decideInboundEmailMessage (agents/clara/lib/email/process-inbound-message.ts:~1954), which processInboundEmailMessage calls for every inbound message regardless of which mailbox delivered it.
  • lead.replied — what it actually is: emitted from src/lib/domain/leasing/clara-guest-card-first-email-send.ts:560 only after Clara's own guest-card first email is delivered. Consumers: propflow-record-consumer.ts (calls onProspectEngaged, which starts the follow-up cadence, not stops it) and pms/side-effects/register-dispatcher.ts (the AppFolio write-back dispatcher). It is not the signal that stops cadence on a prospect's reply — that is the separate maybeSignalProspectReplyByEmail path above.
  • AppFolio write-back — verified: src/lib/domain/pms/side-effects/dispatcher.ts calls resolvePmsSideEffectMode (mode.ts:76) and is registered on the cron drain (src/app/api/cron/lead-received-outbox-drain/route.ts) — it is live, not dark; the stale "NOTHING READS THIS YET" text lives only in the admin route's own comment (src/app/api/admin/companies/[id]/pms-side-effect-mode/route.ts:13). Both the dispatcher's adapter (appfolio-adapter.ts) and the original inline writer (src/lib/domain/leasing/guest-card-write-back.ts / guest-card-write-back-outbox.ts) key off the same door-derived ids (gc:assign:<inquiryId>:<target>, gc:append_note:...), so a redelivery or an early "apply" flip cannot double-write. Absent an explicit company setting, every company still defaults to shadow (compute + log, write nothing) — the inline writer is what writes for real today.

Five most likely to cause the next miss

  1. Guest-card first email still has its own send code. Any fix landed on the shared sender (holds, suppression, templates) doesn't reach a prospect's very first email.
  2. AppFolio write-back has two writers, and the live one is the old one. A property manager could be told "the new safer write-back is handling this" while, for every real customer, the original inline writer is still what's actually writing — because no company has been flipped to the new one, and the two pull requests that would delete the old writer for good aren't merged.
  3. A relayed listing-site guest-card email skips guest-card intake entirely (issue #10487). Clara answers it, but through the general email agent, not the real new-lead loop — so it never fires a new-lead event, never enrolls the follow-up cadence, and never leaves a note on the AppFolio card. A related finding from the same test run is #10486.
  4. No real building-matching rule for a centralized client with more than one building outside AppFolio. Mail could land with no owner.
  5. The new company-wide mailbox poller is unverified for alerting. It closes the "no coverage at all" gap, but nobody has confirmed it pages the same way a stalled per-building subscription does when it silently stops.

Table B — PRs that added, merged, or removed a path (2026-09-15 to 2026-09-21)

Curated from a pass over pull requests merged into main in that window that touch mailbox connect/receive, guest-card intake, first email, follow-up cadence, calendar/tours, phone/SMS, or AppFolio write-back — not every merged pull request in the window, only the ones that changed which of these paths exist. Verdict is based on the diff, not the title.

DateWhat happenedVerdictStill alive on main today
2026-09-15A guest-card mailbox-choice setting (model + writers/backfill) shipped, then both halves were reverted the same dayAdded, then removedNo
2026-09-15–16A periodic mail-checker for company-wide mailboxes that can't get a push subscription (the poller Lambda), landing beside the existing subscription-based checkerAdded a second path, by design — both feed the same intake codeYes
2026-09-18Clara can now write from a company's connected Gmail inbox, alongside the existing Outlook pathAdded a second provider, not a duplicate of the same provider's logicYes
2026-09-18A guest-card-specific email adapter for syndicated guest cards, separate from the shared prospect-email senderAdded a second path beside send-clara-prospect-email.tsYes
2026-09-18–19Guest-card write-back outbox/seam work (note+assign doors, outbox writer) built toward a shared writer, ahead of the AppFolio side-effect adapter programExtended toward one path (not yet finished)Yes (dispatcher side dark by default)
2026-09-20The AppFolio side-effect adapter, its dispatcher, and the company-level shadow/apply setting (agent-loop decoupling program, issue #10344)Added a second AppFolio writer beside the original inline one, dark by default (shadow)Yes, both writers exist
2026-09-20The sync Lambda stopped calling Clara's reply code directly and now drops a queued event insteadMerged two call shapes into one (a producer/consumer split, removing the direct call)Yes
2026-09-21A fix so new-lead events carry the company's own building id instead of one derived from the AppFolio numberExtended existing path (bug fix, not a new one)Yes
Engineer detail — PR numbers
  • Mailbox setting added/reverted: #8641, #8644, #8659, #8671, #8672.
  • Company-mailbox poller: #8647, #8648, #8649, #8664, #8670, #8674, #8756, #8760, #8888.
  • Gmail inbox: #9373, #9376, #9443, #9507, #9518, #9528.
  • Syndicated guest-card email adapter: #9425, #9483, #9647, #9696, #9705, #9737, #9750.
  • Agent-loop decoupling (AppFolio adapter/dispatcher): #10348, #10354, #10361, #10374, #10375, #10382, #10388, #10392, #10423, #10494. Tracking issue #10344, page agent-loop-decoupling-2026-09.html.
  • Sync-to-queue split: #10300, #10317.
  • Open PRs referenced, not re-filed: #10480 (brands guest-card first email), #10501 (creator stamp).

This table is a curated subset, not every pull request touched in the window — the search window returned several hundred merged pull requests across this repository's fleet of sessions; only the ones that changed which implementation of a capability exists are listed.

Proposed merge-back order

Smallest and riskiest first. Each item below is meant to be one pull request, roughly 300 lines, dark (no customer-visible change), safe to ship on its own.

  1. Wire the guest-card first-email send path through the shared send-clara-prospect-email.ts function instead of its own send block.
  2. Add an alert for a company-wide mailbox whose periodic poller stalls, matching what already pages when a per-building subscription stalls.
  3. Land the two queued pull requests (session 010) that delete the original inline AppFolio writer, once the new dispatcher has a clean run in "apply" mode — leaving exactly one writer.
  4. Route the relayed listing-site guest-card email (#10487) into real guest-card intake instead of the general email agent, so it gets a new-lead event, cadence enrollment, and an AppFolio note like every other guest card.
  5. Build the missing building-matching rule for a centralized client with more than one building outside AppFolio.
  6. Finish collapsing the per-building webhook and the company-mailbox resolver into one entry point — they already share the same downstream processing function, this closes the last gap between them.
  7. Confirm (and fix if needed) whether a company-wide knowledge fact overrides a building-specific one at read time.

Parking lot

Re-architecture items noticed during the week that don't yet have a home above. Empty for now.

Prospect stage and tours: what the Prospects page shows (2026-09-21)

Decided — option C (Fede, 2026-09-21 ~1:50 PM MT): AppFolio owns the stages it records; PropFlow fills inquiry and tour when the card only says Active. Building. · Origin: a Western Slope prospect with a tour Clara booked showed as "Inquiry" all day (Slack #tickets-bugs, 2026-09-21).

What we found

Where each stage comes from

Company typeStage sourceTour visibility
AppFolio-owned (e.g. Western Slope)Guest card status only, an activity feed with no tour value (status.ts:18-24,48-58; map in prospects-from-mirror.ts:124-133)None on the list row; tour is stamped on PropFlow's own inquiry record, never joined in (apply-tour-intent.ts:1043-1047)
AppFolio-owned, legacy foldApplied/approved/lease-signed from AppFolio's applications and lease reports get discarded when the row folds onto a card (prospects-merge-native.ts:262-296)Survives only if the row stays standalone (prospects-merge-native.ts:343-357)
PropFlow-ownedOwn stage field, monotonic — never regresses once past Inquiry (statuses/prospects.ts:78-97; sticky writer pms/writers/guest-card.ts:825-830)Tour is part of the same stage progression

Options

OptionWhat changesWhat breaksEffortVerdict
ALeave stage as AppFolio's; add tour as its own fact on the row (next tour date/state), joined in from the tour record the same way other joins already workNothing; the funnel's "upcoming tours" count still won't count Western Slope tours~250 lines, one PRDo this first
BMerge stage by "furthest step reached" everywhereReverses a decision made 2026-09-20; an AppFolio "Inactive" could no longer close a lead PropFlow thinks is further alongSmall diff, large riskNot recommended
COne stage model: AppFolio's card can still set applied/approved/rejected/lease-signed/not-interested; when it just says plain Inquiry, PropFlow's own tour record fills in the tour step (reusing a rule already in place elsewhere)Nothing new; fixes the tour count and the funnel math for AppFolio-owned companiesTwo PRs after A, ~200 lines each, dark by defaultRecommended, after A

Separate initiative: parse the tour events already sitting in AppFolio's card activity log so tours a staff member books by hand show up too, not just ones Clara books.

Put Clara's tours on the AppFolio guest card as showings

Proposed — pending review (2026-09-21) · At Western Slope, a staff member added a showing on a guest card by hand and AppFolio moved that lead into the touring stage on its own. Clara books tours today but never creates the matching AppFolio showing, so the client's own calendar in AppFolio misses every tour Clara books. Fede asked for Clara's tours to show up there and stay current through reschedules and cancellations.

What we found

Tested headlessly against the Willows test property only, on a fictional test card, 2026-09-21. Nothing at Western Slope or Situs was touched.

ActionHow AppFolio does it
CreateThe guest card's "Schedule a showing" form; posts unit, start/end time, assigned staff, guest card id, and notify flags; returns a showing id
RescheduleSame fields, sent as an update to that showing id
Cancel / no-showA status change on that showing id (Scheduled → Completed once the time passes, or No Show / Canceled — Canceled is final and shows a "SHOWING – CANCELED" badge on the card)

Three things shape the design. AppFolio texts or emails the prospect itself when a showing is created unless the notify flags are off — ours must always turn those off, since Clara already confirms and PropFlow is the communication layer. AppFolio does not stop a duplicate; a second identical showing was created with no warning, so we save the returned showing id on our tour and never create a second one. A unit is required, and every time is the property's local time.

Today's gap: our existing AppFolio guest-card write queue already has a "cancel tour" kind, but it only adds a note — nothing creates, moves, or cancels a real showing. We also don't read AppFolio's own Showings report, so we can't see a showing staff add by hand, which is why a tour Clara confirmed at one time and a showing staff added at another time conflicted on 2026-09-21.

Proposed build (small PRs, one queue)

StepWhat it adds
1Showing actions for the AppFolio robot: create, reschedule, cancel — notifications always forced off
2Three new kinds in the existing write queue: schedule showing, reschedule showing, cancel showing; the showing id gets saved on the tour
3Tour booked/rescheduled/cancelled events feed the queue, in a separate process from Clara's reply loop
4Read AppFolio's Showings report to verify our own writes and pick up staff-added showings, so Clara never double-books

Each step extends the one existing guest-card write queue — no second path. Ships off by default as a company setting on the customer page; Fede turns it on per customer.

Open decision for Fede

When staff add a showing by hand for a prospect Clara already booked: (a, recommended) treat the staff showing as the truth — update Clara's tour to match and tell the prospect; (b) keep Clara's time and alert the team to the mismatch; (c) do nothing until step 4 ships.

Willows proof — 2026-09-22

Update 5:30 AM MT, 9/22: the trigger is now registered (#10716) and the live Willows run (harness PR #10738, --live --showings) executed the real path. It stopped at the robot: the app sent the wrong field names (fixed in #10741), and creating a showing needs the AppFolio unit token we do not capture yet (#10742). Reschedule and cancel correctly parked with no showing id. No email or text went to anyone; outbound was suppressed. Status: blocked on #10742, not proven.

Infra confirmed deployed and live · live write proof still pending Fede's go · Everything up to the automatic hand-off is now built, merged, and running in production for Willows: the company setting is on, the write queue and AppFolio writer exist, and the trigger that watches Clara's tours and would fire those writes is registered and enabled. The one thing that has NOT happened is an actual book→reschedule→cancel against the real AppFolio account — that needs Fede's explicit go, already asked, still waiting as of this update.

The setting, in plain English: it's called "AppFolio showing sync" (the code name is Organization.pmsShowingSyncEnabled). On the company's admin page (/admin/companies/<company id>) it's a plain on/off switch for that one company. Read directly from the live database just now: it is ON for the real Willows company only — internal org id org_sandbox (AppFolio property appfolio-45, "The Willows"). Off everywhere else.

ClaimEvidence
The real Willows is org_sandbox / AppFolio property appfolio-45, not a decoy fixtureRead live: the Property row for appfolio-45 ("The Willows") carries organizationId: org_sandbox. A migration script's own comments independently confirm the same fact — appfolio-45 is Willows and its home org is the sandbox org, not the JP&Co/Camellia customer org (org_jpco), which a naive property-name match could have confused it with. org_jpco was checked too and does not have the setting on.
The setting is on for exactly one orgQueried every Organization row live via DynamoDB (7 total): only org_sandbox has pmsShowingSyncEnabled: true. All others (JPCO, Western Slope, Western Slope Mirror, two test companies, PropFlow Staff) read false/unset.
The write queue, AppFolio writer, and browser-robot routes are merged and deployedAll MERGED: propflowai #10665 (company setting), #10673 (schedule/reschedule/cancel_showing outbox kinds), #10675 (showing-sync producer), #10678 (setting + outbox + producer, combined), #10716 (registers the stream trigger); browser-agent repo #411 (the three AppFolio robot routes: create/reschedule/cancel showing, notify flags forced off, readback-verified).
The automatic hand-off (tour booked/rescheduled/cancelled → write queued) is now really wired up, not just code that existsChecked live against AWS, not just the code: the DynamoDB Streams trigger is registered and Enabled — event-source-mapping id 08a805d3-7609-4de9-ad12-e5f63a3d4d39, function propflow-showing-sync-stream-consumer-prod:live, filtered to dynamodb.NewImage.SK prefix TOUR#, on the production table's change stream. This was the missing piece in the previous version of this proof (PR #10716 closed it).
Live write proof (book → reschedule → cancel against real AppFolio) is PENDING Fede's explicit goAsked in #agent-trinity 2026-09-22 ~04:20 Mountain (message: "Need your go: for #10661 ... To prove it actually works I need to BOOK, then RESCHEDULE, then CANCEL a real Showing on the live Willows AppFolio account"). Not yet answered as of this update. No real Showing has been created, rescheduled, or cancelled as of this writing. Nothing in this row was run.
No ZZ Harness prospect exists yet at Willows to run that proof againstChecked live: no prospect named "ZZ Harness" (or containing "harness"/"zz") exists among Willows's 113 prospects. None created today either — the newest Willows prospect rows predate September. A live proof would need a fresh card, not a reused one.
Scope stayed inside Willows only, read-onlyEvery read above touched only org_sandbox / appfolio-45 and the deployed infra's own state. Western Slope and Situs Group were never queried. No write of any kind was made to AppFolio, DynamoDB, or the outbox.

The other builder's test fixtures for this feature (branch fede/guest-card-write-harness, read-only, not touched, informational context only — not something this proof used or will use): three fixtures already exist and are marked not-runnable — gc-harness-showing-schedule, gc-harness-showing-reschedule, gc-harness-showing-cancel in scripts/appfolio-guest-card-harness/fixtures.ts, written to use the exact same outbox kind names this feature shipped with (schedule_showing / reschedule_showing / cancel_showing) precisely so they can flip on later "with a rename/no-op, not a rewrite." Each carries liveDrivable: false and a note that a separate builder session owns this surface — flipping a fixture to real is a matter of that session setting its liveDrivable field to true once it decides to drive a real write; deliberately left to that session, not this proof.

What's left: nothing more to build for the happy path — infra is fully deployed. The only remaining step is Fede's go on the live write test itself (book → reschedule → cancel one real Showing at Willows), after which the same already-on setting means the very next tour booked at Willows shows up as a real Showing with no further switch to flip.

What Western Slope's first live day taught us (2026-09-21)

Proposed — pending review · Western Slope's first real leasing leads went live 2026-09-21. See the checklist this fed on the onboarding playbook: docs.propflowai.co/a/clara-onboarding-playbook?s=next-client-checklist.

One prospect's three days on that first live day, traced end to end with every miss and its root cause: docs.propflowai.co/a/rca-western-slope-ethan-2026-09-21.

Table A — bad assumptions

AssumptionWhat actually happened
A proof on Willows or Camellia proves Western Slope. Western Slope is a different shape: single-family homes, one company mailbox, one company phone line. The copies of the path that only a new client this shape exercises were never run before go-live.
A conversation belongs to a home. Fede's ruling 2026-09-21: a conversation belongs to the person. At a scattered-site company it is held at the portfolio level — every inquiry and every home that person asks about flows into that one conversation, no duplicates.
A setting on the property is enough. Clara's AppFolio user, her email signature details, and the home's time zone are all company facts. Typed per property instead, they were simply missing on 69 homes.
The follow-up writer does not need to know about the home. It invited a prospect to tour a home Clara had already told him was rented, because it has no availability data and only reads the conversation in one stage.
An unknown inbound email can be skipped quietly. A skipped reply from a real prospect raised no alert. Nobody knew it happened until it was found by hand.
A failed write to AppFolio can just park. A parked write has no door back. It needed a hand fix to recover.
If Clara gave a full reply, the team note can be dropped. Clara told a prospect the property team had all his questions, and the system dropped the team note because her reply looked complete. Nobody on the team was told the prospect still needed a person.
The agent's copy of the property reader is the same as the dashboard's. It is a fork with no company filter. An exact address, "566 Placer Street", matched two rows across companies (the real home and a sandbox mirror copy), three tools failed as "ambiguous", and Clara told the prospect she could not find the home.
AppFolio's card status can drive every stage on the Prospects page. AppFolio has no tour status (Pre-qualified, Active, Waitlisted, Application completed, Approved, Denied, Converted). A prospect with a booked tour showed as "Inquiry" for the whole day.

Table B — refactor list

ChangeWhyState
One conversation per person, per company. So a person asking about several homes at a scattered-site company is one thread, not one per home. Decision made
Inventory and diagrams published: docs.propflowai.co/a/agent-loop-decoupling-2026-09?s=conversation-belongs-to-person. Design not started.
Company mailbox replies route to the person's thread. So a reply to Clara's email is never filed under a catch-all property and skipped. In review
Company-level connector settings for Clara's AppFolio user and her email signature. So both are set once for the whole company, not missing on every property that isn't Willows. Merging
Follow-up cadence checks the home's status and Clara's last message before inviting a tour. So a follow-up never invites a tour of a home Clara already said was rented. Merging #10558
The mirror carries the whole AppFolio card; our page renders from it. Today the mirror copies a slice (name, contact, status, inquiry list). Max rent is copied and dropped before the page; bedrooms / unit type are fetched and never saved; notes and the Actions Log (with every showing) are downloaded and discarded. A prospect's page showed dashes for facts AppFolio had. Open
Budget + unit type PR in progress 2026-09-21; the full-card rule is the refactor.
One home resolver, shared by every channel and tool. Listing id first, then exact address, then community name, tolerant of typos and partial addresses, always fenced to the caller's company. Today the agent, the email matcher and the dashboard each have their own lookup; the agent's ignores the company. Open
Company fence on the agent's reader: PR in progress 2026-09-21. The shared resolver is the refactor.
Tour stage is PropFlow's on a PMS-owned prospect row. The PMS owns every stage it tracks; the tour stage is ours because the PMS has none. Open
PR in progress 2026-09-21.
Inbound text retry. An aborted first attempt waited six minutes for the queue timer before retrying. Merged #10546
Alert when a real system-created card or a real prospect reply is skipped. So a skipped reply is never silent. Open
Parked AppFolio writes get a retry door. So a parked write doesn't need a hand fix to recover. Open
A team note is never dropped when Clara's reply says the team has it. So a complete-sounding reply from Clara never causes a real handoff to silently disappear. Open
Assign a guest card to a named staff member, and one-step pause of Clara for one person. So handing a person off to staff is one action, not a manual, multi-step fix. Mechanism being built, dark
Time zone inferred from address at import. So a home never launches with no time zone and wrong quiet hours. Handed to onboarding lane
Tour type setting (self-guided vs. staffed), per home or per company. So Clara and the team never disagree with a prospect about how a tour works. Needs Fede's go

Guest card = inbound message (decided 2026-09-24)

Ruling (Fede): "Think about the guest card arriving as a message. It's just very rich and has more information, but in the core system it is an input message that we're going to reply to. It doesn't matter what the transport mechanism is. We should know what it is because there's more structured information, but it should act the same as other channels." And: "If there's a book tour in the first turn, second turn, doesn't matter. It should be all consistent."

Today: since 2026-09-18 the guest-card first email has run through a separate composer with no tools (about 20 files). Engine behaviors — booking, home anchoring, escalation — were re-created there as hand rules, and some got missed: a tour got said but never booked, and the roster got listed next to the correctly named home. The card was originally routed through the engine (issue #9761); a later change pulled it back out.

After: a guest card becomes an inbound message on the person's thread, channel email, carrying the card's structured facts (property/unit id, listing site, move-in date, requested tour time, inquiry text). The engine takes its normal turn with its normal tools. Kept: the review door (hold and approve) and the AppFolio write-back note. Retired: the composer and its rails. Org derives from the property, never the card — the org stays the wall.

#ConcernProof
1AdapterCard facts land as a normal inbound message on the person's thread
2First-touch prompt overlayEngine turn reads the same structured facts as a first-touch context
3Route the consumer through the engine turnSame review door as today, no bypass
4Booking and home from engine tools; delete hand railsTour-said implies tour-booked; correct home shown, not the roster
5Retire the composerNo-tools composer path removed
6Extend existing harnesses with a four-dimension judge (right home / policies / calendar / escalation) and controlsHarness run with planted-violation controls, green
7Sandbox redrive of every guest-card dataset, end to endFull redrive in the sandbox org, published as the turn-on proof

PR chain tracked on issue #11694, integration branch fede/guest-card-engine.

Acceptance (Fede, same day): "I want a sandbox environment where we redrive all the data sets we have for guest cards and make sure we are just not doing anything dumb and everything works end to end. After that's done, I want to turn it on." Deadline is this afternoon. Turning Western Slope on is Fede's press.

PropFlow Docs