0136 — Launch criteria: org-scoped reads everywhere, attachment resolves person → property → org, old paths kept through the bake, one Settings page

Context

The multi-org architecture is switched on for two bought test lines with a one-week bake (row p5-bake-signals: every old-path and head-path read emits a signal and increments a durable counter). Three customer companies onboard within four to seven weeks. The standup changed some product shapes — most visibly, a tour is assigned to a leasing person's calendar rather than a building's — and the same evening an engineer, filtered to one property, opened the Clara Conversations tab and saw several properties. Gera's instruction, verbatim: "everything open could be driven to completion … we don't want to delete the old architecture just yet … every page needs to be clearly aware of the new org strategy, so we don't do table scans and pulls; everything should be pulled strictly from its org … the design changed, but that means the architecture should still support the old one, just in case — per person calendar or per org or per property, we're that flexible." Companion ruling: ADR-0135.

Decision

  1. Every read is scoped to the selection, everywhere. No page, route, tab or job reads outside the org(s) and building(s) the viewer has selected; no table scans on user-facing paths. "The selection is the read scope" (PR #8474, #8502) is the rule for the whole app, not for the routes that happen to have been swept. A drift guard fails when a route reads outside the selection. The Conversations tab report is the first named consumer to fix (row ui-s-d, the consumer sweep). 1a. At the database — the filtering, the window, the order, and the page. Decision 1 ruled where a read may reach: only the selection. This clause rules where the narrowing happens, and it is the same place — in the query. Gera, 2026-09-21 ~04:20Z, verbatim: "we have to do an audit … every page that pulls from the database should do all the filtering and all the adjustments there for two reasons. One is security and two is latency. It's a lot faster if we do it there and we just pull the necessary information. And we could possibly even do pagination to be even faster … things along those lines need to be practiced throughout. So we need to make it clear that each page needs this behavior." A read satisfies 1a when all four are true of the query itself, not of the code after it:

    • SCOPE. The company, the selection and the property bound are key conditions or filters in the query. A bound applied to an array after the read does not satisfy this at any latency: the rows left the database before the check, in a process that had no business holding them. That is the security half, and it is the half that does not improve when the table is small.
    • SHAPE. The sort and the window — a date range, latest N — are key conditions with a Limit, so cost is proportional to rows shown rather than rows stored. A .sort() or a .slice() after the read is not a window.
    • PAGINATION. A list reads one page: a Limit plus a cursor derived from DynamoDB's LastEvaluatedKey, returned to the caller opaque. Never an offset over a fully-read set. The first page is rendered on the server, the next page is prefetched on the client after paint, and a poll re-reads page one only.
    • JOINS. No per-row read after the main read. Columns a list renders are carried on the row, stamped at write time or projected onto the index the list reads, not fetched per row.

    Staff and "All companies" are not an exemption. A caller with no narrowing selection — a platform_admin, or an empty selection — reads a bounded page, not the whole estate. "No selection" means the first page of everything you are entitled to, never every row in the table. This is written out because the shared scope helper currently does the opposite (a null scope falls through to the unscoped reader), and because staff accounts take that branch on every page they open — so the rule reads as satisfied in exactly the test that matters most.

    Background work is bound differently and is not excused. Synchronization, ingestion and durable workflows take their own authorized tenant scope and pinned operation context rather than a browser selection, per Decision 1's existing carve-out. That changes which bound applies; it does not license an unbounded read. A job that walks every property partition still crosses every tenant boundary.

    A deliberate whole-estate read is allowed, and must say so. Reconciliation oracles, spine sweeps and archive readers legitimately need every row. Those call a reader named for it and carry a one-line comment giving the reason. The defect this clause names is not breadth; it is breadth arrived at by omission — an optional scope argument left off, so the read silently takes its widest branch.

    The measurement that occasioned this clause. A read-only audit on 2026-09-21 measured the properties list against the stage mirror through its own loader functions: five reads issued with no scope argument, 10,791 child rows read on every paint, against 48 rows returned for one company — and four of six companies discarding 85–100% of what was read. The page re-runs that read every five seconds while the tab is open. The prospects list is the same defect measured in production at roughly six seconds per paint. Board standard row: standard-every-page-filters-and-paginates-at-the-database-not-in-memory-for-security-and-latency.

  2. Attachments resolve person → property → org, and all three levels stay supported. The standup moved tour booking onto the leasing person's calendar. That is a default resolution order, not a replacement: a calendar (and the same shape for phone lines, mailboxes, knowledge) may be attached to a person, a property, or an org, and the resolver walks person, then property, then org. The previous building- and company-level attachment work is not scratched; it is the lower rungs of the same ladder. A future customer with one shared calendar per building, or one per company, is served by the same code path with nothing new built.

  3. The old architecture stays through the bake. No deletion row (p9-deletions, the config-file retirement in p5-config-dumps / v6-config-dumps, the catch-all mailbox in p5-catchall) executes its deletion until the bake readout shows the flipped lines' old-path reads at zero and Gera says the new architecture is running smoothly. Building the dumps and the guards proceeds; the delete waits.

  4. One Settings entry, four sub-navs. The main navigation carries a single Settings; inside it a sub-navigation with Admin, Property, Organization, Profile. Every existing settings page is placed under one of those four — placed, not redesigned. Organization and Property may later gain a nested sub-nav of their own. Per ADR-0135 every page under Settings is visible and editable to every signed-in person in the company; Admin alone stays behind platform_admin. Gera's designs for the page's inner UI follow later and are out of scope here.

  5. Drive everything open to completion; Gera tests after. The dock is driven to the finisher's count with the criteria above as acceptance; polishing (the settings redesign among it) starts only when the open rows are closed and the founders have tested the result.

What a future reader should check code against

Consequences

Amendment 1 (2026-09-24) — the Settings redesign starts now

The ask. Gera, 2026-09-24: Settings should be "a clear pretty home… clean and standard" for each level, with the knowledge base included. The redesign is no longer polish that waits for the bake; it is the next piece of work on Settings.

The layout.

  1. One Settings entry, four levels: You (profile, sign-in and security, my calendar, my notifications), Company, Building, and Admin, which only PropFlow staff (platform_admin) see. This renames Decision 4's four sub-navs (Profile → You, Organization → Company, Property → Building); it does not add or remove one. The page follows the building picker: no building picked shows Company, a building picked shows that building.
  2. Company and Building have the same three sections: Knowledge (facts about the place), Policies (written rules Clara follows) and Settings (switches and numbers, grouped by topic such as leasing and tours, maintenance, connections, notifications, billing). The knowledge base moves here as its own section at both levels.
  3. The company sets a rule; a building may set its own. A building that has not set its own value keeps the company's value. Superseded by decision E1 (2026-09-30): this item used to say the company may LOCK a rule so a building cannot override it ("Ask the company to change"). A prod audit found zero live locks at any real company, and Gera ruled "just allow it to be set anywhere. No one can restrict it." The lock was removed in #14928 (switch, writes, resolver) and 5a-2 (dead code).
  4. Every value shows its source in words: "Set by your company", "Set here", "From your company", "PropFlow default · your company hasn't set this", or "Not provided". A money fact with no value at either level is never invented; Clara says it is not on file.
  5. Who edits. Everyone signed in to the company can see and edit the Company and Building sections in version 1, per ADR-0135, which this amendment does not change. You is yours alone. Two things are set up by PropFlow only: the property-system connection and the sending email address, because changing either can break the whole integration. Both are edited on the Admin level, behind the platform_admin gate ADR-0135 already names, and only mirrored read-only on the Company page. No new role gate is added anywhere.
  6. Search uses the existing global search, not a second box inside Settings. Opened from a Settings page, it starts with an @settings tag already added (and, the same way, other pages will add their own tag, such as @tenants); removing the tag searches everything. A result lands on the exact setting at the level you are on.

Build order: tier → organize → pretty → search (Gera, 2026-09-16: "First phase, determine what goes in what tier. Second phase, organize… third phase, make it pretty… then… search.").

Open: the Policies section is pending decision b90269585 (should written rules such as pet, renewal and follow-up policies get their own section?). Recommended: yes. Nothing else in this amendment waits on it.

What a future reader should check code against (this amendment)

Amendment 2 (2026-10-13) — "All companies" is the union, not "pick a company"

The ruling, verbatim. Gera, 2026-10-13: "for every page, it should always be the combination of everything selected, whether it's all companies and all properties." His test, per page: pick all companies but one and all buildings but one, then plain "All companies"; the left-out rows must be missing and everything else must be there.

What it means in code.

  1. Plain "All companies" is the union of every company the viewer may see, never wider. Only platform staff can pick it (a customer's org=* falls to their own company), so it is every company in the staff grant.
  2. It is answered one leg per company, the path a several-company pick already takes (companyLegsOrAllCompanies → resolveOrgLegs → the surface's per-company loader and fold). A total is each company's own number folded by the payload's rule file, never one wide read. A number that has no honest combined value stays per company, as for any several-company pick.
  3. A surface with a dedicated all-companies reader (the work-orders fleet partition, the conversations recency index, the tenants and turnovers first pages) keeps it.
  4. Sandbox companies are in the union exactly as they are when staff tick them in the picker: each surface's own test-building rule applies per leg.
  5. Load: production on 2026-10-13 has 6 companies and 158 buildings behind "All companies". The list caches front the several-company read on their own key, so a poll inside the fresh window reads nothing past the scope.

What a future reader should check code against (this amendment)