0144 — The gatekeeper is the keeper of every gate: one registry for every switch, four buckets, and a census-first procedure for moving a switch in
- Status: Accepted
- Date: 2026-10-16
- Deciders: Gera (ruling, 2026-10-15 and 2026-10-16; D1 and D4, 2026-10-27; per-path overrides, 2026-10-29, amendments below)
- Related: ADR-0139 (one Off · Supervised · Live switch per module per customer), ADR-0141 (held messages and who reviews them), ADR-0143 (turnover and new lease follow the gatekeeper; Supervised holds their property-software writes)
- Inventory: app-switch-inventory-2026-10 (staff only)
Who this is for. A future session handed "move switches into the gatekeeper", with none of the conversation that produced this record. Sections 1 to 5 say what the end state is. Section 6 is the procedure to follow, step by step. Section 8 lists gates that are already done, as worked examples.
Context
ADR-0139 fixed one problem for the four product modules: there used to be two answers to "is PropFlow doing X at this building?", and the places that act did not all ask the same one. ADR-0143 fixed it again for turnover and the new-lease pipeline, which had their own platform arms and per-building flags beside the module status.
The same problem exists across the whole app. The October 2026 inventory counted about 370 switches, flags, modes and settings that change product behavior, of which only about 8% are under the gatekeeper. The rest live in hosting environment variables, platform rows, hard-coded lists and plain code. Nobody can answer "what is switched on for this customer, and who can change it?" from one place, and a customer's page can show a switch that no longer gates anything (the guest-card company row did until #19906 took it off the page).
Gera, 2026-10-15/16: "The gatekeeper is the keeper of every gate."
Decision
1. Every gate is registered in one place
Every switch, flag, mode or setting that changes product behavior is registered in one registry, whatever kind it is and wherever its value is stored. That registry is the list of every gate. A gate that is not in it is a defect, the same way an ungated write tool is (ADR-0139, consequences).
Registering a gate does not move its value. A hosting environment variable stays a hosting environment variable; it gains a registry entry that says what it is, who owns it and what it defaults to. The customer-facing ones also move their value into the settings registry (bucket a, below).
Today the settings registry is KEYS in src/lib/domain/portfolio/registry/keys.ts. Gates whose
value lives elsewhere (bucket b) are registered with source naming where the value lives. Until
that registry covers them, the inventory page is the list.
2. Levels: Platform, then Company, then Building
A gate has a value at up to three levels, folded by the existing settings precedence:
| Level | Holds | Who sees it |
|---|---|---|
| Platform (everyone) | Our engineering plumbing: kill switches, model choices, timeouts, rate limits, test and safety exemptions, CI | Staff only. Never shown on a customer's page. |
| Company | A customer's choice for all its buildings | That company's page |
| Building | A customer's choice for one building, overriding its company | That company's page, on the building |
Precedence lives only in foldEffective (src/lib/domain/portfolio/scope/resolve-effective.ts).
No reader restates it. A new gate never writes its own "building first, else company, else default"
logic; it calls the resolver.
A customer's page (/admin/customers/[id] and the customer's own settings page) shows only that
company's company-level and building-level gates, and only the customer-facing ones. Platform
gates never appear there.
3. The fields every gate carries
| Field | Values | Meaning |
|---|---|---|
| scope | platform · company · building · per-record | The lowest level the gate can be set at. Per-record means it rides on one work order, lease or conversation. |
| kind | module · feature · kill switch · setting · test-safety · allowlist | What sort of gate it is. A module is one of ADR-0139's four; a feature hangs under a module. |
| owner | System admin · Staff · Customer · Engineering | Who decides its value. A system admin (an assigned platform administrator: Gera, Fede, Sean) owns turning a real customer's sending or acting on (amended 2026-10-22, see §6.3). |
| source | settings registry · hosting env · platform row · hard-coded list · code | Where the value is stored today. |
| default | the value when nothing is stored | Must match the code (absentMeans in KEYS, which a drift test already checks). |
| customer-visible | yes · no | Yes only for bucket (a). |
| description | one sentence | What changes when it flips, in plain words. |
| decision | bucket a · b · c · d, plus the PR or ruling that placed it | Where it belongs and why. |
Two filled examples:
| Gate | scope | kind | owner | source | default | visible | description | decision |
|---|---|---|---|---|---|---|---|---|
turnover.auto_release |
company | feature (under Maintenance) | System admin | settings registry | off | yes | Held turnover work goes out once the unit is empty, without waiting for a person. | (a), #19563 #19564 |
| A provider kill switch in the hosting environment | platform | kill switch | Engineering | hosting env | on | no | Stops all calls to one outside provider at once. | (b) |
4. The four buckets, and the test for each
Every gate goes in exactly one bucket. Apply the tests in order; the first that fits wins.
(a) Customer-facing. Test: flipping it changes what a customer, tenant, applicant or vendor experiences, or what PropFlow writes to the customer's property software. Where it goes: under the gatekeeper, in one of two ways:
- it is decided by its module's Off · Supervised · Live status (no switch of its own), or
- it is a feature switch shown under its module row on
/admin/customers/[id].
It is never a second module status. A feature switch narrows or extends what its module does; it does not compete with it. When the module is Off, the feature is off.
(b) Our plumbing. Test: it exists so engineering can run the system safely: kill switches, model, timeout and rate settings, test and safety exemptions, CI. A customer would not understand it or want to choose it. Where it goes: the Platform level, registered, staff-only. Kill switches stay one-click and global. Never fold a kill switch into per-company settings: its whole job is to stop everything in one move during an incident.
(c) A bug fix or safety rule behind a switch. Test: the switch exists only so a fix could ship dark, or it turns off a rule nobody should ever turn off. Where it goes: nowhere. Remove the switch and ship the behavior always-on.
(d) Unused. Test: nothing reads it, or every stored value is the default and nobody plans to change it. Where it goes: delete it.
5. What Supervised means, as built (ADR-0139 §9, ADR-0143)
Supervised is the middle module status. As of this week:
- It holds Clara's outbound replies to residents and prospects (ADR-0139 §9). A turn from the
company's own staff or a vendor is never held by the module gate, and sends declared
'vendor'never reach the drafts check. Vendor-side holds exist only in named lanes: turnover and new-lease dispatch (ADR-0143) and the in-house handyman page ("Handyman texts", #20060–#20239). Reviewers per module are ADR-0141's. - For turnover and the new-lease pipeline only, it also holds property-software writes
(ADR-0143 §3; everywhere else the rule is still "messages are held, property-software writes go").
A held write is a
HeldPmsWriterow (#20064). It shows its exact payload on/review(#20087): for a charge, the tenant, the unit, the charge type, the amount and the GL account name. Approve writes it exactly once from the stored params, durably (#20088, #20112). Reject drops it. A case the hold flow cannot read clearly fails closed: nothing is written. Move-out charges, the move-out draft, unit availability and work-order sync follow this (#20148, #20165, #20177, #20178). - An existing hold is a person's decision. If the building later moves to Live, the Live path does not file the same change itself and does not close the hold. A waiting hold still waits.
- Known gap: a held page to the in-house handyman still assigns the AppFolio vendor when the crew member has an AppFolio vendor id. That assignment is a property-software write and should be held with the page. Not yet built.
- Phone calls (amended 2026-10-23). Gera, 2026-10-22: "For Supervised, it just doesn't do
any outbound, but it still writes a PMS and everything." Gera, 2026-10-23: "Yes, same rule
everywhere." An inbound call at a Supervised building is handled as Live in every module
(maintenance #22055; leasing and renewals in the PR carrying this note): only a stored Off is
off. Every outbound contact waits for a person, phone calls included: the dial door
(
initiate-outbound-call.ts, viasupervised-call-state.ts) refuses an unapproved call for a Supervised module, collections included. Property-software writes still go. This replaces the 2026-10-01 rule (ruling fe08e2091), under which Supervised with "Supervised calls go to: team" (also the default) made an inbound call behave as Off. Gera, 2026-10-23, on that per-building call choice: "Remove it." Its dropdown, API field and readers are deleted in follow-up PRs; stored values are left in place and ignored (no data migration, §6).
6. The procedure for moving a switch into the gatekeeper
This is the pattern proven this week. Follow it in order.
Census first, read-only, in prod. Using the real readers (the same functions the product calls, not a hand-written query), compute for every company and every building what the gate answers today and what it would answer after the change. Put the counts in the PR body. Example: the guest-card merge's census found 0 of 159 buildings change behavior (#19906 body).
Read the census, then decide.
- Zero buildings change: ship it.
- Any building would start sending or acting: stop. Get the owner's go (Fede's) before merging.
- Any building would stop sending or acting: report the list in the PR and in your summary.
Never turn a real customer's feature on in a merge. A new feature switch defaults to off, or to "follow the module". A system admin turns it on per company afterwards. A switch that can turn on sending at a real company carries the system-admin bar in its writer: the staff route calls
requirePlatformAdministrator(the assigned platform-administrator roster), whichadmin-scope-inventory.drift.test.tsalready requires of every mutating admin handler. Amended 2026-10-22 (Gera): "it's not just Fede and me, it's also Sean… we are like system admins, so it'd be more like a system admin check." This replaced the original "Fede bar" (#19831, #19868), which named one person; no new role was added.The new gate replaces the old switch: one source of truth. CLAUDE.md's ONE SOURCE OF TRUTH rule applies. By default, the PR that moves a switch's last caller onto the new gate also deletes the old switch: its reader, staff row, route and registry entry. Do not migrate or delete prod data. Orphaned stored rows are fine because nothing reads them.
When one PR is too big, split it vertically and keep the two answers equal. A move that won't fit under the 1,000-line ceiling may ship as small vertical PRs: the reader and default first, then each producer, then the customer-page switch, then the removal. This is the only allowed exception to the one-source rule, and it comes with three conditions:
- Say so first. CLAUDE.md: "If the refactor is genuinely too large for one PR, say so before writing code so we scope smaller." Name the split, and why one PR can't hold it, before the first PR is written, and again in its body.
- Prove the paths agree. Every intermediate PR's census must show the old switch and the new gate give the same answer for every building, so production never has two paths that disagree.
- Finish in the same lane. The removal PR is part of the same piece of work and lands before the lane closes. It is never parked "for later".
That's how #20380 and #20401 landed: by the time they merged, nothing read the old switches.
Worked examples to copy:
| Change | PRs | Shape |
|---|---|---|
| Turnover auto-release, a new company feature switch, default off | #19563, #19564 | reader + behavior, then the switch under Maintenance on the customer page |
| Guest-card company row folded into the building setting | #19831, #19839, #19868, #19898, #19906, #19927 | Fede bar, then script, then each reader moved, then the old row deleted; census 0 of 159 |
| "Handyman texts", a company mode under Maintenance | #20060, #20237, #20238, #20239 | one sender per part, then the select on the customer page |
| Old turnover and new-lease switches removed | #20380, #20401 | removal only, no data migration |
7. Rule going forward
No new switch merges unless it is registered with the fields in §3 and placed in a bucket from §4. Until a test enforces this, the PR that adds a switch carries the §3 row for it in its body. Be honest about the gap: no review lens checks for that row yet, so today the rule rests on the author. The follow-up is a drift test, or a reviewer lens, that fails an unregistered env flag or boolean gate.
8. Gates already under the gatekeeper (examples, not the list)
These are done and can be copied. They are examples; the inventory page is the list.
- The four module statuses (Off · Supervised · Live), ADR-0139.
turnover.auto_release,lease.send_for_signature,maintenance.handyman_texts("Handyman texts"),emergency.alert_chainandmaintenance.intake_only: company switches inKEYS, read throughsrc/lib/domain/settings/company-switch.tsor their own one reader.- The guest-card building setting ("Guest card after a call",
guestCardWriteFromCalls, read throughgetEffectiveLeasingSettings). - The per-building staff switches shown under their module on the customer page (#19715, #19802).
Consequences
- Any session can answer "what is on for this customer, and who decides?" from one registry, and can move a switch in by following §6 without asking.
- A customer's page shows only that customer's own choices. Engineering plumbing stops leaking onto it.
- Moving the remaining ~92% is a long tail of small PRs. Each needs its own census; there is no bulk move.
- Bucket (c) and (d) work deletes code. It must still pass the census in step 1, because a "dead" switch can turn out to be read somewhere.
- Follow-up, not built: a drift test that fails CI when code reads an environment flag or a boolean gate that is not registered. Until it exists, §7 is held by review only.
- Follow-up, not built: hold the AppFolio vendor assignment on a held handyman page (§5 known gap).
Alternatives considered
- Leave each kind of switch where it is, with no shared list. Rejected: that is today's state, and it is why nobody can say what is on for a customer.
- Show every gate, plumbing included, on the customer's page. Rejected: customers would see switches they cannot judge, and a kill switch spread across per-company settings can no longer stop everything in one move.
- Give a feature its own Off · Supervised · Live status beside its module. Rejected: two statuses that can disagree is the problem ADR-0139 removed. A feature hangs under its module.
- Move switches in bulk with a data migration. Rejected: a census per switch and orphaned rows left in place are safer than rewriting prod settings.
Amendment 2026-10-27: the global email and text switches are named stops
Gera, 2026-10-27, on census item D6: CONFIG/SETTINGS.emailsEnabled and .smsEnabled stay as
switches and join the named emergency stops as Stop all email and Stop all texts, next to
Stop all AppFolio syncing, Stop all renewal texts and emails, Stop all past-due rent reminders and
Lock out a revoked key. They are §4 bucket (b) kill switches: global, one move, staff-only. The
list lives in EMERGENCY_STOPS (src/lib/domain/admin/arms-registry.ts) and
docs/architecture/emergency-stops.md. How either switch is read did not change.
Amendment 2026-10-28: D1, "Clara is live" stops gating sends; the modules decide
Gera, 2026-10-27, census item D1: Organization.claraLive ("Clara is live", the company master
switch) is folded into the module controls and then deleted. It was the last company-wide switch
outside the gatekeeper, and it could disagree with the modules: a company with a module Live could
still be dark, and the customer page showed both. Part 1 has shipped. Part 2 removes the readers
that are left.
What was decided.
claraLiveno longer gates sends. The company step every send door shares (resolveCompanyLiveHold,src/lib/domain/organizations/clara-live.ts) does not read it. That covers the email seam (customer sends and declared staff notices), the text door (off-means-off.ts), the prospect SMS touch and the first-reply text. The Supervised holds no longer step aside for a company that is notclaraLiveeither:supervisedModulesAt,shouldSpeakFirstEmailand rule R8 (every enabled reply module Supervised, none Live) apply at every company. (#22899)- A company is held only when every plan module is Off. "Not live" is now derived: a company
whose four plan modules (Leasing, Renewals, Maintenance, Collections) all resolve Off at the
company level is held, as
org_not_live, exactly as aclaraLive: falsecompany was. That holds customer sends, staff notices and notification texts alike ("off means off", Fede, 2026-10-19). One plan module Supervised or Live means the company is live, and the per-door module holds decide the rest. The send modules (Owner reports, Team notices) do not count: they are notices about the plan modules' work. (#22899) - A new company starts all Off.
createOrganizationstores every plan module Off for a new customer company, records the creator's picks as Setting up (Off for every gate), and leaves the send modules on their Live default, held with the company until staff turn a plan module on. This is whatclaraLive: falseused to guarantee on its own. A customer company always starts quiet, whoever creates it and whether or not it states a plan; an infrastructure creator with no plan (sentinels, sandboxes, eval fixtures) inherits the defaults, as before. (#23248, #23285) The company'smodules.enabledrow is written in the same transaction as the company, because a company with no row reads every module On (#23311). - Outage behaviour is unchanged. A company that names no company (
null) is held asorg_unresolved, as before. A company whose row or modules cannot be read (an outage) is held, except where the send was already allowed to go in an outage: staff notices and texts send on an unreadable company (sendWhenUnreadable), and a life-safety page whose caller already read the company sends when the re-read fails. Fede, 2026-10-19: only a company READ as not live is held; an outage never drops a gas page (ADR-0052).
Proof it changed nothing that was live. Before #22899 merged, the old and new company step
were run side by side on every production company row (read-only): identical at all six companies
where claraLive was true. The §6 census compared old and new at all 145 buildings of JPCO, Situs
Group, Western Slope, ConAm and PropFlow Staff: 0 change. A read-only check on 2026-10-28 found a
modules.enabled row at all 8 production companies, so none reads the defaults by accident.
Part 2: the claraLive readers that are left. Each one holds or skips when the company is not
claraLive, so none can send something the modules would hold. They are removed in part 2, with
the field, following §6 step 4 (the PR that moves the last reader deletes the switch).
| What it does | Readers |
|---|---|
| Leasing send paths (skip or hold when not live) | first-email-mode.ts (the guest-card first email), guest-card-reply-intake.ts (replies only to cards that arrived after go-live, from claraLiveChangedAt), held-first-email-team-notice.ts, outreach-trigger.ts (two readers), gmail-company-inbox-poller.ts |
| Go-live time stamps | lambda/appfolio-sync/handler.ts and inbox-items.ts read claraLiveChangedAt as the go-live moment |
| Alerting scope | inbound-reply-clock.ts watches only live companies |
| Onboarding stage | onboarding-access.ts, answers-read-only.ts, clara-stage.ts, customer-building-control.ts, hidden-module-topics-server.ts, the go-live checklist, setup-intake, buildings, renewal-policy and email-setup routes, disconnect-policy.ts, the customer inbox and the onboarding pages |
| New-company start | quietNewCompanySettings keeps a creator's modules as given when it says claraLive: true |
| Leftovers of part 1 | shouldHoldForCompanyLive has no production send caller; resolveCompanyLiveHold's preReadOrganization is still typed with claraLive, which it no longer reads |
| The switch itself | the staff route admin/companies/[id]/clara-live, the "Clara is live" switch on the customer page, the Customers list column and the Atlas health row |
Two questions, two answers (2026-10-30). "Is this company live right now" is the module state
(companyLiveByModules); "has this company ever gone live" is Organization.liveSince, stamped
once by setOrgEnabledModules when the company first leaves all-Off, never moved or cleared, and
read through companyHasGoneLive / companyWentLiveAt (clara-live.ts).
Part 2 done (2026-11-01). The send readers (#24076), the date readers (#24277) and the
lifecycle readers (#24278) moved; the six live companies were stamped (liveSince, Gera's go,
2026-11-01); the claraLive fallbacks, the switch, its staff route and its writer are deleted. The
retired type fields and the stored attributes go last, the attributes by a dry run for Gera.
Turning a company live means turning a plan module Supervised or Live on the company card.
Amendment 2026-10-29: D4, Clara's email replies have one control
Gera, 2026-10-27, census item D4: Clara's automatic replies to emailed leads had two controls, the
CONFIG/OPERATING_MODE_ARM row (a platform enabled flag plus a per-building allowlist) and the
email.clara_auto_replies switch (a company switch with a building override). The arm is retired
and the switch is the only control. Yale 25 Station, the one building on the allowlist, got a
building row of true on 2026-10-29 before the reader was deleted; a read-only check of all 204
production buildings found no change.
How this squares with §4(b). The arm's enabled flag was described in code as a kill switch,
but it was never one of the named emergency stops (EMERGENCY_STOPS) and it guarded one door for
one building. D4 removes it on that basis. §4(b) still holds for the named stops: Stop all email
(CONFIG/SETTINGS.emailsEnabled) still stops these replies along with every other email. There is
no longer a stop for this one path on its own. The stored arm row is an orphan nothing reads (§6.4).
Until a staff writer for email.clara_auto_replies ships (§6.3), changing it is a hand-run write
through putSettingAttachment.
Amendment 2026-10-29: a path may hold its own mode, inheriting its module's
Gera, 2026-10-29, on the Clara's paths page (#23550): "Each path gets its own mode. Design it as an override that inherits: a path with no override follows its module's mode, so nothing changes until someone sets one. A path override wins over the module, through the canonical resolver, so every send door reads one function that answers 'what mode is this path in?'"
This amends §4(a) ("it is never a second module status") and the alternative rejected under Alternatives considered ("give a feature its own Off · Supervised · Live status beside its module"). Both stand for the reason they were written: two statuses that can disagree by accident is the defect ADR-0139 removed. What changes is the shape: a path's status is not a second switch beside the module's; it is an override of the module's, absent by default, folded by the one resolver, so there is still exactly one answer and one place that computes it.
What a path is
A path is one thing a customer, resident, prospect, vendor, owner or the company's own team would
notice Clara doing: an answer, a send, a call, or a property-software write. The list is the
registry in src/lib/domain/modules/clara-paths.ts (70 rows on 2026-10-29), one row per path with
its module, who it reaches and by what channel, what governs it today, and the evidence (the file
lands with #23550, part 1 of this lane). A path is registered there before it may hold an override,
and a send site declares its path id beside the module it already declares.
The decisions
One registry key, slotted by path id.
gate.path.<path>inKEYS(portfolio/registry/keys.ts), classPARAMETER, writable at the company only — one mode per path, for the whole company (Gera, 2026-10-30, on the page: "if I turn it on, it's per building, how do I turn it off for one building?"; the key was born writable at a building too and was narrowed before any row was written). The resolver reads the row at the company node alone, through the same fold every routing setting uses (foldEffectivethroughreadRoutingSetting), so a stray building row is never read and can never override. A building switched out of Propflow keeps its own key (building.record_only), shown read-only on the page. The closed slot list is the registry's path ids; the route refuses any other. The value is one ofoff·preview·supervised·live, stored whole (Live is a value here, not the absence of a marker), because the absence means something else (see 2).Absent means inherit. With no row at the building and none at the company, the path's mode is its module's mode, exactly as today. No prod row is written by the change; every company resolves as it did the day before.
absentMeans: null.An override wins over the module. A stored row answers for the path whatever the module says, Off included: a company may run Maintenance Live and hold its handyman texts Supervised (today's
maintenance.handyman_textsis this override under another name), or run Leasing Supervised and let tour reminders go Live. Two things sit above a path override: the company step (a company whose plan modules all resolve Off is held asorg_not_live, amendment 2026-10-28 D1, before any module or path is asked) and the emergency passthrough (ADR-0149; decision 5 below).One resolver, every door.
resolvePathModeForOrg(organizationId, pathId, propertyId?)(modules/path-mode.ts) reads the module's mode withresolveModuleModeForOrg, then the override through the resolver, and answers the override when one is stored. It never throws. A failed override read fails closed: it answers the stricter of the module's mode and Preview (Off ›unresolved› Preview › Supervised › Live). Preview, not Supervised, is the floor because Supervised still lets property-software writes and calls through (module-mode.ts): a floor of Supervised would release the writes of a path a person had set Off or Preview. Under Preview nothing is sent, written or dialled, and what Clara would have done is recorded; a module that is Off orunresolvedstays so. (Tightened from "Supervised" on #23557's review, before part 2b implemented it.) A door askscapabilitiesFor(mode, subject)on that answer, exactly as it asks on a module's today, so the Preview and Supervised rules (module-mode.ts) apply unchanged. No door restates the precedence.Emergencies cannot be overridden. A path whose governor is
always(the emergency work order, the on-call chain, the relay call, the life-safety page; sign-in codes; a staff member's own words) has no override: the writer refuses the slot, andcapabilitiesForwith the emergency subject answers the Live row in every mode anyway (ADR-0149). The row stays grayed on the page.Supervised holds; it never drops. A path that today answers Supervised by refusing the send at the door (tour reminders, follow-up cadences, the application link, vendor quotes and chases) is not wired: Supervised must hold it on the one approval queue, as it holds a reply. Wiring the 39 partly- or un-wired paths is the third part of this lane, one module or one door per PR, each with a test that fails on main. Until a path is wired, its row is grayed with the reason and its override may not be written (the page offers no pick; the route refuses the slot), so an override never promises a behaviour the door does not have.
Existing feature modes fold into the override.
maintenance.handyman_texts(follow_module·supervised·live) is the override on pathmaintenance.handyman_textsin all but two details, which the fold maps and the §6 census checks as "same answer at every building": the old key is company-only, so the resolver reads the old row where nogate.path.…row exists relative to the module, as the old door read it (follow_module= inherit;supervised= the stricter of the module and Supervised, since it only ever ADDED a hold;live= Live where the module is Supervised and the module's own mode otherwise, since it only RELEASED Supervised; amended 2026-10-30 on the #23642 review: the fold is never looser than the module; it is TIGHTER than the old door in one place, deliberately: an oldliverow no longer sends past a Preview or Off module, which is Gera's 2026-10-28 Preview rule, so the §6 census expects that difference at Preview and Off buildings and only there), a read-side fold under §6.5 — no data migrates (§6.4 stands); the staff select writes the new key, "Same as the module" ends the old row too so the fold cannot bring it back, and the old key's own reader goes when nothing but the fold reads it; and itssupervisedholds urgent work at every priority, which the override keeps because the emergency carve-out is the subject's (ADR-0149), not the key's. It moves by §6's procedure (census; same answer at every building; the old key's last reader deleted in the same lane). The on/off feature switches under a module (turnover.auto_release,maintenance.quotes,email.clara_auto_replies, the per-building staff switches) are not modes and stay as they are: they say whether the path exists at the company; the override says in which mode it runs.Who writes it. Staff only, through
PUT /api/admin/companies/[id]/path-modebehind the platform-administrator roster, the same bar as a module mode. The customer page shows it in the Clara's paths table: the select on a row writes the row's own override; "Same as" clears it. Turning a real customer's path on past its module is a system admin's act (§6.3).
How this squares with hot rule 8 and §4
Hot rule 8 asks for one company or building setting per capability, visible on the admin page. The override is that one setting for the path's mode; it does not add a switch beside the module's, it narrows the module's answer for one path through the fold the module's own answer already uses. §4(a) still holds for existence switches ("it is never a second module status"): an override is not a status a path carries on its own, it is a value in the module's chain. The rejected alternative stays rejected in its original form, two independent statuses; the accepted form is one status with a per-path override that is absent until a person sets it.
Consequences
- Every send door moves from "which module am I?" to "which path am I?", one declaration wider, and reads one function. A door that declares no path is a defect, the same as one that declares no module.
- The page can answer, per path, "Off · Preview · Supervised · Live, and who set it" from the resolver's provenance.
- Follow-ups, in order: the key, resolver, writer and page select (part 2); the wiring of the 39 paths
(part 3); the fold of
maintenance.handyman_textsinto its path override (part 3, Maintenance).
Amendment 2026-11-05: §6.4 allows one additive backfill for the Company knowledge base
Gera, 2026-11-05, recorded in ADR-0156 §10: for the knowledge base's brain.* / brain_suggested.* rows only, §6.4's "do not migrate or delete prod data" gains one exception. One additive backfill copies the legacy knowledge rows into the new slots, overwrites and deletes nothing, and runs per company, per step, on Gera's direct approval of that exact step with a fresh dry run's counts (CLAUDE.md's prod-data rule). Old rows stay in place, unread after the removal lane. The rest of §6 applies unchanged.