# ADR 0001: Work-order media uses single-org scope (board-aligned) ## Status **Proposed — awaiting approval** (2026-08-05). This ADR is **not** self-accepted by the PR author. Approval is required from: - CODEOWNERS team `@Sea-Haven-Industries/internal-dev` (see `.github/CODEOWNERS`) - SH-116 product owner Until approved (or until real tenant enforcement lands), this document records a **pending** exception request against the hard rule below — it does not waive the rule on its own. ## Context SH-116 requires that cross-tenant, unauthorized, and out-of-scope media access be rejected without metadata disclosure. The repository hard rule (`ARCHITECTURE_AND_CODE_QUALITY.md` §2) requires **server-derived tenant scope**: filtering by tenant/customer/owner comes from the authenticated principal and the data layer, never from client-supplied body/query as the source of truth. Verified against the current codebase: - There is no `TenantId` / `CustomerId` column on `WorkOrder`, `WorkOrderAttachments`, or `ApplicationUser`. - `WorkOrder.Customer` is free-text (`nvarchar`), not a FK. - JWT issuance (`AuthenticationService.GetToken`) emits only `Name`, `NameIdentifier`, `Jti`, and `Role` — no tenant/customer claim. - Board, search, and detail already treat the deployment as a single organization via `ApplyBaseScope` (non-deleted, non-template). Introducing a multi-tenant key requires product modeling plus a schema migration, which is out of scope for the SH-116 media contract and blocked by `AGENTS.md` (no migrations / product behavior without explicit instruction). ## Decision (proposed) Until real tenant enforcement exists, work-order media authorization matches the board: 1. **Organization boundary** = `ApplyBaseScope` in the data layer (`istemplate != true` and not deleted). Lookups outside that set resolve as missing → `NotFound` / null (no disclosure). 2. **Authorization at service entry** from claims: staff roles (`Admin`, `Manager`, `Dispatcher`, `Supervisor`) may read/mutate any in-org work order; role `User` (technician) only when `WorkOrder.AssignTo == actorId`; delete remains staff-only. 3. Scope is never taken from request body or query as the trust source; `actorId` and roles come from the authenticated principal. This is **not** a substitute for SH-116 cross-tenant isolation. It is the interim behavior while the exception is under review or real tenant keys land. ## Accepted risk (while Proposed / if Accepted) Any authenticated **staff** principal who knows a numeric work-order id can read or mutate media for that work order, provided it passes `ApplyBaseScope`. There is no server-derived tenant/customer boundary separating staff access across customers. Deleted / template / missing ids are **not** a cross-tenant test; they only prove the `ApplyBaseScope` filter. ## Consequences - Tests for deleted, template, and missing work-order ids cover `ApplyBaseScope` rejection only — they must not be labeled as SH-116 cross-tenant coverage. - Staff org-wide access by numeric id remains intentional and aligned with the board for this interim state. - Merge of PR #47 that relies on this ADR requires either: 1. formal approval of this ADR by the approvers listed in Status, or 2. landing of real tenant enforcement (see below). ## Path to real enforcement Candidate design for the deferred multi-tenant work (tracked in the linked Jira ticket): 1. **Tenant key** — reuse the existing `Accounts` entity (CRM customer) as the customer boundary; add `WorkOrder.AccountId` (FK) and `ApplicationUser.AccountId` (or an equivalent membership table). 2. **Claim** — emit a server-derived `account_id` (or equivalent) claim in `AuthenticationService.GetToken` from the authenticated user’s account membership; never accept account id from body/query as trust source. 3. **Data filter** — extend `ApplyBaseScope` (or a sibling filter) so board, detail, search, and media loads restrict by the claim-derived account scope; staff may still be broader if product defines org-wide roles, but that must be an explicit claims rule, not “any numeric id”. 4. **Backfill** — map free-text `WorkOrder.Customer` strings to `Accounts` rows where possible; unresolved rows need a product decision (block, orphan bucket, or manual remapping). 5. **Tests** — add true cross-tenant rejection tests (staff/tech of account A cannot read or mutate media of a work order owned by account B) with stable `NotFound` / `Forbidden` and no metadata disclosure. ## Excepted rule Hard rule: **server-derived tenant scope** (`ARCHITECTURE_AND_CODE_QUALITY.md` §2). Requested clarification while this ADR is Proposed/Accepted: in the work-order domain, the interim server-derived scope key is the organization boundary enforced by `ApplyBaseScope` plus claims-derived role/assignee — not a `TenantId`/`CustomerId` column. Absence of a multi-tenant key is a temporary product/architecture gap until superseded by the path above. ## Review / expiry Re-review by **2027-02-04**, or earlier if product introduces `TenantId`/`CustomerId`/`AccountId` on work orders or JWT claims, or when the linked multi-tenant ticket closes. ## References - SH-116 — Completion document: fields + media categorization - SH-221 — Server-derived tenant/customer scope for Work Order domain (deferred enforcement) - PR that relies on this ADR: Sea-Haven-Industries/shoc-backend#47 - `WorkOrderBoardQueryFilters.ApplyBaseScope` - `WorkOrderMediaAuthorization` - `ARCHITECTURE_AND_CODE_QUALITY.md` §2, §10 - `REVIEW_AND_PR_FRAMEWORK.md` §7, §8