No audio yet. The file at
artifacts/audio/portfolio-architecture-walkthrough.m4a did not load.
Full transcript, and how the audio was made
A medium-length two-host walkthrough of the proposal below, synthesized from the transcript with ElevenLabs' two-speaker dialogue voices (host + guest). Regenerate any time by re-running the render script over this transcript and re-dropping the .m4a.
PropFlow already sells to one kind of customer well: a single building with its own phone number, its own mailbox, its own calendar. Camellia is that shape. But the customers arriving now are not that shape. Western Slope is a company that runs eighteen scattered single-family homes off one number and owns none of them. Situs is a centralized company with its own database. ConAm manages a building it does not own. There is a floating leasing assistant who covers two buildings and belongs to neither. The question this document tries to answer is whether all of those can be the same system with different rows — or whether each new shape means another migration.
Fede set the bar in plain words. Never have to migrate again. Cover single properties, multiple properties, centralized, decentralized, hybrid, people who rotate, people who are assigned, one calendar or many, one line or many. Scale from the setup that exists today. And, quote, “I want proof.” So this is not a whiteboard sketch. It is a recommendation with five tests attached, an adversarial review that tried to break it, and a harness that has to go green.
One thing to say up front, because it governs how you should listen to the rest: almost none of this is decided. Fede and Gera walked it together on the evening of September sixth and took no hard decisions. Exactly one thing is settled, and only for the voice agents: one shared set of agents for every client, never cloned per property, and when a line points at a company, Clara asks which building. Everything else is proposed. Gera is running his own investigation at the code level and owns the implementation — the “how.” This is the “what,” and it gets corrected wherever his investigation shows it is wrong.
The whole idea, in one picture
Here is the entire model. There is a company, which is the customer and the wall between customers — that part already exists and does not change. Underneath it, optionally, a group: a named handful of buildings, a region, a cluster that shares a line. Most customers have zero groups; it exists so that adding a level later is inserting a row, not changing the schema. And underneath that, the building: one property, or one scattered-site home. The building stays the storage partition for its knowledge, its settings, its tours and work orders, because that is the one coupling in the code you cannot relax without physically moving rows.
Now the important move. A phone line, a mailbox, a calendar, the PMS login, a policy value, a vendor roster, an escalation owner — every one of those becomes an attachment on a node in that chain. Same row type at every level. And here is the payoff: the only difference between a single building, a centralized company, and a hybrid operator is which box the phone line is attached to. Camellia has the line on the building. Western Slope has one line on the company above its eighteen homes. The hybrid operator has one line on a group and separate lines on two other buildings. Three customers, one model, three places the line sits.
That is why “centralized” and “single building” stop being modes. They were never really different systems; they were the same system with the line in a different spot. So the property record's operatingMode field — the flag that says what kind of customer this is — gets retired. There is no customer-type flag, no branch per shape, no agent cloned per mode.
Address claims and the one resolver
Two mechanisms make that safe. The first is the address claim. A phone number or a mailbox belongs to exactly one node, and the database enforces that when the row is written — not a convention, a constraint. When a customer leaves, the claim is released and can be taken again. Compare that to today, where two buildings can silently list the same number and the winner depends on load order. That is the kind of bug that texts one customer's resident from another customer's number, and the claim is what makes it impossible rather than unlikely.
The second is the resolver. One function walks building, then group, then company. The nearest explicit value wins. Every answer it returns says which node set the value, who set it, and when. And if it cannot name a company, it refuses — no company, no answer, no reads at all. It costs at most three queries per resolution, with the company's attachments cached for sixty seconds, and it has to fit inside the ring-time budget the voice webhook already lives under. The reason this matters: there are seven different ways settings get resolved in the code today. This replaces them with one, modeled on the one resolver that already works well — the preferred-vendor resolver. So it is not a new invention; it is generalizing the pattern that already earns its keep.
Who runs it, who owns it, who sees it
This is the part every earlier proposal missed, and it is worth slowing down on. The morning's proposals, and all five of the panel designs, put the owner inside the operator's records as a label. That answers “group my buildings by owner.” It does not answer Fede's actual question, which is: a company logged into its own account, seeing a granted slice of a building that a different company runs.
Take Yale 25 Station. JP and Co runs it and owns it. ConAm manages it under contract. Three logins should see three different things: JP and Co's owner sees Camellia and Yale, because it owns both. ConAm sees only Yale, the building it manages. Camellia's on-site team sees only Camellia. The way you get that cleanly is a dated relationship row per fact — “runs,” “owned by,” “managed by” — and then every login's view is derived from those relationships at read time, deny by default. A manager gets the operational view of what it manages and nothing more. An owner gets reports. Anything beyond that is explicit. And because the view is computed from the live relationship rather than stored, it ends the moment the relationship ends.
The honest dependency here, stated plainly: today the dashboard decides what a login sees from a list on the login record, not from staff roles, and a company admin can still see every building on the platform because a July decision to fix that has not shipped. That read-side permission work is a prerequisite, and the rule the plan sets is blunt — no customer's people get a login until it is done.
A call on a company line
This is the one piece that is actually decided, so it is the clearest to walk. Someone dials the shared 970 number. The number's claim names a node. If that node is a building — Camellia's case — the building is known at pickup, and nothing about the call changes; the “which home” question is never even asked. But if the node is a company or a group, the building is a modeled “unknown” state. The resolver walks up for the shared values — hours, policies, calendar — and the same Clara, the same single agent, is told to ask which home. Once the caller names one, it is validated to be inside that company before any tool runs.
Two corrections from the review are baked into that now. First, the voice platform will not accept a tool parameter that is both stamped by the server and typed by the model. So the building is not “demoted” to a proposal the model fills. Instead the bound tools gain an optional “which property” text field the model fills from the conversation, and the server resolves it inside the company or refuses. Second, the resolved company travels with the request as a required argument, so a tool that has no company refuses with zero reads. Email gets the same unknown-building state: a company mailbox message the roster can't narrow asks or escalates — it never scores every building on the platform. And the two dangerous shortcuts today go away: the voice tools trusting a model-filled property id outright, and an unmapped number quietly adopting the caller's own building.
People and their calendars
A calendar belongs to the person, never to the building. That is not an aesthetic choice — it is how Cal.com and Deputy model it in production, and how multi-site healthcare scheduling works. So a centralized company like Situs has three leasing people, each with their own work calendar, assigned at the company and covering all its buildings, and tours rotate across the three. The floating assistant at Yale has her own calendar, is assigned to two buildings, and availability checks her calendar first, then the building's, then the manager's. Assignment is just a row: at one building, at several, or at the company.
The review call sharpened one thing here. Scheduling is code, not the model. Tour assignment across rotating people is modeled like an on-call rotation — a schedule that lives at the company or the building, resolved deterministically — and the model only ever calls one “schedule a tour” interface. It never picks a calendar. That shrinks what has to be evaluated. And one safety rule the review added: a calendar reachable from more than one company — the floating assistant's, when she works at two customers — receives busy blocks only. Today PropFlow writes the prospect's name, phone, income and Clara's notes into the calendar event; the other company's connection would fetch those fields. So the detail stays in PropFlow and the event just links to it. One prerequisite this surfaced: production refuses Google calendars right now — the adapter is half-built and a guard blocks it — and Situs runs on Google Workspace. That is one of the open decisions.
What “proposed” means, and the honest limits
The document is careful about what it is not claiming, and this is the section to actually pay attention to. The single most important limit: knowledge isolation weakens from structural to resolver-enforced. Today, one building cannot read another building's learned answers even by mistake, because the building is the database key and a pinned test asserts against that key. Under inheritance, that guarantee becomes “a function does it correctly.” The company wall stays structural — the anchor carries the company — but sibling-building isolation becomes a rule a resolver enforces. That is the feature you want for a portfolio, and a genuine downgrade for a multi-building customer. It should not be smoothed over, and the plan rewrites the pinned test to assert the walk only ever visits the building's own ancestors.
A few more, quickly, because they are the kind of thing that bites in week one. Failing closed looks like a regression: a number or mailbox that used to guess a winner will now refuse, and the operator has to be warned before the flip or it reads as a break. Two operators on one building is simply refused — if both partners in a joint venture want their own Clara on one address, that is two building records. And the design rides on top of some live bugs it does not pretend to fix: two voice-tool routes with different gates, renewal tools skipping an ownership guard on one route, permission overrides that are platform-global with no company key. Those are named openly as separate fixes.
What the adversarial review changed
Before this went out, eight attackers each took one lens and tried to break the draft — under-built for the onboardings, leaks, migration realism, “is this five proposals stapled together,” voice-platform feasibility, and a lens that threw thirteen future customer shapes at the entity model. Seventy-nine findings. Each went to two independent verifiers told to refute it against the code and default to “not material” when unsure. A finding survived only if both agreed it was real. Twenty survived. And notably, the “customer 50” lens that tested thirteen future shapes produced zero survivors — the entity model itself held.
What the twenty changed is instructive. One false sentence about conversations got corrected: the draft assumed a company-line conversation could live in a special partition every per-building query would still see, and four lenses showed it would actually be invisible on every per-building screen. So conversation birth was pulled earlier in the plan — a conversation now knows its company before any line is bound to one, because that window is exactly where a cross-customer identity pool would form. The review also named a pile of surfaces the draft never mentioned — email building-selection, the calendar write payload, AppFolio credentials, the platform-wide send switches, the read-side permissions — each now its own step. And the estimate roughly doubled, from fifty-three-to-ninety-three engineer-days to a hundred-to-a-hundred-sixty-four, once the review priced what the draft had left out. That doubling is the review working, not the plan failing.
The decisions on the table
None of these are taken. They are laid out as multiple choice, recommendation first, and turning any of it on at a real customer stays Fede's call. What does “company” mean — the recommendation is the operator, the account holder whose Clara answers, with owner and manager as relationship rows. What happens to Yale 25 — leave it under JP and Co, add the “owned by” and “managed by” rows, and change who runs it only if ConAm ever becomes the account holder; nothing moves today. Whether to ship the group tier now — recommendation is yes, ship the support, create zero groups. How taught answers attribute, how default grants work, how scattered homes are modeled — each has a recommended option and two alternatives.
The one with a real external dependency is Situs's calendars, because production refuses Google today. The options are: ask Situs to connect a Microsoft calendar, or finish the half-built Google writer that is sitting behind a guard. The recommendation is to ask Situs first, but price the Google writer in the same week so the answer doesn't wait on us. All of these are mirrored on the onboarding plan as decisions X10 through X17.
Proof: the harness
Fede's line was “if you're gonna recommend one architecture, I want proof,” so the recommendation ends where it should — with a test rig, not a conclusion. There is already a portfolio harness merged to main: a seeded company of eight scattered homes on one line, a second company sharing phone digits, twenty-two cases that are red on today's code. This extends it — same seed writer, same verdict contract, same fixture manifest, so the hash lock and the no-real-names guard cover every new case. Ten seeded portfolios, each modeled on a real shape but with invented names and numbers: the single building, the centralized company, the scattered-site manager, the owner-with-a-third-party-manager, the hybrid, the same human on staff at two customers, four hundred scattered homes, an acquisition, a joint venture.
And every headline assertion has the same shape: it must fail on today's code and pass after the step that claims it. Re-bind a line from a building to its company — new rows plus one claim rewrite, zero rows change partition, zero schema fields added. Camellia replays byte-identical before and after every step, and the shared agent's prompt-and-tool-schema hash is unchanged. An owner's login reads exactly its granted projection and zero other rows, with the negative case as a red test. An unmapped number is refused with zero writes and nobody minted. That is what “never migrate again” looks like when you make it something a machine checks, instead of something a document promises.
Organization architecture — one model for every operating shape
Organization architecture · Organization Core. Names updated September 16 to match the existing ORG# boundary. “Portfolio” means a collection of properties. Earlier recordings and quotations retain their wording; existing links and technical IDs remain stable.
How PropFlow models companies, buildings, owners, managers, people, phone lines, mailboxes, calendars and policies so that a single building, a centralized company, a hybrid, a third-party manager and a floating assistant are all the same system with different rows. Recommendation, evidence, an adversarial review, and the decisions the team has to make.
2026-09-06 · Fable, for the team's architecture discussion · revised after the adversarial review and the Fede/Gera review call (v2, evening) · status: proposed, no decisions taken yet
Recommendation. Keep the company as the customer and the wall between customers. Give every building exactly one company that runs it, optionally through a named group. Turn phone lines, mailboxes, calendars, the PMS login, policies and staff assignments into attachments on a node in that chain, with one function that walks the chain and refuses when it cannot name the company. Add dated relationships for who owns and who manages a building, and derive every login's view from those relationships. "Centralized" and "single building" stop being modes: they are where the phone line is attached.
What is decided and what is not. Decided (Fede, Sep 5), for the voice agents only: one shared set of agents for every client, never cloned per property or per mode, and when a line points at a company Clara asks which building. Everything else on this page is proposed. Fede and Gera reviewed it on the evening of Sep 6 and took no hard decisions. Gera is running his own independent investigation at the code level and owns the implementation, the "how"; this page is the "what," and it will be corrected wherever his investigation shows it is wrong. The morning's earlier proposals and Gera's Sep 1 binding design were inputs to the study, weighted the same as the outside patterns. Decisions are at the bottom and mirrored on the onboarding plan, section 12, as X10–X17.
What the adversarial review changed. The entity model survived every lens, including the one that threw thirteen future customer shapes at it. Twenty findings survived out of seventy-nine; they corrected three sentences that were false about the code, reordered the plan so the conversation record knows its company before any line is bound to a company, added five prerequisites the plan had assumed into existence (a Google calendar writer, a second AppFolio browser runner, the company-wide send switches, the read-side permission work, a settings cleanup), and roughly doubled the estimate. Details in the review section below.
The bar, as tests
Fede, Sep 6: "never have to migrate again"; the pattern has to cover single properties, multiple properties, centralized, decentralized, hybrid, people who rotate, people who are assigned, people who are centralized, one or many calendars, one or many lines; "scale from the current setup"; "I want proof." Written as five tests the design and its harness must pass:
Never migrate again. Onboarding a customer of a new shape is data entry. Adding a building, or moving its line from the building to the company, adds rows and rewrites one claim; no partition changes and no schema field is added.
Reachable from today. Every step ships dark and is a provable no-op for existing customers; Camellia is replayed byte-identical after each step, by a named instrument, not a sentence.
Isolation is structural where it can be, and named where it cannot. The company is resolved once, at the edge, from the address the message arrived on, and unknown fails closed with zero reads. Thread selection, tool arguments and email building-selection are rules a resolver enforces, and the page says so.
No modes. One voice-agent definition. No customer-type flag, no branch per shape. The property record's existing operatingMode field is retired.
Views from relationships. A company that owns sees what it owns; a company that manages sees what it manages; the team at a building sees that building. Same rows, three views.
The model in one picture
Figure 1. Three real customers on one model. Boxes are the chain Clara walks (company, optional group, building). The highlighted chip is the phone line. The only difference between the three customers is which box the line is attached to. Mailboxes, calendars, the PMS login, policies and staff attach the same way.
Company: the customer. Already the wall between customers today, and stays exactly that. Also the operator: whoever's Clara answers and whose policies apply.
Group: an optional named handful of buildings inside one company (a region, a cluster sharing a line, a leasing pod). Most customers have zero. A group carries an axis; only the operations axis is walked, other axes are reporting labels. It exists so that adding a level later is inserting a row, not changing the schema.
Building: one property, or one scattered-site home. Unchanged. It stays the storage partition for its knowledge, settings, tours and work orders, because that is the one coupling in the code that cannot be relaxed without moving rows.
Attachment: a phone line, mailbox, calendar, PMS login, policy value, vendor roster, escalation owner, or capability stage, attached to a node. Same row type at every level. Every attachment records who set it and when, and a company can lock one so buildings below cannot change it. An exception is an attachment with a reason and an expiry date, and an expired exception is treated as absent.
Address claim: one phone number or mailbox belongs to exactly one node, enforced by the database when the row is written, and released when a customer leaves. Today two buildings can silently list the same number and the winner depends on load order.
One resolver: walks building, group, company; nearest explicit value wins; every answer says which node set it, who, and when; no company means no answer and no reads. It costs at most three queries per resolution, with the company's attachments cached for sixty seconds, and it must fit inside the ring-time budget the voice webhook already has. There are seven different resolution shapes in the code today; this replaces them with one, modeled on the one that already works (the preferred-vendor resolver).
Who runs it, who owns it, who sees it
Figure 2. A building has exactly one company that runs it (the chain Clara walks and the identity pool residents belong to). It can also be owned by a company and managed by another. Those are dated relationship rows. Every login's view is derived from them, deny by default: an owner gets reports; a manager gets the operational view of the buildings it manages; a staff member gets their assigned buildings. Yale 25 stays exactly where it is today.
This is the part the morning's proposals and all five panel designs missed. They put the owner inside the operator's records as a label, which answers "group my buildings by owner" but not Fede's question: a company logged into its own account seeing a granted slice of a building that another company runs. Western Slope manages buildings it does not own, Situs owns buildings that Western Slope manages, some owners have a management arm, and ConAm needs to open Yale 25 without seeing Camellia. One dated relationship row per fact covers all of those without moving anything. The view a related company gets is computed from the live relationship at read time, never stored, so it ends when the relationship ends. Default grants: a manager gets the operational view of the buildings it manages and nothing else; an owner gets reports; anything more is explicit.
What this depends on in the code, stated plainly: today the dashboard decides what a login sees from a list on the login record, not from staff roles, and a company admin still sees every building on the platform because the July decision to fix that has not shipped. Those two things are the first half of the "views" step below, and no customer's people get a login before they are done.
A call on a company line
Figure 3. The voice decision Fede already made, expressed on the model. The number's claim names the node. If the node is a building, the call is Camellia's today. If it is a company or a group, the building is a modeled "unknown" state, the same Clara asks, and a named home is validated to be inside the company before any tool runs. Today the voice tools trust a model-filled property id outright and an unmapped number adopts the caller's own building; both go away.
Two corrections from the review are now part of the picture. First, the voice platform will not accept a tool parameter that is both stamped by the server and typed by the model, so the building is not "demoted" to a proposal: the bound tools gain an optional, described "which property" text field the model fills from the conversation, and the server resolves it inside the company or refuses. Second, the check lives where the tools actually read their arguments, on both voice routes and in the leasing tools' own resolver, and the resolved company travels with the request as a required argument so a tool with no company refuses with zero reads. Email gets the same unknown-building state: a company mailbox message the company's roster cannot narrow asks or escalates; it never scores every building on the platform.
People and calendars
Figure 4. A person's calendar belongs to the person, never to the building; the production schemas of Cal.com and Deputy both do it this way, and so does multi-site healthcare scheduling. Assignment is a row: at one building, at several, or at the company. Rotation across calendars is a setting on the tour key. The same human working at two different customers is two person records, one per company, with an admin-only link and calendars that point at the same real calendar.
Per the review call, assignment across those calendars is deterministic: a schedule lives at the company or the building, code picks the host, and the model only calls "schedule a tour." One rule the adversarial review added: a calendar reachable from more than one company (the floating assistant's, when she works at two customers) receives busy blocks only. Today PropFlow writes the prospect's name, phone, email, stated income and Clara's notes into the calendar event, and the other company's connection would fetch those fields. The detail stays in PropFlow and the event links to it. And one prerequisite it surfaced: production refuses Google calendars today, the adapter is half built and a guard blocks it on every availability path. Situs runs on Google Workspace. Decision X17 below asks whether Situs connects a Microsoft calendar or we finish the Google writer.
What the Sep 6 review call added
Fede and Gera walked the recommendation together on the evening of Sep 6 (the raw transcript is in the standup thread). They converged on the same shape in their own words: a graph of nodes with capabilities that attach at any level, one resolver that knows how to walk it, and "a map at the top that tells you where each thing lives, so if you want to change anything it is done once, and you don't migrate." Points from the call that sharpen this page:
The "how" is the bigger risk, and Gera owns it. "The architecture is maybe 30%; the bigger chunk is implementing it so it allows mutation later." The schema must let a building in a hybrid get its own number, or lose it, by changing one attachment, never by restructuring. Gera's independent investigation targets exactly that, and his Claude has this page and the onboarding plan as inputs.
Fields are labeled dynamic or fixed. Not every field may live at the company: "you don't want tenants in companies." Schedules, vendors, phone lines, mailboxes, policies are the dynamic ones. On this page that label is the registry's "where may this be written" rule, one line per key.
A phone number is for one or N buildings. Fede's framing: a number is either associated with one building, in which case "which property" is skipped, or with several, in which case it is not. That is the claim row plus the attachment's node, and the "building known" flag the voice webhook stamps.
Scheduling is code, not the model. Tour assignment across rotating leasing people is modeled like an on-call rotation: a schedule that lives at the company or the building, resolved deterministically. The model calls one "schedule a tour" interface and never chooses a calendar. The calendar layer is decoupled from the Outlook API behind that interface. This replaces the draft's "rotation is a setting on the tour key" with something stricter, and it shrinks the evaluation surface.
Cross-sell is a capability, only where it makes sense. Suggesting a neighboring building is on for clustered portfolios like Situs, off for standalone buildings and for anything 100 miles away. Which buildings count as neighbors is decided in code from configured neighborhoods or distance, never by the model.
Views and permissions, with names. JP&Co's owner sees Camellia and Yale even though it does not run Yale; ConAm sees only what it manages; a regional manager like Kenya sees her two buildings; Hugo at Situs sees Situs's buildings and, because Situs owns part of Western Slope, may see those through ownership; the founders see everything. Roles today are loosely defined and barely implemented. The team also wants tool-level read versus write permissions through Clara, and scoped test accounts (a JP&Co-only login) to verify isolation. The harness Fede is building runs any implementation through those permutations.
Company-line triage on every channel. Before a tour or a work order there is an extra step: which property, or what budget and neighborhood. Fede is prototyping it on the Western Slope line, which today runs on prompt and injected portfolio context and cannot book tours.
Ownership for next week. Fede: customer-facing onboarding and leasing, targeting the first customer's leasing by the end of the week. Gera: maintenance architecture (the call-center replacement) and the organization architecture implementation. Both agreed the organization architecture is what everything else depends on, that every card needs one owner and a definition of done, and that testing starts from fresh test properties rather than the Willows data.
Every shape, as rows
Operating shape
How it is expressed (data only)
Single building, own line and calendar (Camellia)
Line, mailbox, calendar attached to the building. Zero groups. Byte-identical to today.
Centralized company, one number and mailbox (Western Slope, Situs)
Line and mailbox attached to the company. Calendars: one per leasing person with rotation, or one at the company. Both are rows once the calendar step lands.
Hybrid (the LA operator Sean saw)
Some buildings with their own line; a group holding the shared line for the cluster.
People assigned to one building
A staff role at that building, and the dashboard reads roles.
People rotating across buildings
A staff role at the company or a group; their own calendar; rotation on the tour setting.
People centralized (a routing team)
Same, plus the routing team's role at the company.
Floating assistant, two buildings, three calendars (Yale 25)
Roles at both buildings; her calendar on her; building calendar on the building; manager's calendar on the manager. Person first, then place.
Third-party manager reporting by owner (ConAm)
"Owned by" rows to each owner company; the owner's login gets a reports view; reports group by owner.
Owner-operator (Situs)
"Runs" and "owned by" both point at itself. Nothing special.
Owner with a management arm
One company; "managed by" rows from other owners' buildings pointing at it, with the operational view granted.
Manager that owns nothing (Western Slope)
"Runs" for every building; "owned by" rows to the owners, Situs among them. An individual owner is a company record with one person.
Yale 25 today
Runs: JP&Co. Owned by: JP&Co. Managed by: ConAm, with the operational view of Yale only. Nothing moves.
Two companies owned by the same people (Situs, Western Slope)
Two companies, two identity pools, relationship rows for the shared owners.
Joint venture on one building
Two "owned by" rows with shares; one "runs". Two operators on one building is refused; the honest model for that is two building records over one address.
Acquisition
Default: keep both companies and give the acquirer a view through relationships. A true merge re-stamps the company on every row of the acquired company and reconciles two identity pools; it is a supervised, scripted job, not a re-parent.
400 scattered homes
400 buildings (not 400 units of one property), four attachments at the company, groups for towns, a search tool above the size a menu can carry. Needs the dedicated work-order index first: today's lookup throws above 100 buildings per company.
A customer leaves
Claims released, relationships ended (so derived views end), the company's rows retained or exported under a stated retention rule, its people and conversations never visible to the next holder of the number.
What it deletes
The hand-typed phone map and its inverted copy with two hand-pinned overrides; the fallback that texts a customer from another customer's number; the five files that must be edited in lockstep to add a phone number; the property record's unused operatingMode field and its switch; six company-settings fields that nothing reads; the voice path that adopts the caller's own building when a number is unmapped; the email match that takes the first building on the platform whose sender matches; the AppFolio credential lookup that falls back to the requesting user's own login; the never-expiring cache that assumes a building never changes company; the login-record list of assigned buildings (staff roles become the source); the calendar token on the property row (it moves onto the calendar attachment, so one shared calendar stops invalidating its siblings); the umbrella-property workaround and its six documented hacks; six of the seven ways settings are resolved today.
Getting there from today, one dark step at a time
Figure 5. Sixteen steps in two rows plus one parallel lane. Each ships dark, reads move before writes, old fields stay until the shadow window is clean, and each step names its rollback. Estimates are engineer-days and are estimates, derived from the file counts in the code inspection.
#
Step
Proof and rollback
Effort (estimate)
0
Company rows exist for every company id in use (today the self-serve path mints an id with no row), including on the test backend; one writer; the company id becomes required on every building; one property-creation factory (company, ticker, an explicit shadow stage, an escalation owner); the conversation record gains company and scope fields, read by nothing; the platform-wide settings route is restricted to platform admins and stamps who and when.
Not a pure no-op: requiring the company id activates the existing fence on scoped reads, so this is verified against every real property before the flag. Rollback: the requirement flag.
3–5 days
1
Address claims and channel attachments written from today's fields and maps, read by nothing.
A drift test compares the new lookup against the old one for every address in the five lockstep files: zero mismatches is the gate. Rollback: delete rows.
4–7
2
The resolver and the key registry with one key, the vendor roster, run in parallel with today's resolver against its 18 pinned assertions; the read budget (three queries, sixty-second cache) pinned by a test.
Byte-equal on every real company and building. Rollback: a flag.
5–8
3
Inbound and outbound reads switch to claims, per building, behind a new switch keyed by node (the existing switch cannot name a company and already gates something else); the machine-sender suppression list is re-derived in the same change; caches get an expiry; an interim guard refuses any AppFolio login whose database differs from the building's. Writes stay on the old fields.
Per building; Camellia last. Rollback: the switch, because writes have not moved.
6–10
4
The cross-company lookup burn-down: the drift guard's 20 open sites to zero, safe now because the company is known before each lane runs and the gas-leak page is re-anchored to the claim.
The guard's count. Gates texting on Western Slope's line by the guard's own rule.
5–10
5
Conversation birth: the partition comes from a frozen birth anchor, never from the mutable building field; a company-line conversation is born in that company's front door, which is its own record type with one enumeration seam every per-building read goes through, plus an index by building; the cross-company thread fence reads the conversation's own company; a conversation carrying a company can never be stamped into the platform-wide "unrouted" pool.
Harness: a company-line conversation appears on its building's page and in the rollups; exactly one record per conversation after a mid-call bind; zero new rows in the shared sentinels. Rollback: the anchor equals the building for every existing row, so old rows are untouched.
8–12
6
The unknown-building state for voice and email; the resolved company travels into the tools as a required argument; the model's building id is scrubbed to a checked value on both voice routes; the eight bound tools gain the "which property" text field (a live change to the shared agent's tool schema, with its own dark step and a prompt hash in the Camellia proof); email matchers require the company and ask instead of guessing; the voice fail-open path deleted.
Camellia's building is always known; the new path is never taken; the prompt hash is unchanged.
8–12
7
Finish the read-side permission work accepted in July: company admins scoped to their company, the 21 unverified routes to zero, the dashboard reading staff roles instead of the login-record list.
A gate, not a feature: no customer's person gets a login before the count is zero. Today any company admin can read any building's conversations by query string.
6–10
8
Relationship rows ("runs" derived from the tree; Yale gets "owned by" and "managed by"), views derived from them as a third read axis composed as a union at the single permission composition point; the resolver takes a principal.
Harness: an owner's login reads exactly its granted projection and zero other rows; its negative twin as a red case.
5–8
9
AppFolio credentials become an attachment on the company (today one login holds exactly one database, so a shared staff account cannot serve two companies), re-wrapped under the new key; a second browser runner (a purchase, Fede's go); the account id threaded through the writer so the browser stops being pinned to one database.
Before Situs's first AppFolio write. Harness needs a real credential path; marked.
5–9
10
Settings keys move to the resolver one at a time, each with "absent" set to today's hardcoded default, after a sweep deletes stored per-building values that equal the default (the form today writes every default as an explicit override) and the form is changed to send only what changed; the five company-wide send switches become company keys with a deny-only platform floor; an explicit "live" written for every building before the capability stage learns to inherit; renewals wired to the stage.
Each key a no-op by construction, proven by saving each fixture's settings page once through the real route and asserting a company value still wins.
12–20, spread
11
Calendars: the token moves onto the calendar attachment; the calendar id becomes data instead of "the mailbox's default"; the tour slot carries the assigned host; availability unions the person's, the building's and the manager's calendars; busy-only across companies. Plus the Google writer if X17 says build it.
Harness: N consecutive tours land on N different hosts' calendars. Google writer unpriced until X17.
6–10
12
Staff roles may point at a building or a group, and the inbound staff fence compares the destination, not the person's home company; the group tier; person calendars with rotation modes.
Zero group rows for today's customers.
5–8
13
Knowledge by level: a company-level knowledge record; structured facts inherit only for the fields where an inherited value is true of the building (audited one by one; the code already refuses a fleet default for one); free-form sections shown at both levels, never merged; the isolation test rewritten to assert the walk visits only the building's own ancestors, and kept pinned.
See the honest limit on knowledge below.
5–8
14
Long-running workflows snapshot the resolved scope at start and re-resolve only at declared checkpoints; a test forbids workflows from calling the resolver directly.
Harness: a workflow started before the step completes after it with the same outcome.
2–4
15
Deletions; per-company ticker uniqueness together with the company-scoped work-order lookup on a dedicated index (never separately, and required before any company passes 100 buildings); offboarding paths: claim release, relationship end dates, a departing-customer rule.
Deletion list above.
5–8
UI
In parallel from step 2: the connections screen ("who does this answer for?"), the settings page by module with a scope switcher and the always-visible "why is this the value, set by whom, when" line and lock icon, the team screen, and the go-live checklist that is the resolver rendered.
A capability shown ready that the resolver would refuse is a failing test.
10–15
Total estimate 100–164 engineer-days, up from the draft's 53–93 after the review priced what the draft had left out. Steps 0–2 (12–20 days) run alongside the three onboardings. Google calendar writer and the browser runner are outside the total.
What each customer gets, and when
Western Slope. Its shared voice line runs correctly after steps 0–6; texting on that line also needs step 4, because the drift guard keeps texting off while any open cross-company lookup remains. Until then the line runs on the reverted prototype, which is the state Jay's team has today. Multi-database AppFolio (step 9) before any write into their database.
Situs. Before any Situs person logs in: step 7. Before the first AppFolio write: step 9. Their three leasing calendars: step 11 and decision X17, because production refuses Google calendars today.
Yale 25 and ConAm. Nothing moves. ConAm's view of Yale: steps 7 and 8. The floating assistant's three calendars: step 11.
Camellia. Byte-identical at every step, flipped last, and the control fixture in the harness.
Honest limits
Knowledge isolation weakens from structural to resolver-enforced. Today "a read cannot address another building's learned answers even by mistake" is a database guarantee: the building is the key, and a pinned test asserts against the key. Under inheritance it becomes "a function does it correctly." The company wall stays structural, because the anchor carries the company; sibling-building isolation becomes a rule a resolver enforces. That is the feature for a portfolio and a real downgrade for a multi-building customer. The pinned test is rewritten to assert the walk visits only the building's own ancestors, and it stays pinned.
Two operators on one building is refused. If both partners of a joint venture want their own Clara on one address, that is two building records.
Changing who runs a building across companies (Yale moving under a ConAm account; two companies merging) re-stamps the company on every row involved and forks the identity pool. The model makes it scripted and reversible; it does not make it free.
Rows already sitting in today's shared sentinels (conversations with no building, people minted into the platform-wide unrouted pool) are not fixed by this. The new rule is "no new sentinel rows," not "no sentinel rows."
The bugs it rides on and does not fix. Two live voice-tool routes with different gates; renewal commit tools skipping their ownership guard on the per-tool route; permission overrides that are platform-global with no company key, so one flip changes every customer. A guard installed on one route is not installed on the other. These are separate fixes and this design does not disguise them.
Failing closed looks like a regression in week one. A number or mailbox that used to guess a winner will refuse. The operator has to be told before the flip.
Travel time between scattered homes exists nowhere today and is not solved here.
Compliance floors have a home and no contents. The registry gives jurisdiction rules a place to live; the jurisdiction table does not exist, and fair-housing is one hardcoded constant that assumes every property is in one state.
The oldest hot key is not the new one. The single index value that lists every property of every customer is read on the ring path today; the front-door partitions are smaller than that at any scale we will see soon.
Adversarial review
Method. Eight attackers, each with one lens, tried to break the draft: under-built for the three onboardings; customer 5, 10 and 50; leaks; migration and rollback realism; "is this five proposals stapled together"; voice-platform and DynamoDB feasibility; the operator's screens; exact fit to Fede's owner-versus-operator and Situs statements. They produced 79 findings. Each finding went to two independent verifiers instructed to refute it against the code and to default to "not material" when unsure. A finding survived only if both said it was real and material.
Outcome
Count
Findings raised
79
Survived both verifiers
20
Refuted by both
20
Refuted by one, conceded by the other (judgment calls, recorded)
39
Survivors from the "customer 50" lens, which tested thirteen future shapes against the entity model
0
Four root causes behind the twenty.
One false sentence about conversations. The draft said a company-line conversation could live in a special partition that every per-building query would still see. Four lenses independently showed the code enumerates buildings from a property index, so those conversations would be invisible on every per-building screen, the cross-company thread fence would go inert on them, and "never moves" collided with how the partition key is derived. Fixed: the partition comes from a frozen birth anchor; the front door is its own record type with one enumeration seam every per-building read uses; the fence reads the conversation's own company.
Step order. The draft shipped the company-line voice state before the conversation record knew its company, and four lenses found that window is where a cross-customer identity pool forms. Fixed: conversation birth is now step 5, ahead of the voice work, and "Western Slope needs steps 3–4" became "0–6, plus 4 for texting."
The tool boundary. The draft's "one shared guard" was on neither the leasing tools' path nor the email lane, and a check on a proposal is blind when nothing is proposed. Fixed: the company is a required argument that travels; the model's id is only ever a narrowing; the scrub is on both voice routes; the bound tools gain a "which property" text field because the voice platform forbids a parameter that is both server-stamped and model-typed.
Surfaces the draft never named. Email building-selection, the shared calendar's write payload, AppFolio credentials and the browser runner, the live platform-wide send switches, settings defaults stored as explicit overrides, the calendar token, and the read-side permission layer. Each is a live mechanism with a customer in flight attached to it. Each is now a step.
What no lens examined, found by the completeness critique, and now on the page: the knowledge-isolation downgrade (above, verbatim as all three judges asked); a step, a rule and a proof for taught answers, including on a company-line conversation with no building; the resolver's read budget and the ring-path latency it must fit; offboarding, which was absent from every document; who set a value and when; the full count of per-building configuration rows the settings step must port (8 record types, about 150 fields, 33 setter scripts).
Claims corrected. The draft's "88 fan-outs" is now stated as "dozens, depending on how you count" because seven verifiers got seven counts; "five fail-opens deleted" and the deletion list now agree; "inserts only" became "inserts plus one claim rewrite"; "steps 0–2 touch no live path" was struck because requiring the company id activates the existing fence; the estimate roughly doubled.
The full review, the eight attack reports, the refuted list with reasons, and the five proposals and three judgments it drew on are in the working directory for this study.
Decisions to make
None taken yet (Fede, Sep 6 evening). For the team, multiple choice, recommendation first; turning any of it on at a customer stays Fede's call. Mirrored on the onboarding plan, section 12, as X10–X17. Two earlier plan decisions already cover part of this: X5 (the same human at two customers: two records plus an admin-only link, recommended) and X9 (what Clara learns may cross homes: company facts shared, building facts isolated, recommended).
#
Decision
Options
X10
What "company" means
(A) The operator, the account holder whose Clara answers; owner and manager are relationship rows — recommended · (B) the owner · (C) both privileged
X11
Yale 25 now
(A) Leave it under JP&Co; add "owned by JP&Co" and "managed by ConAm" with the operational view of Yale only; change who runs it only if ConAm becomes the account holder — recommended · (B) move it under a ConAm company now · (C) two records
X12
Conversations on a company line
(A) Born in the company's front door with a frozen anchor, gain the home as a field plus an index, never move — recommended · (B) move partitions when the home is named · (C) umbrella property
X13
The group tier
(A) Ship the scope variant and resolver support now, create zero groups — recommended · (B) defer the tier · (C) create groups for Situs at onboarding
X14
Taught answers
(A) A staff reply teaches the building it answered; on a company-line conversation with no building yet, it teaches the company; one click promotes or demotes — recommended · (B) teaches the level of the line it arrived on · (C) always the company
X15
Default grants
(A) "Managed by" grants the operational view of that building only; "owned by" grants reports only; anything more is explicit; grants are computed from the live relationship, never stored — recommended · (B) owners see everything · (C) per-relationship configuration from day one
X16
Scattered homes
(A) One building record per home, with the dedicated work-order index built first — recommended · (B) one umbrella property with homes as units
X17
Situs's calendars (production refuses Google today)
(A) Ask Situs to connect a Microsoft calendar for tours · (B) finish the Google calendar writer (unpriced; it is the half-built adapter behind a guard) · Recommended: ask Situs first, and price B in the same week so the answer does not wait on us
Superseded or restated: the plan's "where a shared line binds the property" (its option A is this design); X6 owner grouping (superseded: an owner is a company with a relationship, not a group); X7 the portfolio test line purchase (unchanged, Fede's go).
Proof: the harness
"If you're gonna recommend one architecture, I want proof." The Sep 6 portfolio harness (a seeded company of eight scattered homes on one line, mailbox and calendar, a second company sharing phone digits, 22 cases red on today's code, merged to main on Sep 6) is extended, not replaced, using its seed writer, its case verdict contract and its fixture manifest so the hash lock and the no-real-names guard cover every new case. Ten seeded portfolios, each modeled on a named real shape, with invented names, numbers and addresses:
A single building with its own line and calendar (Camellia's shape): the byte-identity control.
A centralized company of 14 buildings, central office, three leasing people rotating with individual work calendars, an admin team, its own PMS database (Situs's shape).
A scattered single-family manager, about 18 homes as 18 buildings, one number, one mailbox, one calendar, owning nothing, with owner companies including one that is itself a customer with its own PMS database whose ids overlap (Western Slope's shape).
An owner company with one building it runs itself and one it owns but a third party manages, with the manager granted a view and a floating assistant with three calendars (JP&Co and ConAm's shape).
A hybrid: a cluster sharing one line and mailbox through a group, two buildings with their own (the LA operator).
A third-party manager with three owners, reporting by owner, with a per-building "what may be pooled" setting from the management agreement.
The same human on staff at two customers.
400 scattered homes.
An acquisition kept as two companies with a roll-up view; and a true merge as a red case for the supervised job.
A joint venture: two owners with shares, one operator.
Headline assertions, each of which must fail on today's code and pass after the step that claims it:
Re-bind a line from a building to its company: new rows plus one claim rewrite, zero rows change partition, zero schema fields added.
Camellia's fixture replays byte-identical before and after every step, and the shared voice agent's prompt and tool-schema hash are unchanged.
Every inbound for one portfolio performs zero reads in another portfolio's rows, and the conversation it selects belongs to the resolved company.
A company-line call writes zero rows into the shared "unassigned" partition and mints zero people into the platform-wide unrouted pool; a caller whose number reaches two customers' company lines resolves to two distinct people.
A company-line conversation appears on its building's page and in the rollups; exactly one record per conversation after a mid-call bind.
An owner's login reads exactly the granted projection of the other company and zero person, conversation, knowledge or settings rows; the negative twin is a red case.
An unmapped number or mailbox is refused with zero writes and no person minted; a company-mailbox message the roster cannot narrow asks instead of guessing.
Every fixture line resolves to the same agent id; only the injected data differs.
Each resolution issues at most three queries; the company cache has an expiry, pinned by a test.
A workflow started before a step completes after it with the same outcome.
A second claim on an address is refused by the database; a released claim can be re-taken.
Every effective value carries the node that set it, who, and when.
Save each fixture's settings page once through the real route and assert a company value still wins.
N consecutive tours on a rotating team land on N different hosts' calendars; a foreign company's calendar shows busy blocks only, on the write side.
Every AppFolio read and write for one portfolio resolves to that portfolio's database, including the browser runner (needs a real credential path; marked).
Seeding a portfolio calls zero setter scripts and edits zero source files (true for invented lines; a real line still needs the number-to-agent file).
Robot calls on the six written scenarios plus three company-line scenarios, once a portfolio test line exists (a purchase, Fede's go).
Where this came from
Seven research streams, read in full: the team's Slack transcripts back to May (including the Situs whiteboard session, the Western Slope design-partner call, the ConAm onboarding threads, and every link Gera and Sean shared, among them Gera's Sep 1 audio overview); every internal document including Gera's Sep 1 "org tier as built" design and his "why folders break multi-tenant software" brief; a read-only inspection of the product code at the current main; outside research on multi-tenant hierarchies and settings inheritance (Google Cloud, AWS Organizations, Azure, Kubernetes, Slack Enterprise Grid, Stripe, Zendesk, GitHub, Auth0), on party, role and contact-mechanism data models (Fowler's Analysis Patterns read from the original text, Silverston, Hay, Cal.com's and Deputy's production schemas, hotel chains, multi-site healthcare), on how property-management and multi-location products model companies, owners, groups, lines and calendars (AppFolio, Yardi, RealPage, Entrata, Buildium, Propertyware, Rent Manager, Funnel, Knock, ShowMojo, RingCentral, Aircall), and 29 podcasts, talks and interviews.
Then five independent architects each designed from a different starting point (established data-model patterns; the code as it is; cloud resource hierarchies; Gera's designs taken to completion; the operator's screens), three judges scored them from three lenses (the engineer who ships it in four weeks, the architect at customer 50, the non-technical cofounder and the operator), and this recommendation was synthesized from all five, taking the skeleton from the cloud-hierarchy design, the step ladder and shadow-diff gate from the code-first design, the relationships from the data-pattern design, the grouping and claim mechanics from Gera's, and the settings screens from the operator-first one. Two gaps all five missed were added: the owner's cross-company view, and long-running workflows under a scope that can change. The adversarial round above then attacked the result, and this is the revised version.
Worth the team's time, ranked by the research
Slack, "Changing the Model: Why and How We Re-Architected Slack" (QCon SF 2024). A company that bolted an org layer onto a single-workspace model and paid for years: about 300 settings resolved case by case. The cautionary tale for building the company tier now.
Full research, proposals, judgments and attack reports are in the working directory for this study (seven research reports, five proposals, three judgments, eight attacks, the verified review); the harness lives in the product repo under the portfolio harness.