ADR-0113: Project Arrival Import — Records Arrive as They Are, People Arrive as They Were¶
- Status: accepted (2026-08-10 late evening — "confirm. please go ahead", on the drafted
text; decisions D1a/D2a/D3a+chain/D4a/D5/D6 from his red-pen round on
docs/planning/arrival-import-options.md) - Date: 2026-08-10
- Deciders: Nicolas Burri ("go with 1. It's the better decision" — the reproduction loader never touches a real instance; "if I move a project from one server to another I would like to see the external users on the documents and a doc imported in released state should stay released"; D3 chain rule: "focus on the comala information because in the end these were the versions that got reviewed and released")
- Relates to: ADR-0111 (the substrate this consumes: EXTERNAL persons, placeholder rule, staffing-does-not-travel, §4 evidence artifact), ADR-0075 (source history — §4 amended by this ADR, see its dated amendment), ADR-0110 (the trail-truth lens applied here), ADR-0097 (the governed-surface pattern), ADR-0068 (the reproduction importer this supersedes for real instances), register #40.
Context¶
The corpus loader reproduces documents by re-driving this instance's workflows as login personas and backdating via SQL — acceptable for demo stacks, wrong for a real instance twice over: the trail then shows acts nobody meaningfully performed, and released documents are only released because this instance re-released them. ADR-0111 built the person substrate for the honest alternative and left the importer arc "next". This ADR is that arc's core.
Decision¶
- The arrival-write power is structurally caged (D1). A new GLOBAL-only-declared
permission
IMPORT_PROJECTSgates a governed import surface that writes ONLY into an import-born scope (created by the import act, or a scope with zero documents). A version born RELEASED can never appear in a scope with local history — the guard is shape, not trust. Failure recovery is structural for the same reason: dissolve the import-born scope, retry. - Version rows carry the source truth; the trail records the import (D2). An imported
version's
released_atand releaser stamp (the ADR-0110/V088 fields) carry the SOURCE date and the EXTERNAL person — the record's truth, which periodic review, "changes since" and obligations operate on. The instance trail gets oneDOCUMENT_IMPORTEDrow per document: actor = the importing operator, timestamp = now, payload = origin, source released-at, source releaser, bundle id. Nothing in the trail is ever backdated; the version row tells the record's history, the trail tells this instance's. - What arrives: the PROVEN released chain, then the head, never a guess (D3, his rule: the Comala-released versions are the versions). Per document, in evidence order:
- The head released version — proven by Comala's own pointer. The source's Comala
workflow mirrors into Confluence content properties (
cdm-status/cw-status, carryingapprovedVersion), fetchable read-only with the existing credentials (verified live 2026-08-10; 284/293 corpus pages carry the properties). - Historical released versions — imported only when the evidence validates. Two
independent signals exist and were measured on the real corpus: the Comala publish
ceremony's fingerprint in the version timeline (approve mints a same-second version
twin; the FIRST of the pair is the approved version — verified against the head
pointer), and the Jira QMSDOC
Warte auf Freigabe → Dokument in Verwendunggate passages (121 of 259 tickets carry ≥1; 172 total; the relation to Comala is loose — his "mess" confirmed: transitions lag the publish by days). The implementation builds the detector on the fingerprint with Jira as corroboration and MUST validate it against the full-corpus oracle (detector's head == Comala'sapprovedVersionfor every page carrying one) before any historical version imports. Historical bodies are fetched via the Confluence historical-version API (verified live). A release the detector cannot validate stays in the ADR-0075 §3 claims record — displayed as source history, never minted as a version. - The current source draft (where one exists beyond the released head) arrives as a DRAFT. Documents with no provable release at all arrive head-only in their honest state.
- Historical-body caveats are recorded, not hidden: Confluence does not version-pin attachments (a historical body's attachment references resolve to current binaries — the provenance record says so per version), and old bodies run the same transform with best-effort macro/diagram rendering, caveats in provenance.
- The surface is a governed REST family (D4):
/api/admin/import/*underIMPORT_PROJECTS, audited as itself; the loader shrinks to a thin client feeding the bundle (the transform's existing shape — manifest, markdown, attachments, link and history maps — minimally formalized). Content and blobs land atomically per version BEFORE it seals; the frozen-tree discipline holds from the first byte. - People and vocabulary (D5). The bundle names source actors; the import act mints-or-adopts EXTERNAL persons by the ADR-0111 natural key (origin+name slug at the reserved .invalid domain). Staffing does not travel (decided); source staffing history lands in the §4 evidence artifact attached to the import. The bundle declares the document types and element-kind activations it needs; the import act creates what is missing through the existing governed surfaces (ADR-0097/0112), each act audited as itself.
- Preview before decision (D6). A dry-run endpoint answers what WOULD be created — scope, types, kinds, persons, documents with their version chains and evidence grades, links — in the ADR-0097 D-4 proposal grammar. Per-document idempotency by human id within the import-born scope.
Amendment 1 (2026-08-10 night): the ceremony premise measured and falsified — the floor IS the shipped shape for this corpus¶
§3's historical-evidence paragraph rested on a sampled observation (same-second version
twins as the Comala publish fingerprint). The detector was built, the full-corpus oracle
run, and the premise is FALSE for this instance: all 255+25 twins name their own
non-Comala cause in their version metadata (an Atlassian macro-migration artifact and
offline-edit saves), and lastApproval — a third Comala property found during the fetch —
proves an approval mints NO page version at all (it lands days after the version it
approves). The raw rule's 89-100% oracle agreement was coincidence (the migration twin
happening to sit at the tail of approved, since-untouched pages); tuned on that evidence
the detector validates ZERO historical releases. Outcome: this corpus arrives at the §3
floor — head-only, proven by Comala's approvedVersion — and every unproven state stays
in the claims record, exactly as designed. The §3 ladder and the detector machinery stand
unchanged for sources whose fingerprint survives the same oracle. Two measured refinements
ride along: the head imports the APPROVED body (on 243 pages the approved version sits
below the current page — the released content is the version Comala approved, fetched via
the historical API), with lastApproval as the source released_at; and the current page
state arrives as a draft only when it is human work (243 drafts whose sole difference is
the macro migration are flagged draftCause: automatic-conversion and skipped, recorded in
provenance — machine noise is not a draft).
Amendment 2 (2026-08-17): the finalize pass closes a BATCH FAMILY, so cross-scope citations become edges¶
D1's cage publishes exactly ONE scope per batch, and §D4's closing re-derivation inherited that
bound. The live corpus proved the bound too tight in one specific place: MERIDIAN-QMS and PULSEMED are
one organization's quality system arriving as two projects from one source system, and PULSEMED's
bodies carry 17 references (14 distinct) INTO MERIDIAN-QMS. With one scope published those targets
resolve to nothing, so TextReferenceSync skips them in silence and they stay prose while every
within-scope citation becomes an edge — an audit pack under-collects and referencedCount reads
zero for a corpus that plainly cites across (register 2026-08-06 #41a/#41d). Nicolas ruled BUILD on
2026-08-15, choosing the family pass over accepting text-degrade as the portability truth.
Decision. finalizeBatch publishes the batch's whole FAMILY — every scope carrying a live
(OPEN or FINALIZED) batch of the same (mandator_id, source_system) — and re-derives references
over it. Four properties bound the widening:
- The family key is DERIVED, not stored.
source_systemis already the ADR-0111 origin key deciding whether two corpora's people are the same people; a family column would be a second spelling of a fact the schema carries. No migration. - Publication is wider than re-derivation. Every family scope is published, because a citation's TARGET need only be visible. The WRITING half runs over this batch's own scope and over sibling scopes the import itself CREATED — a sibling the import merely adopted is a project with a history of its own and is a legitimate target, not this act's to write into.
- Every finalize re-derives the whole family. It must: the citing corpus may finalize before the cited one exists, and FINALIZED is terminal, so its half can only ever be closed by a sibling's finalize. This makes the pass ORDER-INDEPENDENT — whichever corpus finalizes last completes the graph in both directions — and it is idempotent, so a closed scope is re-read and left alone (no second edge, no second trail row).
- The edge belongs to its SOURCE's scope, which is what V017's RLS keys on: a reader of
MERIDIAN-QMS alone never learns that an PULSEMED document cites theirs.
relationshiphas never constrained an edge's target to the source's scope, and a cross-scopereferencesedge is already a shipped, tested act — so nothing about separation changes here.
What D1 still forbids is untouched: an arrival may not write a DOCUMENT anywhere but its own scope,
the drift check still refuses a scope with a history of its own, and the operator still reaches no
content of their own. Named cut: sync removes from_text edges whose target it can no longer
see, so a family scope citing a scope OUTSIDE the family would have such an edge removed — a
mirror's inherent hazard rather than a new one (a native release runs the same sync under a
narrower set), manual edges are never touched, and no path creates such an edge today.
Explicitly out of scope¶
Export-side bundling from a live LQMS (the portability twin — its own slice); staffing migration (does not travel, decided); import UI beyond the preview payload; Comala API integration beyond the content-property mirror (the mirror suffices; their cloud API would need per-space keys and buys nothing the oracle doesn't).
Consequences¶
- Fetch grows: Comala properties per document page + historical bodies for detector-validated releases (read-only, existing credentials).
- The reproduction loader remains for local demo stacks only, clearly marked; real instances import through this surface exclusively.
- ADR-0075 §4 is amended (dated amendment there): its ban on historical bodies rested on the red line, and the red line is untouched — arrival-writes create versions without claiming lifecycle acts, which is what dissolves the ban, not a weakening of it.
- The PULSEMED space runs the same detector; where its evidence is thinner, head-only is the honest floor.
- The register #40 payoff stands: MERIDIAN-QMS + PULSEMED arrive on lqms.ch under the normal organization "Meridian" (his naming), released documents released, their people EXTERNAL and visible, the trail saying exactly one true thing.