Today every building belongs to exactly one company, everywhere in the system. The founders said a building can belong to two. This page lays out the four honest ways to read that, recommends one, and turns the eight questions that ride on it into small multiple-choice picks.
2026-09-23 · decided, except question 6 (see the section just below) · code facts checked against the main branch that morning · roles only, no company names · answer on the decision link the Driver raises, not here
The shape is A′. One company keeps the building's file; the co-managing company's staff work the building through a written co-manager arrangement, and the building also shows on the co-managing company's own list. Seven of the eight questions below are settled; one is still with the founder.
| Question | Outcome | Decided by |
|---|---|---|
| Shape | A′ — one file, a co-manager arrangement, and the building on both companies' lists. | decider · f7f6ab71d |
| 1 Record owner | The company the building was imported from and is billed to. | decider · f358b31c1 |
| 2 Who sets policy | The company doing the day-to-day managing sets the rules — pet fee, quiet hours, escalation. | founder · block b90181521 |
| 3 Acceptance step | Superseded by the founder: no acceptance step for now. A co-manager arrangement is live as soon as it is written, and only staff / engineers write it. There is no customer-facing invite-and-accept flow yet. The decider's "nothing until accepted" ruling (f6e2f14ad) no longer applies. | founder, 2026-09-23 in chat |
| 4 Phone number | One number per building, held by the record owner; the co-manager's staff are reached through it. | decider · fd94c4a24 |
| 5 Quiet hours and defaults | The building's own settings, in the record owner's settings chain. | decider · fbbb876c2 |
| 6 Co-manager expiry | still open With the founder. Recommendation stands: never expires; either company ends it. | block b90181535 |
| 7 Co-manager's admins | They see the shared building, marked as managed for another company — never the record owner's other buildings. | decider · f6f0a1a18 |
| 8 Sep 10 vs Sep 23 | Sep 23 stands: the building belongs to both. Transfer (Option B) stays available for a building that really changes hands. | decider · f536efca5 |
The founder, in his own words on question 2: "the company doing the day-to-day managing sets the rules." On question 3: they do not have to say yes — for now the engineers set it. And at the shared building in question, the co-managing company runs it day to day, so it is the one that sets that building's rules, not the company holding the original property-management-system record.
How the arrangement gets written, for now: a staff-only writer, the same template as other per-building settings — a dedicated writer script, and the building behaves as unshared when no arrangement is written. Company names live only in the data written at runtime, never in the code.
Decided: A′ (was the recommendation) — one company keeps the building's file; the other company is written in as its co-manager, and the building shows up on both companies' lists. Every building that is not shared behaves exactly as it does today. The one thing the system refuses to do is give a building two files.
A building belongs to one company, and that one fact is enforced in three places at once: the building's record can only hold one company; each company's building list is a shelf that a building can sit on only once; and the database refuses to save a building under a second company. On top of that, about ninety places in the code ask "is this building in my company?" with a plain equals sign, every message and call from the building is filed under that one company, and a phone number or calendar can be claimed by one company only.
So today, if the co-managing company's staff log in, they cannot see the building at all. And if the co-managing company's own AppFolio database also lists the building, importing it does not fail — it quietly creates a second building record, with no link to the first. Neither outcome is "belongs to both".
| Where | What it does | Effect on a shared building |
|---|---|---|
Property.organizationId: string (types.ts, pinned by property-org-required.drift.test.ts) | the type holds one company | cannot express a second one |
proporg-index: PROPORG#<org> / PROP#<id> (proporg-gsi.ts) | the roster is one attribute pair on the building's row | the co-managing company's list never returns it |
write guard attribute_not_exists(organizationId) OR organizationId = :orgId → CrossOrgIdentityError | refuses a save under another company | a PMS import from the other company's account is a different key, so it creates a second record instead |
isPropertyInOrgScope (53 files), getOrgScopedPropertyIds (28), scopeByOrg (10) — all prop.organizationId === orgScope | route admission for reads and writes | a caller scoped to the co-managing company gets 404 on every building route |
getOrgIdForProperty (20 callers: SMS/voice webhooks, inbound router, identity, maintenance) | stamps every inbound person, thread, work order and consent to the building's company | everything files under the record owner; correct under A/A′, wrong under C |
phone-line and calendar coverage conditions organizationId = :runner; address head organizationId = :org AND #scope = :scope | one company per phone number / mailbox / calendar | the co-managing company's own line can never say it serves the building |
operatorOrgOfBuilding(b) returns b.organizationId (addressed-write.ts) | policy authority = the paying company | the inversion the D-0909-1 ruling named: the manager is refused at write |
buildScopePath → [PROP#, GROUP#…, ORG#<owner>] | the settings chain ends at one company | the co-managing company's defaults never reach the building |
computeScopeOf throws on many-orgs; the union lanes fold one leg per company | dashboards add companies up | a building in two legs double-counts unless unions dedupe by building id |
Counts are non-test files, grep over an export of main; the positive control (getUserOrgScope, 100 files) was found by the same method.
REL_ROW_KINDS = ['owned_by','managed_by','in_group','represents','transferred_to'] (rel-rows.ts). A managed_by row is PROP#<bldg>/REL#managed_by#ORG#<co-manager> and already stamps GSI1PK = REL_PARTY#managed_by#ORG#<co-manager> — so "the buildings this company manages for others" is one query on an index the rows already carry. Nothing reads it today.putRelationship / endRelationship (put-relationship.ts) is merged with zero production callers — the two other files that mention it do so in comments only. operator-authority-seam.drift.test.ts fails the first time a src/ file calls it; its stated exit is "answer #7843, then make operatorOrgOfBuilding read the accepted row".PersonRole.actingForParty and ActorView.actingOrg exist: the co-manager's staff are members of the record owner's company, scoped to the one building, labelled with the company they act for.is-live.ts deliberately does not decide whether an unaccepted row grants anything — "a founder's, not this file's" (#7843).grantClockVerdict(validUntil) (grants.ts) already reads a row's own expiry; a row with no clock never lapses.f7f6ab71d.Four readings, one card each. For each: what a property manager actually sees, what changes underneath, roughly how big it is, and how you get back out.
PROPORG#<co-manager> ∪ live REL_PARTY#managed_by#ORG#<co-manager>, deduped by building id. The read leg for that building is still minted as {organizationId: owner, actingOrg: co-manager} — one company context per leg, so nothing is written into the co-manager's partition.isPropertyInOrgScope / canCallerViewProperty through one shared predicate that accepts "on my shelf, or I hold a live accepted co-manager row". Do not add the clause at 53 sites.bug-roster-freshness-has-no-request-path-check shape).Each one is its own small pick. Choosing A′ above answers the shape; these decide how it behaves. The recommendation on each is the one that keeps unshared buildings byte-identical and adds nothing the current rulings contradict.
f358b31c1.One company's file has to hold the building — its residents, their threads, its phone number, its bill. Which one?
Recommend (a). The company whose PMS account the building is imported from, and who pays; a co-manager row for the other. A transfer later is Option B, deliberately.
The record owner is Property.organizationId: the storage partition, the identity pool (ensurePersonByClaim is per company), the line's assistant, and the PMS credential holder (ownerId → PMSCRED#). Import already keys the building per PMS account (appfolio-<account>-property-<n>), which is why (a) is the shape that exists.
b90181521. With no acceptance step (question 3), the arrangement takes effect once staff write it.Pet fee, quiet hours, who gets escalated. Today the paying company wins, and a manager from the other company is refused when they try to change a rule. An earlier ruling said that is backwards.
Recommend (b). It is already the ruling; the code just cannot ask the question yet because no row can exist. Answering question 3 is what lets it.
operatorOrgOfBuilding returns building.organizationId today and its own comment says that is a coincidence, not the rule. The seam drift test's exit condition is this change: read the accepted managed_by row with an operational grant; fall back to the record owner when there is none. Option (c) is the grants:['operational:maintenance'] shape from the topology grid (F18) — functions delegated, never split across walls.
f6e2f14ad, no longer applies. The options below are kept as the record of what was asked.The record owner writes "company X co-manages this building". Until company X says yes, can company X's people see or change anything?
Recommend (a). This is question #7843, the one the seam test is waiting on. Answering it unblocks the first real caller of the writer.
is-live.ts is purely temporal and says so; acceptance is a separate check the caller asks for. Under (a), "live" for admission and authority means isLive(row) && row.acceptedAt. The row's acceptedAt is optional on managed_by today; (a) makes it required for any grant to take effect, not for the row to exist.
fd94c4a24.Residents know one number for the building. Today one company holds it. Does the co-managing company get its own number for the same building, share the one number, or take it over?
Recommend (a). Whether the co-managing company's own company line may later say "I also serve this building" is a separate, later coverage question — not part of this raise.
Address heads are one company per number (organizationId = :org AND #scope = :scope); phone-line and calendar coverage require organizationId = :runner on the building. (a) touches none of that. (b) is the "two assistants over one address" shape that the design refuses. The later question — a co-manager's shared line naming the building — is the coverage rows (p3-coverage-r2, p3-head).
fbbb876c2. Read with question 2: the day-to-day manager is who edits those building settings.Both companies have their own house rules — when texting stops at night, who gets escalated. A resident of the shared building can hear both companies. Which rulebook applies?
Recommend (a). The building has one rulebook. Who may edit it is question 2; whose company-wide defaults fill the blanks stays the record owner's.
buildScopePath is [PROP#, GROUP#…, ORG#<owner>]. The design already says a walk under an actingOrg context never reads the co-manager's org-level rows (vendor roster, escalation owner), and keys carry visibleToGrantee default false. (a) keeps that as is.
b90181535. The recommendation below stands.Outside people given access to a company's buildings lose it after ninety days unless renewed; a customer's own administrators never do. Is a co-managing company an outsider or a customer?
Recommend (a). Consistent with the narrowed ruling on outside-party expiry (b89404898): outside parties expire, a customer's own administrator never does. A co-manager is on the customer side of that line.
The managed_by row carries an optional validUntil; grantClockVerdict returns no_clock when absent. (a) = write no clock on the row; the co-manager's staff roles keep whatever clock the invite flow gives them (the D-0914 default for outside individuals).
f6f0a1a18.The founders said "organization admins can see all their organization". Is a building the company only co-manages part of "their organization"?
Recommend (a). This is the reconciliation the admin-visibility row has been blocked on: "all their organization" includes buildings held on a live, accepted co-manager row, and nothing else of the other company's.
The organization-administrator stopgap in property-assignment.ts returns the org's own shelf. Under (a) it returns the A′ union for that company; the record owner's other buildings are on a different shelf and never enter it. This is the p7-admin-flip row, blocked on b90054565.
f536efca5.Two weeks ago the standup answer for this building was "just move it to the other company". This week the answer was "it belongs to both". They are different shapes, and the code needs one.
Recommend (a). If it later turns out one company should hold the file, (c) is reachable then: transfer, then co-manage. Doing it in that order now costs a re-consent that "both" does not need.
These rows are waiting on the ruling. None of them needs a new row; the shape decides which one moves first.
| Board row | What the ruling settles for it | Answered by |
|---|---|---|
p7-relationships | the first production caller of the co-manager writer, with the seam pin moved from "zero callers" to "these callers" in the same PR | shape + Q3 |
p7-actor-view | the building's policy authority reads the accepted row | Q2 + Q3 |
p7-invite, p7-staff-job | minting the co-manager's staff as building-scoped members labelled with their company | shape |
p7-admin-flip | whether "all their organization" includes co-managed buildings | Q7 |
p00-operator-vocabulary, p7-terminology-surfaces, p10-terminology-consistency | "record owner" vs "co-managing company" wording; retire "exactly one operator org" if A′ lands | shape |
p00-replace-the-admin-handoff… | the multi-party grant and its expiry = the co-manager row and its clock | Q6 |
p3-registry | relationship kinds and grants (functions on the row) | Q2 (c) or not |
p3-head, p3-coverage-r2, p5-claim | one company per phone number / coverage stays; the later "co-manager's line names the building" question is scoped | Q4 |
p4-* resolver, p9-keys-move | whose defaults fill the building's settings chain | Q5 |
p5-importers, bug-roster-freshness…, p10-core-freshness | the union roster and the request-path proof that an ended row stops admitting | A′ |
p6-conv-org, p6-fanouts, p9-conv-index | threads stay in the record owner's file; do not re-key conversations to one company before this ruling | shape |
p8-engagement, p8-roster | which company's vendors dispatch at the building (record owner's, under Q5 (a)) | Q5 |
p0-client-seed-r2 | import refuses, or links, a building already held by another company (today it silently duplicates) | shape |
p10-core-scope, p10-core-contract, p2-r2-founder-topology-controls | the two-company fixture gains a shared building; no double count, no leak of the record owner's other buildings; the 20 org-anchor sites listed | shape |
| Driver 1's Phase 11 union lanes | told, not edited: multi-company totals dedupe by building id | A′ |
Written before the rulings. Questions 1–3 are now settled (see Decided), so the "do not switch on the writer" line below is lifted for the staff-only writer; the rest still applies.
7e5e2832), by file, with a positive control on the counting method.