Design Reference · for Zareef (schema) & Matt (surface)

Morgue Storage & Coroner-Tenancy Model

ADR-067 · one model, three scenarios · in-house morgue / internal coroner section / provincial coroner morgues

01

Tenancy boundaries — who can see whom

The firewall is the acts_as_tenant boundary, not a permission rule. The tenant is the coroner service, not the individual morgue: the province is one tenant with its morgues as branches under it; Kearney is the operating contractor, not the owner. Megan's internal coroner section is a separate tenant again. A human who works two sides (Megan) holds two logins — ADR-029, one user = one tenant.

graph TD
    subgraph T1["TENANT · Kearney Funeral Home"]
      K1["Funeral cases
(at-need)"] K2["In-house morgue
cooler + slots"] end subgraph T2["TENANT · Kearney's internal coroner section (BTC)"] C1["Coroner-held bodies
(caseless)"] C2["Morgue cooler + slots"] end subgraph T3["TENANT · BC Coroner Service (provincial)"] P0["Province = the tenant"] P1["Morgue A — branch"] P2["Morgue B — branch"] P3["Morgue N — branch (Kearney operates, does not own)"] end MEGAN(["Megan · one human"]) -.->|login 1| T1 MEGAN -.->|login 2| T2 K1 -->|cross-tenant transfer
recorded independently| C1 classDef kearney fill:#b4530922,stroke:#b45309,stroke-width:1.5px; classDef prov fill:#0f766e22,stroke:#0f766e,stroke-width:1.5px; classDef human fill:#1e407822,stroke:#1e4078,stroke-width:2px; class K1,K2 kearney; class C1,C2,P0,P1,P2,P3 prov; class MEGAN human;
Kearney funeral tenant
Coroner tenant (internal or provincial)
One human, N logins
Why tenancy and not Pundit. The discovery notes describe a hard wall — funeral directors "don't know who's in there." A tenant boundary can't be leaked by a policy bug; a Pundit rule inside a shared tenant can. Isolation holds even when Kearney owns the coroner operation (the internal section) — ownership doesn't collapse the firewall.
02

Three layers — the thing we keep conflating

Most of our confusion was collapsing these into one. They are distinct records answering distinct questions. Occupancy is not custody. The slot catalogue is not the occupancy record.

SLOT CATALOGUE resources

The physical slots

A cooler is a resources row (sub_type: cooler); each slot is a child resources row (cooler_slot, parent_resource_id → cooler). Already ships. Answers: what slots physically exist? Same in every morgue.

OCCUPANCY storage_occupancies

Who is in which slot

One stored row per stay — subject + slot + admitted_at/released_at. Answers: is this slot free; how long has this body been in it? (inventory + billing). Drives the availability view and Phase-3 coroner billing.

CUSTODY custody_events

Who had legal possession

Layer-1, append-only, immutable (ADR-014). Answers: who possessed the body, when? Exists only on the Kearney funeral side (at-need cases). Not written on the coroner side.

Why occupancy is separate from custody. They coincide physically at an in-house check-in but are different records. Keeping occupancy out of Layer 1 means a mistyped slot is an ordinary edit, not an immutable supersession event — and lets occupancy exist on a coroner tenant that has no RequiemOS custody chain at all.
03

Scan → what gets written

Capture is not the same on both sides — this is the thing the earlier draft of this page got wrong. The scan-decedent + scan-slot flow lives only on the Kearney internal funeral morgue, where the QR / wristband infrastructure exists and the subject is an at-need case. On the coroner tenants (Megan's internal section and the provincial morgues) RequiemOS is a terminus storage facility: it takes the in/out event fed from the province's own system or entered manually — there is no RequiemOS scan there. What both paths share is only the destination: one storage_occupancies row, read back by one source-agnostic availability query.

graph TD
    HK["KEARNEY INTERNAL FUNERAL MORGUE
subject is an at-need case"] HC["CORONER TENANTS
caseless Person · terminus storage"] HK --> SK["Capture: scan decedent + scan slot
any order · direction inferred · manual fallback"] SK --> K["Write custody_event"] K --> KO["storage_occupancies row
case_id SET · custody_event_id SET"] HC --> SC["Capture: in/out fed from province system
or manual entry · NO RequiemOS scan"] SC --> C["No custody_event written"] C --> CO["storage_occupancies row
case_id NULL · custody_event_id NULL"] KO --> AV["Availability view
slots WHERE released_at IS NULL"] CO --> AV classDef hdrK fill:#b45309,stroke:#b45309,color:#ffffff,stroke-width:0px; classDef hdrC fill:#0f766e,stroke:#0f766e,color:#ffffff,stroke-width:0px; classDef kearney fill:#b4530915,stroke:#b45309,stroke-width:1.5px; classDef prov fill:#0f766e15,stroke:#0f766e,stroke-width:1.5px; classDef shared fill:#15803d18,stroke:#15803d,stroke-width:2px; class HK hdrK; class HC hdrC; class SK,K,KO kearney; class SC,C,CO prov; class AV shared;
Coroner side = terminus storage (decided 2026-09-21, Ryan session). RequiemOS records only the building-boundary in/out and does not own the province's internal chain of custody ("we're terminus… we're just a storage facility"). The province may run its own barcode/CoC system — but that's theirs; it (or a human) simply feeds RequiemOS the in/out event. So no caseless custody event is ever written on this side, and ADR-016 Rule 4 stays intact. The two lanes converge only at the occupancy row and the availability query.
04

The one-column delta — storage_occupancies

In-house and coroner morgues use the same table. The entire structural difference is which columns are populated on a given row. The highlighted row is the delta that carries the whole distinction.

Column In-house morgue Coroner morgue (internal or provincial)
id · tenant_id sametenant_id scopes to the funeral tenant sametenant_id scopes to the coroner tenant
storage_slot_id same→ a cooler_slot resource same→ a cooler_slot resource
person_id SETsubject is always a Person SETcaseless Person (zero cases, per ADR-016)
case_id SETthe at-need case NULLno Kearney case exists — resolves OQ-0263
custody_event_id SETrow is the event's projection NULLterminus storage — no custody event (D6)
admitted_at · released_at samecheck-in / check-out timestamps samesame — and this is the billing signal
admitted_by · released_by · entry_method · notes sameentry_method: scan | manual samemanual likely more common here
For Zareef. case_id nullable is a soft invariant — "coroner-tenant rows must not carry a case_id" is enforced at the model/service layer + a spec, not the DB (same posture as ADR-053's category-vs-type check). custody_event_id is nullable purely to future-proof a hypothetical reversal of D6 at zero migration cost — it is not populated on the coroner side today.
05

Say this, not that — the vocabulary that trips us up

Every one of these got muddled at least once while we worked the model. Fixing the words fixes most of the "wait, which thing?" moments.

Say this

  • "The province is the tenant; the morgues are branches under it."
  • "Occupancy — who's in the slot" (a storage_occupancies row).
  • "The coroner side is terminus storage; we hold the body, not its custody chain."
  • "The slot catalogue is resources; it's the same for every morgue."
  • "Megan has two logins — one per tenant."
  • "On the coroner side, case_id is NULL; the subject is a caseless Person."

Not that

  • "Each morgue is its own tenant." — no; the coroner service is the tenant.
  • "Occupancy is just a read off custody events." — only on the Kearney side; it's a stored row.
  • "We track the coroner's chain of custody." — we don't; that's theirs.
  • "Build a storage_locations table." — dropped; it duplicates cooler_slot.
  • "Megan needs cross-tenant access." — no cross-tenant identity exists (ADR-029).
  • "case_id is required." — that made caseless bodies impossible (the old defect).
06

Decisions & open items — the sign-off checklist

What ADR-067 commits to (review these), and the one thing still genuinely open. Ratify on the ADR.

D1 decided

Coroner storage is its own tenant

Province = one tenant, morgues = branches; Kearney operates, doesn't own. Internal section = separate tenant. Firewall = acts_as_tenant. Two logins for Megan.

D2 decided

Slot catalogue stays in resources

Parent cooler + cooler_slot children, every morgue. "Not time-schedulable" was never the membership test — schedulable and custody-location are independent flags.

D3 / D4 decided

Occupancy is first-class; case_id nullable

One storage_occupancies table, always-Person subject. The single nullable column is the whole in-house-vs-coroner delta. Resolves OQ-0263.

D5 decided

Scan mechanic + in-house = two linked records

Scan decedent + slot, direction inferred, manual fallback. In-house writes a custody event and an occupancy row from one action.

D6 decided

Coroner side = terminus storage

Building-boundary in/out only; we don't own the province's CoC. No caseless custody events → ADR-016 Rule 4 intact. (Was deferred; decided at the 2026-09-21 Ryan session.)

OPEN Megan + Ryan

Decedent identity / login on coroner tenant

How province-side users are provisioned and how a coroner decedent's Person record is created. Doesn't affect this schema, but gates a usable coroner-tenant surface.

Also Phase-2 / out of scope here: per-night billing calculation (OQ-0267 — this ADR only captures the admit/release data); cross-tenant handoff linkage ("which funeral home received the body"); the slot-availability read surface (Prep & Logistics Dashboard §5.1); reservations/holds on slots.