One building, two management companies

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

Decided — 2026-09-23

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.

QuestionOutcomeDecided by
ShapeA′ — one file, a co-manager arrangement, and the building on both companies' lists.decider · f7f6ab71d
1 Record ownerThe company the building was imported from and is billed to.decider · f358b31c1
2 Who sets policyThe company doing the day-to-day managing sets the rules — pet fee, quiet hours, escalation.founder · block b90181521
3 Acceptance stepSuperseded 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 numberOne number per building, held by the record owner; the co-manager's staff are reached through it.decider · fd94c4a24
5 Quiet hours and defaultsThe building's own settings, in the record owner's settings chain.decider · fbbb876c2
6 Co-manager expirystill open With the founder. Recommendation stands: never expires; either company ends it.block b90181535
7 Co-manager's adminsThey see the shared building, marked as managed for another company — never the record owner's other buildings.decider · f6f0a1a18
8 Sep 10 vs Sep 23Sep 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.

The two words this page uses

What is true today

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".

Technical detail — where "one company" is enforced (main branch, 2026-09-23)
WhereWhat it doesEffect on a shared building
Property.organizationId: string (types.ts, pinned by property-org-required.drift.test.ts)the type holds one companycannot express a second one
proporg-index: PROPORG#<org> / PROP#<id> (proporg-gsi.ts)the roster is one attribute pair on the building's rowthe co-managing company's list never returns it
write guard attribute_not_exists(organizationId) OR organizationId = :orgId → CrossOrgIdentityErrorrefuses a save under another companya 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 === orgScoperoute admission for reads and writesa 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 companyeverything 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 = :scopeone company per phone number / mailbox / calendarthe 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 companythe inversion the D-0909-1 ruling named: the manager is refused at write
buildScopePath → [PROP#, GROUP#…, ORG#<owner>]the settings chain ends at one companythe co-managing company's defaults never reach the building
computeScopeOf throws on many-orgs; the union lanes fold one leg per companydashboards add companies upa 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.

Technical detail — what already exists, dark, for the co-managed reading

The main question: which shape?

Decided: A′ — by the decider, receipt 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.

A Co-managed, seen by switching companies

What the PM sees
The co-managing company's staff log in, switch into the record-owning company, and see this one building and nothing else of that company's. Their own company's list, dashboard and settings do not show the building. Residents text one number, talk to one assistant, and everything files under the record owner.
What changes
The dark co-manager row is switched on with a first real caller (an admin action or the invite flow), the co-manager's staff get building-scoped roles inside the record owner's company, and the building's policy authority reads the row.
Cost
smallest Every "is this building in my company?" check is untouched, because the co-manager's staff are members of the record owner. Single-company buildings are byte-identical.
Reversibility
End the row and the co-manager's staff lose the building the same day. Nothing moves, nothing forks.

A′ A, plus the building shows on the co-manager's own list recommended

What the PM sees
Everything in A, and the building also appears in the co-managing company's own building list, picker, dashboard and reports, marked "managed for the record owner". Counts do not double. The co-manager still never sees the record owner's other buildings.
What changes
A, plus: a company's building list becomes "its own shelf, plus buildings it holds a live, accepted co-manager row on" (one extra query on an index the rows already carry); the shared "is this building in my company?" predicate learns the second clause once, in one place; every multi-company total dedupes by building id; and a test proves that ending the row stops admitting the co-manager on the next request.
Cost
small One shared predicate change and one roster reader, not fifty-three edits. The data stays in the record owner's file. The empty-set case (no co-manager row) is byte-identical to today and gets a pinned test.
Reversibility
Same as A. End the row and the building drops off the co-manager's list.
Technical detail — the A′ seam
  • Roster: 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.
  • Admission: route 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.
  • Freshness: GSI1 cannot be read consistently, so the request-path check that an ended row no longer admits must be proven (the existing bug-roster-freshness-has-no-request-path-check shape).
  • Unions: the dashboard fold lanes are one leg per company today; a shared building must be counted once. This is a message to the lane that owns those files, not an edit from this one.

B Transfer the building to the other company

What the PM sees
The building gets a new file under the co-managing company, which becomes its record owner. The old company keeps a reports-only view as owner. The old company's staff lose day-to-day access. Residents' texting consent is collected again, because the building's people now sit in a different company's pool.
What changes
A new building record, a "transferred to" link from the old one, an "owned by" row pointing back for reports, and a re-consent task for residents.
Cost
medium, and it is not "both" Clean if one company is really the operator and the other is really only the owner. It is the 2026-09-10 standup answer, and it gives one company the building, not two.
Reversibility
Only by transferring back — a second fork of the resident pool and a second re-consent.

C Two building records over one address

What the PM sees
Each company fully runs "its" building: two assistants, two phone numbers, two sets of quiet hours, and the residents and their threads split down the middle depending on who they last spoke to.
What changes
Nothing in the one-company code, but a reconciliation layer must tie the two records to one physical building forever, and every resident-facing rule has to pick a side.
Cost
large, and it breaks the promise The design's "one assistant per building" is the thing residents rely on. This is the joint-venture "two assistants on one address" answer, and it should be refused unless the founders explicitly want two independent assistants.
Reversibility
Poor. Merging two records back into one is a data reconciliation, not an undo.

The eight questions that ride on the same decision

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.

1Which company is the record owner of a shared building?

Decided: (a) — the company the building was imported from and is billed to. Decider, receipt f358b31c1.
In plain terms

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.

Technical detail

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.

2Who sets the building's policies — the company that pays, or the one that manages?

Answered by the founder: (b) — "the company doing the day-to-day managing sets the rules." Block b90181521. With no acceptance step (question 3), the arrangement takes effect once staff write it.
In plain terms

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.

Technical detail

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.

3Does a co-manager row grant anything before the co-managing company accepts it?

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. The decider's ruling for (a), receipt f6e2f14ad, no longer applies. The options below are kept as the record of what was asked.
In plain terms

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.

Technical detail

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.

4Whose is the building's phone number?

Decided: (a) — one number, held by the record owner. Decider, receipt fd94c4a24.
In plain terms

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.

Technical detail

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).

