ADR-0002: Single Instance with Scope Hierarchy and Copy-on-Derive¶
- Status: accepted
- Date: 2026-06-04
- Deciders: Nicolas Burri
- Refined by: ADR-0027 (RLS guards content tables; scope/mandator are control-plane), ADR-0127 (§1: universally governing base-library documents are READ IN PLACE — zero copies — so the Consequences bullet "a project export is complete without the base scope" no longer follows; a library-governed project's bundle deliberately does not contain its governing SOPs, and ADR-0125 §6's citation-closure audit pack is the completeness answer instead)
Context¶
LQMS must keep the data of different mandators (customers) and their projects strictly separate — the top quality goal ("at all cost"). At the same time, a shared QMS base (SOPs, WIs, templates of the operating company) must be usable as the starting point for every project, and base content must be developable (drafted, reviewed, released) independently of any project. A future wish is sharing custom files across projects of the same mandator.
Two intertwined questions:
- Topology: does base-QMS development need a separate system from the per-project working system?
- Enforcement: how is mandator/project separation enforced in storage?
A prior decision: when a project adopts a base document, it receives a full copy of a released base version with a provenance link (copy-on-derive) — never a live reference. Base releases never change project content implicitly; projects are notified and adopt consciously.
Options Considered¶
Option 1: Two systems — central template system + per-project replicas¶
A "central" instance for base QMS development; the system replicated per project, holding copies of the base files as active documents.
- Pros: maximal conceptual separation between base development and project work.
- Cons: the central system needs document management, life cycles, reviews, versioning, audit — i.e. LQMS itself, duplicated; replication/synchronization machinery between instances must be built and audited; N+1 deployments to operate, migrate, and back up; "which projects derive from an outdated base version" becomes a cross-system integration; search is fragmented across instances.
Option 2: One application, database per mandator¶
Single codebase, but each mandator's data in its own PostgreSQL database.
- Pros: strongest (physical) separation; per-mandator backup is a plain DB dump.
- Cons: shared base content crosses the tenant boundary by requirement — it would have to be replicated and kept in sync into every mandator database, recreating Option 1's hardest problem; operations (migrations, monitoring, connections) scale ×N; jOOQ/ connection handling per database adds complexity. Documented explicitly as the fallback position if a customer ever contractually requires physical separation.
Option 3: Single instance, scope hierarchy, row-level tenancy with RLS¶
One deployment, one database. All content lives in scopes forming a hierarchy:
Global base scope (company QMS; no mandator; derive source)
└── Mandator scope (future: customer-wide shared files)
└── Project scope (active documents and records)
Separation is enforced by mandator/project keys on every content-bearing table plus PostgreSQL Row-Level Security policies, so the database itself refuses cross-tenant access even if application code is buggy. Blob storage is partitioned per scope (ADR-0001). Copy-on-derive copies released versions downward in the hierarchy.
- Pros: no replication machinery; base development uses the same life-cycle engine as projects (it is just another scope); one backup, search, and operations story; derivation tracking is an in-database query; the mandator scope level makes future customer-wide sharing fall out of the same mechanism.
- Cons: separation is logical, not physical; RLS policies become safety-critical configuration and need dedicated tests.
Decision¶
Option 3. One LQMS codebase, one artifact, one running instance per installation. The "central system" for base QMS development is not a separate system or configuration — it is the global base scope inside the same instance.
Supporting rules:
- Scope is a uniform first-class entity. Behavioral differences between scope types
(global / mandator / project) are declarative capability flags (may be derive
source, belongs to a mandator, may hold records, …) — never
if (isGlobal)branches in business logic. - Copy-on-derive: deriving copies the full content of a released version into the
target scope as a new document with a provenance link (
derived-from: <doc> <version>). Subsequent base releases trigger notifications to derived documents, never changes. - Enforcement: tenancy keys + PostgreSQL RLS on all content-bearing tables; blob paths partitioned per scope; deduplication across scopes is explicitly sacrificed (ADR-0001).
Consequences¶
- RLS misconfiguration is the residual separation risk → entry in the risk register; mitigation: a dedicated automated separation test suite that proves cross-tenant access is impossible at the DB level, run in CI.
- The schema needs
scope(with type and capability flags) as a core table from day one; the domain model discussion must define it. - Per-project zip backup exports one scope; derived documents are self-contained copies, so a project export is complete without the base scope.
- Database-per-mandator remains the documented fallback if contractually demanded; the scope model does not preclude it, but it would reintroduce base-content replication.
- A future MCP/REST consumer is subject to the same RLS enforcement — no separate permission path.