5Whose quiet hours and company defaults reach a resident of a shared building?

Decided: (a) — the building's own settings, in the record owner's chain. Decider, receipt fbbb876c2. Read with question 2: the day-to-day manager is who edits those building settings.
In plain terms

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.

Technical detail

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.

6Does a co-managing company expire after ninety days like an outside party?

Still open — with the founder, block b90181535. The recommendation below stands.
In plain terms

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.

Technical detail

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).

7Do the co-managing company's admins see the shared building on their own company view?

Decided: (a) — the co-manager's admins see the shared building, marked as managed for another company, and nothing else of the record owner's. Decider, receipt f6f0a1a18.
In plain terms

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.

Technical detail

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.

8Which statement stands — "move it to a new company" (Sep 10) or "it belongs to both" (Sep 23)?

Decided: (a) — Sep 23 stands; transfer stays available. Decider, receipt f536efca5.
In plain terms

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.

What each answer unblocks on the board

These rows are waiting on the ruling. None of them needs a new row; the shape decides which one moves first.

Board rowWhat the ruling settles for itAnswered by
p7-relationshipsthe first production caller of the co-manager writer, with the seam pin moved from "zero callers" to "these callers" in the same PRshape + Q3
p7-actor-viewthe building's policy authority reads the accepted rowQ2 + Q3
p7-invite, p7-staff-jobminting the co-manager's staff as building-scoped members labelled with their companyshape
p7-admin-flipwhether "all their organization" includes co-managed buildingsQ7
p00-operator-vocabulary, p7-terminology-surfaces, p10-terminology-consistency"record owner" vs "co-managing company" wording; retire "exactly one operator org" if A′ landsshape
p00-replace-the-admin-handoff…the multi-party grant and its expiry = the co-manager row and its clockQ6
p3-registryrelationship kinds and grants (functions on the row)Q2 (c) or not
p3-head, p3-coverage-r2, p5-claimone company per phone number / coverage stays; the later "co-manager's line names the building" question is scopedQ4
p4-* resolver, p9-keys-movewhose defaults fill the building's settings chainQ5
p5-importers, bug-roster-freshness…, p10-core-freshnessthe union roster and the request-path proof that an ended row stops admittingA′
p6-conv-org, p6-fanouts, p9-conv-indexthreads stay in the record owner's file; do not re-key conversations to one company before this rulingshape
p8-engagement, p8-rosterwhich company's vendors dispatch at the building (record owner's, under Q5 (a))Q5
p0-client-seed-r2import 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-controlsthe two-company fixture gains a shared building; no double count, no leak of the record owner's other buildings; the 20 org-anchor sites listedshape
Driver 1's Phase 11 union lanestold, not edited: multi-company totals dedupe by building idA′

Until the ruling lands

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.

Where this came from

PropFlow Docs