Defensible Retention
Table of contents
- Defensible Retention
- Intent
- Summary
- Composes
- Composition logic
- Composition-level invariants
- Examples
- Walkthrough — regulated bank under SOX §802 and FRCP Rule 37(e)
- Banking — concurrent regulatory investigations under SOX
- Healthcare — HIPAA §164.530(j) records under HHS OCR investigation
- Broker-dealer — SEC Rule 17a-4 non-erasable records under FINRA examination
- E-discovery — FRCP Rule 37(e) preservation duty, disputed destruction
- Advisory-mode override — court-ordered destruction superseding a litigation hold
- GDPR Article 17 collision — erasure request vs. Legal Hold
- Regulated adversarial scenarios
- Generation acceptance
- Edge cases and explicit non-goals
- Standards references
- Status
- Lineage notes
A regulated composition: every record under management is governed by a retention window that bounds its normal purge eligibility, and by a Legal Hold mechanism that suspends that eligibility regardless of the clock. The composition wires Legal Hold, Retention Window, and Audit Trail into the single structure that makes retention defensible — provably complete, provably ordered, provably unaltered. The emergent guarantee: no record under an Active hold may be purged regardless of retention eligibility; every successful retention state transition (placement, hold placement, hold release, purge) plus every firing of the hold-blocks-purge gate is attribution-stamped, retention-bounded, and tamper-evident; and the lawful destruction of any record is provable from the composition’s records alone, without recourse to source code, runbooks, or developer narration.
Intent
A record’s life under a regulated system follows two distinct governance tracks that must coexist without one silently overriding the other. The first is the retention clock: a record must be kept for a minimum period mandated by statute, regulation, or contract — seven years under SOX, six years under HIPAA, three years for some broker-dealer communications under SEC Rule 17a-4. The second is the preservation directive: when litigation is anticipated, when a regulator opens an investigation, when an audit freeze is ordered, the normal retention clock stops being the governing rule. The obligation shifts to keep this record until the legal matter resolves, regardless of what the schedule says.
Neither Retention Window nor Legal Hold, alone, enforces this coexistence. Retention Window enforces the clock — it prevents purge before retention_until and records the eligibility transition. Legal Hold records the preservation obligation — it documents who placed the hold, why, and when. But neither atom enforces the other’s constraint. A record with an elapsed retention window is eligible for RetentionWindow.purge without any knowledge of whether a Legal Hold is active. A record with an Active hold is recorded as preserved but the Legal Hold atom does not intercept a purge call issued against the Retention Window. The gate that enforces no purge while any Active hold covers a record belongs to the composition — and this composition is that gate.
Audit Trail provides the third leg: every hold placement, every release, every retention placement, and every purge is attribution-stamped, retention-bounded, and tamper-evident. The destruction of any record is defensible because the evidence trail — who held it, who released the hold, who purged it, under what policy, at what time — is itself a regulated, integrity-protected record that can be verified from the records alone.
This is a composition, not a new primitive. Legal Hold, Retention Window, and Audit Trail are unchanged; the composition is the wiring that makes them coherent as a single retention-governance surface. The construction is what every records-management system under SOX, HIPAA, FRCP Rule 37(e), or SEC Rule 17a-4 implements — but rarely names as a composable concept with explicit structural invariants. This composition retires the forthcoming-link debt in Audit Trail’s edge case Legal hold suspension of purge, which named this composition as the resolution: it is now landed.
Summary
Defensible Retention is a regulated composition (a spec that wires two or more atoms — freestanding, self-contained pattern specs — together) that solves a specific problem that neither its constituent atoms nor Audit Trail alone can solve: ensuring that a record under an active Legal Hold (a legally mandated preservation order suspending normal deletion) cannot be purged even after its retention window (the bounded period during which records must be kept) has elapsed, and that every destruction event is provably compliant to an external auditor from the records alone. It wires three constituents: Legal Hold (which records who placed a preservation directive, on which record, and why), Retention Window (which records the minimum lifetime a record must be kept and enforces that no record is deleted before that window closes), and Audit Trail (the tamper-evident — designed so unauthorized changes are detectable — regulated-audit substrate that attribution-stamps and seals every governance decision).
The composition’s defining emergent invariant (a property that only appears when atoms are combined — no single atom carries it) is the hold-blocks-purge gate: no record with at least one Active hold may be purged through this composition’s surface under strict mode, regardless of whether the retention clock has run out. Neither Legal Hold nor Retention Window enforces this rule alone — Legal Hold records the obligation but does not intercept purge calls; Retention Window enforces the clock but has no knowledge of the hold store. The gate lives in the composition’s wiring, between the hold check and the purge call, and it fires an Audit Trail event both when it passes (a record_purged event carrying hold_check_result: empty) and when it blocks (a purge_blocked_by_hold event carrying the blocking hold identifiers). An external auditor can read those two event classes and verify, from the records alone, both that every destruction was hold-clear and that every hold-blocked attempt was recorded — the full dual-sided behavior of the gate is visible in the records.
Beyond the gate, the composition guarantees that every hold placement, hold release, retention placement, and purge decision is attribution-stamped and tamper-evident through the Audit Trail substrate. It also guarantees defensible destruction (deletion that can be proven compliant to an auditor — all holds cleared, retention satisfied): every successfully purged record has, in the records, an Audit Trail purge event with hold_check_result: empty, a Retention Window record in Purged state, and a tamper-evident seal — the three elements an auditor needs to confirm the destruction was lawful, attributed, and unaltered.
This composition is grounded (passed all required review passes and is stable enough to generate from). Its most common uses are financial records governance under SOX (Sarbanes-Oxley Act) §802, patient records under HIPAA (US Health Insurance Portability and Accountability Act) §164.530(j), electronically stored information subject to FRCP (Federal Rules of Civil Procedure) Rule 37(e) litigation holds, and broker-dealer communications under SEC Rule 17a-4. Any system that must prove it did not destroy records while a legal or regulatory hold was active — and that it did eventually destroy them once the hold was released and the retention window had closed — is a candidate for this composition.
Composes
- Legal Hold — provides the preservation directive: a named, actor-issued hold on a record that suspends purge eligibility regardless of the retention window’s clock. The composition maintains exactly one Legal Hold store instance. The
place,release, andreadAPI are used directly; the composition wrapsplaceandreleasewith Audit Trail recording. - Retention Window — provides the policy-bounded record lifetime:
place_under_retentionstarts the retention clock for a business record;purgedestroys the record after the clock runs out. The composition maintains exactly one Retention Window instance governing the business records under management (distinct from the Retention Window instance inside Audit Trail, which governs audit events). The hold-check gate is inserted ahead of everypurgedelegation. - Audit Trail — the regulated-audit substrate. Every hold placement, hold release, retention placement, and purge decision is recorded as one
record_actioncall on the Audit Trail instance, producing an Event Log entry, an Actor Identity attestation, a Retention Window record (for the audit event), and a Tamper Evidence seal per the cadence. The application maintains exactly one Audit Trail instance configured with the host’s regulatory retention policy for audit events.
The Event Log, Actor Identity, Retention Window (for audit events), and Tamper Evidence atoms named in the roadmap entry for this composition are reached transitively through Audit Trail; the application does not maintain separate instances of those atoms at this layer. The Retention Window instance named in Composes above governs the business records; the Retention Window instance inside the Audit Trail substrate governs the audit events recording those decisions. The two instances are distinct and their retention policies may differ — audit records should persist at least as long as the business records they describe, and often longer.
Composition logic
Application state
The composition owns emergent state that wires the three constituent elements into one queryable retention-governance surface:
record_to_retentions— map fromrecord_refto the set ofretention_ids covering that record in Retained state. A record may be under multiple concurrent Retained retentions (policy-transition scenarios; see Retention Window’s edge case on concurrent retentions for the samerecord_ref). Entries are added byplace_record_under_retentionand removed bypurge_recordon successful purge; when the set becomes empty therecord_refkey is removed. This is the auditor’s first query surface for determining the governance status of any record.retention_to_record— map fromretention_idto{record_ref, retention_until, purge_deadline}. Required for the hold-check gate inpurge_record— given aretention_id, the composition must know whichrecord_refto check for Active holds before delegating toRetentionWindow.purge. Also providesretention_untilandpurge_deadlinetopurge_eligible()without requiring a Retention Window read API; Retention Window computes both atplace_under_retentiontime and exposes them as properties of the retention record (see Retention Window’s Outputs — “For each retention:retention_id,record_ref,policy_ref,retained_at,retention_until,purge_deadline“), even though the atom-level action signature abbreviates toretention_id. Entries are added byplace_record_under_retentionand removed bypurge_recordon successful purge.
The hold store is owned by the Legal Hold instance; the composition queries it via LegalHold.read({record_ref: X, state: Active}) to evaluate purge eligibility. The retention store and seal store are owned by their respective constituent instances. The Audit Trail emergent state (event_to_attestation, event_to_retention, seal_coverage, sealed_through) is owned by the Audit Trail substrate.
Configuration
audit_trail_retention_policy— the policy reference passed to the Audit Trail instance at eachrecord_actioncall. The audit trail of a retention or hold decision should persist at least as long as the business record it describes, and often longer (for litigation defensibility after the business record is purged). The deployment configures this separately from the business record retention policies. Multi-jurisdiction policy reconciliation — selecting the longer of competing obligations (HIPAA + state law, SOX + GDPR (EU General Data Protection Regulation)) — is out of scope; a Policy Reconciliation composing pattern produces the reconciledpolicy_refvalues this composition consumes.hold_check_mode—strict(default) oradvisory. Understrict, a non-empty Active hold set causespurge_recordto returnrejected(under-legal-hold). Underadvisory, the hold presence is recorded in the Audit Trail event withhold_override = trueand purge proceeds — only valid where a deployment has an external authority that can authorize override (e.g., court-ordered destruction superseding a litigation hold). Deployments operating under FRCP Rule 37(e), SEC Rule 17a-4, or SOX must not configureadvisory; thestrictdefault is the required posture for those regimes.
Primitive policies
The composition takes string-typed inputs at its action boundaries; each is validated either at this layer or by a constituent.
record_ref— opaque byte-identity, as established in the Edge cases entry onrecord_refidentity. The composition does not case-fold, normalize, or trim. Legal Hold’s atom-levelplacerequires at least one non-whitespace character; this composition’splace_record_under_retentionandplace_holdpropagate the constituent’sinvalid-requestrejection without imposing additional rules.policy_ref— opaque token interpreted by the Retention Window’s Policy Registry. Composition surfacesinvalid-requestfor malformed values;invalid-policyandpolicy-not-foundfrom the constituent are mapped toinvalid-request(perplace_record_under_retentionstep 1).actor_ref,placed_by,released_by— opaque actor identifiers. The constituent’s non-whitespace requirement applies (Legal Hold’splaceandreleaseforplaced_by/released_by; Audit Trail’s Actor Identity for all three). Empty / whitespace-only values are rejected withinvalid-request.credential— opaque credential material consumed only byAuditTrail.record_action. Composition does not inspect; Actor Identity surfacesinvalid-credential(mapped toinvalid-requestat this composition’s boundary where it surfaces at all).reason,release_reason,hold_reason— free-form strings. Constituents require at least one non-whitespace character (Legal Hold’splaceandrelease). No length cap is imposed by this composition; deployments needing one wire it into the calling layer.case_ref— opaque optional reference. When supplied, must contain at least one non-whitespace character (Legal Hold’splacerequirement). Absent is valid.placed_at,released_at— optional caller-supplied timestamps. Constituent rules apply (must not be in the future when caller-supplied;released_at ≥ placed_atafter resolution). The composition does not validate further; backdatedplaced_atis the subject of its own Edge cases entry.
No primitive is case-sensitivity-normalized at the composition layer; deployments wanting normalization wire it at the calling layer before invoking composition actions.
Action wiring
The composition exposes five orchestrating actions. Every successful state-changing action records in Audit Trail; in addition, the purge_record strict-mode rejection on under-legal-hold records a gate-firing event in Audit Trail. Hold-status queries pass through to Legal Hold’s read without Audit Trail recording — they are read-only and produce no state change. Other rejection paths (not-known, not-eligible, recording-failure, invalid-request from any action) produce no Audit Trail event at this layer; Failed attribution attempts on non-gate rejections in Edge cases bounds this scope.
place_record_under_retention(record_ref, policy_ref, actor_ref, credential) → retention_id | rejected(invalid-request | recording-failure)RetentionWindow.place_under_retention(record_ref, policy_ref)→retention_idon success (or reject:invalid-requestpropagated directly;invalid-policyandpolicy-not-foundmapped toinvalid-request— from the caller’s perspective both indicate a bad policy reference;storage-failuresurfaced asrecording-failure). On success, the composition readsretention_untilandpurge_deadlinefrom the just-created retention record (Retention Window’s Outputs section lists both as properties of the record, even though the atom-level action signature abbreviates toretention_id); these are composition-internal values used in steps 2 and 3 and are not returned to the caller ofplace_record_under_retention.- Record
record_to_retentions[record_ref] ∪= {retention_id}andretention_to_record[retention_id] = {record_ref, retention_until, purge_deadline}. AuditTrail.record_action(action_ref=retention_placed, actor_ref, credential, data={record_ref, retention_id, policy_ref, retention_until, purge_deadline}, retention_policy=audit_trail_retention_policy)→event_id. If this call fails after step 1 succeeded, returnrejected(recording-failure); the implementation must address the orphan retention record per the Cross-store consistency under failure edge case.- Return
retention_id.
place_hold(record_ref, placed_by, credential, reason, case_ref?, placed_at?) → hold_id | rejected(invalid-request | recording-failure)LegalHold.place(record_ref, placed_by, reason, case_ref?, placed_at?)→hold_id(or propagateinvalid-request;storage-failuresurfaced asrecording-failure).AuditTrail.record_action(action_ref=hold_placed, actor_ref=placed_by, credential, data={hold_id, record_ref, reason, case_ref, placed_at}, retention_policy=audit_trail_retention_policy). If this call fails after step 1 succeeded, returnrejected(recording-failure)and flag the orphan per Partial recording on step failure in Edge cases.- Return
hold_id.
Note on credential. Legal Hold’s atom-level
placetakesplaced_byas an opaque reference; the composition takes an additionalcredentialat the application boundary and passes it toAuditTrail.record_actionfor identity verification via Actor Identity. The atom records the opaque reference; the Audit Trail binds it cryptographically. This mirrors how Audit Trail’srecord_actionhandles the actor-credential pairing.release_hold(hold_id, released_by, credential, reason, released_at?) → released | rejected(invalid-request | not-known | already-released | recording-failure)LegalHold.release(hold_id, released_by, reason, released_at?)→released(or propagate rejections;storage-failuresurfaced asrecording-failure).AuditTrail.record_action(action_ref=hold_released, actor_ref=released_by, credential, data={hold_id, release_reason: reason, released_at}, retention_policy=audit_trail_retention_policy). Same orphan-state handling asplace_holdstep 2.- Return
released.
purge_eligible() → list of (retention_id, record_ref, retention_until, purge_deadline, hold_count) tuples- Iterate over all entries
(retention_id, {record_ref, retention_until, purge_deadline})inretention_to_record. Becausepurge_recordremoves entries on success (step 6), all entries in this map are in Retained state; no additional state-filter against Retention Window is required. - For each entry, evaluate
now ≥ retention_until. Entries where this is false are under active retention obligation and excluded from the result. - For each entry where
now ≥ retention_until: callLegalHold.read({record_ref: record_ref, state: Active})→active_holds. Sethold_count = |active_holds|. - Return the full list of
(retention_id, record_ref, retention_until, purge_deadline, hold_count)tuples. Records wherehold_count = 0are purge-ready; records wherehold_count > 0are hold-blocked. Both classes are returned; the caller’s compliance dashboard distinguishes them. Includingpurge_deadlinein the tuple lets the dashboard further distinguish records within the purge window (retention_until ≤ now < purge_deadline) from records past the purge deadline (now ≥ purge_deadline) — the latter being the GDPR Article 5(1)(e) overshoot signal. The hold-blocked list is a compliance signal: those records have passed their retention window but are being lawfully preserved under active holds.
- Iterate over all entries
purge_record(retention_id, actor_ref, credential) → ok | rejected(not-known | not-eligible | under-legal-hold | recording-failure)- If
retention_idnot inretention_to_record, returnnot-known. - Look up
record_ref = retention_to_record[retention_id]. hold_check = LegalHold.read({record_ref: record_ref, state: Active}). This is the override-retention-on-hold gate. If the result is non-empty understrictmode, the gate fires: emit an Audit Trail event viaAuditTrail.record_action(action_ref=purge_blocked_by_hold, actor_ref, credential, data={retention_id, record_ref, hold_check_result: {hold_ids: [...], count: N}, purged_at: null, outcome: rejected}, retention_policy=audit_trail_retention_policy)and returnrejected(under-legal-hold)to the caller — the rejection data includes the count andhold_ids of the blocking holds. The gate-firing audit is load-bearing: it makes the gate’s firing (not just its passing) visible in the records, which Generation acceptance check 1’s symmetric reading depends on. If this audit call itself fails, returnrejected(recording-failure)and flag the orphan per Partial recording on step failure — the record is not purged either way (step 4 is not reached) so no destruction occurs without audit. If the result is non-empty underadvisorymode, proceed to step 4 withhold_override = truerecorded in step 5 (nopurge_blocked_by_holdevent under advisory mode; the override pathway is the audited path). The hold check is performed regardless of whethernow ≥ retention_untilhas elapsed; a record under an Active hold cannot be purged via this composition even if it is also past its retention window.RetentionWindow.purge(retention_id)→ok(or propagatenot-retainedandnot-knownasnot-known;retention-period-not-elapsedasnot-eligible;storage-failureasrecording-failure).AuditTrail.record_action(action_ref=record_purged, actor_ref, credential, data={retention_id, record_ref, hold_check_result, hold_override, purged_at: now}, retention_policy=audit_trail_retention_policy). Thehold_check_resultfield shape is defined independently of mode:emptywhen the step-3 hold check returned an empty sequence, and{hold_ids: [...], count: N}when non-empty — observable in the records as the literal state of the hold store at gate time. Thehold_overridefield istrueonly when the configuration wasadvisoryand the hold check was non-empty (advisory mode with empty holds recordshold_override: false, matching strict mode with empty holds); under strict mode it is alwaysfalse(the non-empty case never reaches step 5). The four combinations: strict + empty →empty / false; strict + non-empty → does not reach step 5 (gate rejects at step 3, audited via thepurge_blocked_by_holdevent emitted there); advisory + empty →empty / false; advisory + non-empty →{hold_ids: [...], count: N} / true. The pair is the auditable proof of what the hold store contained at purge time and of whether the mode treated that as a block or an override.- Remove
retention_idfromretention_to_record. Removeretention_idfromrecord_to_retentions[record_ref]; if that set is now empty, remove therecord_refkey. - Return
ok.
- If
The load-bearing wiring decision — override-retention-on-hold
The composition’s structural reason to exist: a record under an Active Legal Hold cannot be purged regardless of whether its retention window has elapsed. This rule sits in purge_record step 3 — between the hold check and the RetentionWindow.purge call — and is the gate neither constituent atom can enforce alone.
Principle: Retention Window’s retention-period-not-elapsed precondition prevents premature purge but has no knowledge of Legal Hold’s state. Legal Hold records preservation obligations but does not intercept purge calls. Without the composition wiring the two checks together, a system can lawfully (from each atom’s perspective) purge a record that is under an active litigation hold — a spoliation (destruction of evidence subject to a preservation duty) exposure that is structurally invisible to either atom alone.
Likely objection: Why not have Legal Hold intercept RetentionWindow.purge directly, rather than needing a composition?
Mechanism that resolves it: Legal Hold is a freestanding atom. If it imported Retention Window semantics and intercepted purge calls, it would know about storage layers, retention records, and purge mechanisms — absorbing concerns that belong to the composing layer and breaking its freestanding status. The atom records the obligation; the composition enforces it. This discipline was architected deliberately: Legal Hold’s Composition notes named this composition as the forthcoming resolution; Retention Window’s edge case on Legal hold named Legal Hold as the composing pattern that intercepts purge; Audit Trail’s edge case Legal hold suspension of purge named this composition as the landing point. This composition lands all three forthcoming-links simultaneously.
Result: The gate is structural. It cannot be misconfigured away under strict mode; it is not a runtime check that a well-intentioned operator bypasses by going directly to RetentionWindow.purge. The Audit Trail purge event records hold_check_result: empty — the auditable proof that the gate passed. An auditor can verify, from the records alone, that no Active holds existed at the time of any purge.
Composition-level invariants
These invariants (conditions that must always hold) emerge from the composition. None belongs to a single constituent; each requires two or more working together to hold.
-
Invariant 1 — Hold-blocks-purge. For every record
rcovered by at least one Active hold at the time of apurge_record(retention_id)call, the call returnsrejected(under-legal-hold)andRetentionWindow.purgeis not invoked understrictmode. No record withLegalHold.read({record_ref: r, state: Active})returning a non-empty sequence transitions to Purged state via this composition understrictmode. This is the composition’s defining emergent invariant — it cannot be derived from Legal Hold (which does not intercept purge calls) or from Retention Window (which does not consult the hold store) alone.Defended in-line. The check in
purge_recordstep 3 evaluatesLegalHold.readbeforeRetentionWindow.purge; a non-empty result is a hard rejection understrictmode. The Audit Trail purge record carrieshold_check_result: empty— the auditor verifies from the record that the gate passed. The one structural gap: a race condition where a hold is placed between the check and the purge call (see Concurrent hold placement and purge in Edge cases); named as an explicit out-of-scope and a deployment-serialization obligation. -
Invariant 2 — Retention coverage. Every business record placed under management by this composition has a corresponding Retention Window record in Retained or Purged state.
retention_to_recordis populated only onplace_record_under_retentionsuccess; a record not placed via this composition is not in the map and is not subject to the hold-check gate. -
Invariant 3 — Hold audit coverage. Every successful
place_holdandrelease_holdcall produces an Audit Trail event carrying thehold_id,record_ref, and actor attribution. The full hold lifecycle — placement to release — is reconstructible from the Audit Trail and is tamper-evident via the Audit Trail substrate’s seal coverage. -
Invariant 4 — Retention-decision audit coverage. Every successful
place_record_under_retentionand every successfulpurge_recordcall produces an Audit Trail event (retention_placedandrecord_purgedrespectively). Everypurge_recordcall that rejects under strict mode withunder-legal-holdproduces an Audit Trail event (purge_blocked_by_hold) — the load-bearing gate firing is recorded, not silent. The successful purge event carrieshold_check_result: empty; the gate-firing event carrieshold_check_result: {hold_ids: [...], count: N}. Together, the two event classes give an auditor both halves of the gate’s behavior — the passings (in the records) and the blockings (also in the records). Other rejection paths (not-known,not-eligible,recording-failure,invalid-request) are not audited at this composition’s layer; Failed attribution attempts on non-gate rejections in Edge cases names the bounded scope. -
Invariant 5 — Audit completeness modulo Audit Trail’s partial-attestation contract. Every state-changing action produces exactly one
AuditTrail.record_actioncall. The invariant inherits Audit Trail’s atomicity surface: if Actor Identity’sattest(insiderecord_action) succeeds but Event Log’sappendfails, an orphan attestation exists without a corresponding event-log entry. This composition does not re-derive Audit Trail’s orphan-resolution discipline; it inherits it and requires the implementation to surface and resolve any orphan attestation referencing this composition’saction_refvalues. Invariant 5 holds modulo Audit Trail’s own partial-attestation contract, as established for regulated compositions in Multi-Party Approval’s Invariant 5. -
Invariant 6 — Non-retroactivity of holds. A hold placed after a successful
purge_recorddoes not alter the Retention Window record (terminal in Purged state, by Retention Window’s Invariant 3), does not remove the Audit Trail purge event (immutable — unchangeable once written — by Event Log’s Invariant 2), and does not change the composition’s assessment of the purge’s legality. The hold creates a new Legal Hold record withplaced_atpostdatingpurged_at. The records faithfully document the chronology; legal counsel and the court assess the consequences. -
Invariant 7 — Multi-hold independence. When N Active holds cover a record, releasing any subset does not make the record purge-eligible unless zero Active holds remain.
purge_eligible()lists a record as hold-blocked wheneverhold_count ≥ 1.purge_recordreturnsunder-legal-holdif any Active hold remains, regardless of count. Legal Hold’s Invariant 4 (concurrent holds are independent) is the constituent guarantee; this invariant names the aggregate consequence at composition level. -
Invariant 8 — Defensible destruction. Every record successfully purged via
purge_recordhas, in the composition’s records: (a) an Audit Trail purge event withhold_check_result: empty, evidencing that the hold gate passed; (b) a Retention Window record in Purged state withpurged_at ≥ retention_until(Retention Window Invariant 8); (c) the Audit Trail event carrying the purge attribution and timestamp under a tamper-evident seal. An external auditor can verify all three from the records alone without recourse to source code, runbooks, or developer narration.
Attribution coverage (Invariants 3 and 4) plus hold-blocks-purge (Invariant 1) plus defensible destruction (Invariant 8) together give the provable lawfulness property — every destruction is either blocked or has auditable proof of authorization. Multi-hold independence (Invariant 7) and non-retroactivity (Invariant 6) close the lifecycle coherence.
Examples
Walkthrough — regulated bank under SOX §802 and FRCP Rule 37(e)
A multinational bank uses this composition to govern its general-ledger transaction records. Configuration: business_record_retention_policy = sox_7_year, audit_trail_retention_policy = sox_9_year, hold_check_mode = strict.
-
Retention placed.
place_record_under_retention(record_ref="txn-2026-0441", policy_ref="sox_7_year", actor_ref="records_system", credential)→retention_id = ret-0441.retention_until = 2033-05-10. Audit Trail records the placement. -
Litigation anticipated. Three years later:
place_hold(record_ref="txn-2026-0441", placed_by="counsel_morgan", credential, reason="Litigation hold — anticipated class action re Q3 2026 operations", case_ref="matter-2029-morgan")→hold_id = hold-0441-a. Audit Trail records the hold placement. -
Retention window elapses. In 2033,
purge_eligible()returnstxn-2026-0441in the hold-blocked list:hold_count = 1,now ≥ retention_until. A records-management system that attempts purge anyway —purge_record(retention_id="ret-0441", actor_ref="records_system", credential)— receivesrejected(under-legal-hold)with the blockinghold_ids in the rejection data. The composition does not purge. The compliance dashboard surfaces the hold-blocked status. -
Litigation settles.
release_hold(hold_id="hold-0441-a", released_by="counsel_morgan", credential, reason="Class action settled — May 2033")→released. Audit Trail records the release. -
Purge proceeds. Next
purge_eligible()run returnstxn-2026-0441as purge-ready:hold_count = 0.purge_record(retention_id="ret-0441", actor_ref="records_system", credential)→ok. Retention Window record moves to Purged. Audit Trail records the purge withhold_check_result: empty, purged_at = 2033-05-15. -
SOX §404 audit. The auditor queries Audit Trail and the hold store. Full lifecycle: retention placed → hold placed → hold released → purge.
verify_recordon each Audit Trail event returnsverified. The auditor confirms: (a) no purge occurred while the hold was Active; (b) purge occurred within the allowable window after hold release; (c) purge was attributed to a named actor under a verified credential. Defensible destruction proven from the records.
Banking — concurrent regulatory investigations under SOX
A bank faces simultaneous DOJ criminal and SEC civil enforcement. Both issue preservation demands for the same trading records. Two independent hold sets are placed: hold-doj-* (case_ref: "doj-crim-2026-0011") and hold-sec-* (case_ref: "sec-enf-2026-0087"). When DOJ closes, all hold-doj-* holds are released. purge_eligible() shows the records as hold-blocked (hold_count = 1 — SEC holds remain). Invariant 7 guarantees that releasing the DOJ holds had no effect on the SEC holds. Only after the SEC holds are released do the records become purge-ready.
Healthcare — HIPAA §164.530(j) records under HHS OCR investigation
A hospital places patient encounter records under a 6-year HIPAA retention policy. HHS OCR opens a breach investigation; compliance places holds under case_ref: "ocr-hipaa-inv-2026-0334". The 6-year windows elapse during the investigation; purge_eligible() consistently lists the affected records as hold-blocked. After OCR closes the investigation, holds are released and records are purged in the next eligible run. The Audit Trail for each record shows the complete arc: retention placed, OCR hold placed, OCR hold released, purge executed post-hold. An OCR auditor sees a tamper-evident lifecycle confirming preservation obligations were honored throughout.
Broker-dealer — SEC Rule 17a-4 non-erasable records under FINRA examination
A broker-dealer places all business communications under a 7-year retention policy per SEC Rule 17a-4. When a FINRA examination is announced, holds are placed on all in-scope records under case_ref: "finra-exam-2026-0112". During the exam, purge_eligible() returns the in-scope records as hold-blocked; no records are purged. After the exam closes, holds are released; records with elapsed windows are purged in the post-exam run. The Audit Trail provides the WORM-equivalent decision record the examination requires; the composition’s Tamper Evidence (via Audit Trail) satisfies Rule 17a-4’s integrity requirements.
E-discovery — FRCP Rule 37(e) preservation duty, disputed destruction
Litigation is anticipated. Counsel places holds on email records under scope email-project-alpha-*. Months later, opposing counsel moves for sanctions under FRCP Rule 37(e), claiming documents were destroyed after the duty arose. The defense queries the Audit Trail for record_purged events in the disputed scope. Each purge event either (a) has purged_at predating the preservation duty trigger date — lawful destruction before the duty arose — or (b) has no corresponding hold placed before the destruction — a finding if the duty had already attached. For every record where a hold was placed before any destruction, the Audit Trail shows hold_placed before any record_purged event, and purge_eligible() at the time would have listed the record as hold-blocked. The composition’s records answer the spoliation question structurally, without developer testimony.
Advisory-mode override — court-ordered destruction superseding a litigation hold
A pharmaceutical company has email records under FRCP litigation holds (hold_check_mode = strict is the default). A separate federal court issues a destruction order — narrowly scoped to records containing trade-secret formulations of a discontinued product line — that supersedes the preservation duty in the unrelated litigation matter. Compliance counsel obtains the court’s written order, files the override authorization in their external case-management system, and (per the deployment’s organizational policy and the Permissions composing pattern) reconfigures hold_check_mode = advisory for the scoped purge run only, with the destruction order’s docket number recorded as the override authorization reference.
For each in-scope record: purge_record(retention_id="ret-rx-2018-…", actor_ref="counsel_walsh", credential) proceeds past the non-empty hold check, destroys the record via RetentionWindow.purge, and records the Audit Trail event with hold_check_result: {hold_ids: ["hold-litig-2024-…"], count: 1}, hold_override: true, purged_at: 2026-05-13T…. The blocking hold itself remains Active — the composition does not auto-release it (per the Advisory-mode purge leaves a hold Active over a destroyed record edge case); the underlying preservation obligation in the litigation matter has not ended, and the court order narrowed this record’s fate without ending the obligation. The compliance dashboard surfaces the discrepancy: an Active hold over a Purged record. Counsel separately reviews whether each affected hold should be released; if released, the release_hold call records the reason narrating the court order’s effect.
For the audit: the Audit Trail event’s hold_override: true is the records-alone signal of the override; the externally-clearable check in Generation acceptance verifies the court order’s authorization (the composition records the fact of the override, not its authorization). After the scoped run, the deployment restores hold_check_mode = strict — the default posture under FRCP exposure. The example demonstrates the only configuration in which advisory mode is appropriate: a deployment with an external authority that can produce written, auditable authorization for each override.
GDPR Article 17 collision — erasure request vs. Legal Hold
A data subject in the EU invokes GDPR Article 17 right to erasure. Before executing, the system queries this composition:
If LegalHold.read({record_ref: subject_record, state: Active}) returns non-empty, the erasure is deferred. GDPR Article 17(3)(e) explicitly exempts data necessary for the establishment, exercise, or defence of legal claims; the Active hold is the operational record establishing that exception. The system records the deferral in the Audit Trail, citing the blocking hold_ids, and notifies the data subject that the request has been received but is suspended while a legal hold is active.
If no Active holds exist but now < retention_until, the record is under an active retention obligation that may override the erasure right — HIPAA §164.530(j) and SOX §802 retention requirements are legal obligations under Article 17(3)(b). The system records the retention-obligation ground in the Audit Trail.
If no Active holds and now ≥ retention_until, purge_record proceeds.
In all three branches, the Audit Trail event is the evidence of the system’s decision and the regulatory reasoning. The composition does not adjudicate the legal question — it records the state of the atoms at decision time, giving legal counsel the evidence needed to defend any path taken.
Regulated adversarial scenarios
Regulator audit — “prove no record under hold was destroyed during the examination window.”
An SEC examiner queries the Audit Trail for all record_purged events during the examination period. For each, the examiner reads the hold_check_result field from the event data — empty confirms the gate passed — and cross-references with LegalHold.read({record_ref: X, state: Active, placed_at: {before: purged_at}}) to confirm no hold placed before the purge was still Active at purge time. Invariant 1 (hold-blocks-purge) and Invariant 8 (defensible destruction) are the structural guarantees. The examiner consults no source code or runbooks; every claim is verified from the records.
Disputed destruction — GDPR erasure request collides with Legal Hold.
A data subject’s representative challenges the system’s deferral of their Article 17 erasure request: “was there really a hold on this record?” The system presents LegalHold.read({record_ref: R}) — returning the Active hold with placed_by, hold_reason, placed_at, and case_ref, all immutable by Legal Hold’s Invariant 1. The Audit Trail entry for the deferral carries the hold_id as the stated reason; AuditTrail.verify_record(event_id, payload) → verified confirms the event has not been altered. Legal Hold’s Invariant 7 (placement attribution is complete) — placed_by and hold_reason each contain at least one non-whitespace character — ensures the hold record is not anonymously placed. The challenge cannot be sustained without claiming the entire hold store was fabricated, at which point Tamper Evidence’s seal (via Audit Trail) is the structural rebuttal.
Breach forensics — “was a preservation-deferred record improperly purged?”
During an incident investigation, the team suspects a held record was purged outside this composition’s gate. They query the Audit Trail for record_purged events for the suspect record_ref. If none exist, the record was not purged via this composition — the team investigates direct Retention Window or storage-layer access (a composition-bypass finding). If a record_purged event exists, they read its hold_check_result field and verify via LegalHold.read({record_ref: X}) that the hold store confirmed empty Active holds at purge time. AuditTrail.verify_record(event_id, payload) → verified confirms the event’s tamper-evident integrity. If the purge was preceded by a hold_released event with the same record_ref, the full arc is in the Audit Trail: hold placed, hold released, purge executed. The forensic window is bounded by the Audit Trail’s seal cadence.
Generation acceptance
A derived implementation of Defensible Retention is acceptable — in the regulator-acceptance sense — when an external auditor, given the composition’s emergent state plus the three constituent stores, can do all of the following without recourse to source code, runbooks, or developer narration.
Audit-Trail-traversal-clearable checks
These checks require reading the Audit Trail substrate (part of the composition’s records) in addition to the Legal Hold and Retention Window stores:
-
Hold-blocks-purge verification. For every
record_purgedAudit Trail event, confirm thehold_check_resultfield isempty(orhold_override = truewith appropriate external authorization foradvisorymode deployments). Cross-reference withLegalHold.read({record_ref: X, placed_at: {before: purged_at}, state: Active})to confirm no hold placed before the purge was still Active at the Audit Trail event’spurged_at(the authoritative system-record-of-destruction timestamp per the Dualpurged_atsemantics edge case). Invariant 1 is the contract; this check verifies it across the full purge record set. A singlerecord_purgedevent withhold_check_resultshowing non-empty Active holds (understrictmode) is a conformance failure. Symmetric reading: for everypurge_blocked_by_holdAudit Trail event, confirm thehold_check_resultfield contains a non-empty{hold_ids: [...], count: N}payload and cross-reference each namedhold_idto a then-Active hold in the Legal Hold store at the event’s timestamp. Together the two event classes give the auditor both halves of the gate’s behavior — passings viarecord_purged(the gate let the call through) and firings viapurge_blocked_by_hold(the gate refused). Apurge_blocked_by_holdevent whosehold_check_resultcannot be matched to then-Active holds is itself a conformance failure. -
Hold audit coverage. For every hold in the Legal Hold store (Active or Released), confirm a corresponding
hold_placedAudit Trail event exists. For every Released hold, confirm a correspondinghold_releasedAudit Trail event exists.AuditTrail.verify_recordon each event confirms tamper-evidence. Invariant 3 is the contract. -
Retention-decision audit coverage. For every retention record in the Retention Window store (Retained or Purged), confirm a corresponding
retention_placedAudit Trail event exists. For every Purged retention, confirm a correspondingrecord_purgedAudit Trail event exists withhold_check_result: empty. Invariant 4 is the contract. -
Forensic completability. For any
retention_id, reconstruct the complete governance lifecycle from the records:retention_placedevent (Audit Trail), associated hold events (Legal Hold store joined viarecord_ref), and — if purged — therecord_purgedevent with hold-check confirmation. Invariant 8 is the structural guarantee; this check verifies the reconstruction is complete and consistent. -
Constituent Generation acceptance bars. Verify each constituent’s own Generation acceptance bar over its respective store instance: Legal Hold’s six checks, Retention Window’s five checks, Audit Trail’s six checks. The composition’s invariants depend on the correctness of the constituents’ invariants.
Externally-clearable checks
These audit questions arise around this composition but cannot be answered from the composition’s records alone:
- Whether the retention policy was correctly chosen. The composition records the
policy_refapplied atplace_record_under_retentiontime. It does not verify that the declared policy is the correct policy for the record type under the deployment’s regulatory obligations. Verification requires the deployment’s Policy Registry or the applicable regulation. This parallels Retention Window’s own named out-of-scope on policy registry management and Multi-Party Approval’s named out-of-scope on approver-set policy verification. - Whether the hold authority was properly granted. The composition records
placed_byand verifies the identity via Audit Trail’s Actor Identity attestation. It does not verify thatplaced_bywas authorized under the deployment’s organizational policy to place holds. Verification requires a Permissions instance scoped to hold-placement authority — a composing pattern, not a constituent of this composition. - Whether
advisorymode override purges were properly authorized. Underadvisorymode,record_purgedevents withhold_override = trueare records of overrides. Verification that each override was properly authorized by a competent authority (e.g., a court order) requires external authorization documentation; the composition records the fact of the override, not its external authorization.
Edge cases and explicit non-goals
-
Multi-jurisdiction policy reconciliation. When HIPAA, state law, SOX, and GDPR all apply to one record, selecting the governing retention policy is a Policy Reconciliation composing pattern concern. This composition takes the reconciled
policy_refas input; it does not adjudicate competing obligations. -
Mass purge under hold release. When a Legal Hold covering hundreds of records is released, those records enter the purge-ready list on the next
purge_eligible()run; they do not automatically purge. The caller’s records-management system iterates and callspurge_recordfor each. Atomic batch purge — all records in a scope purged or none — requires a transaction wrapper in the composing layer. -
Concurrent hold placement and purge. A race condition where a hold is placed after
purge_recordperforms the hold check (step 3) but beforeRetentionWindow.purgeexecutes (step 4) can result in a record being purged while a hold exists — a structural violation of Invariant 1. Implementations must serialize the hold-check-and-purge sequence on a givenrecord_ref. Serialization mechanisms (advisory locking, row-level locking, optimistic concurrency with re-validation) are implementation-owned. Deployments under FRCP Rule 37(e) exposure should treat this as a hard serialization requirement. -
Hold placed after record is purged. A hold placed on a
record_refwhose retention record is already in Purged state is accepted by Legal Hold (the atom does not validaterecord_refagainst the retention store) and by this composition’splace_holdaction (which also does not validate). The hold record faithfully documents that a preservation obligation was recognized after destruction. Legal counsel and the court assess the consequences; Invariant 6 states explicitly that the post-purge hold does not alter the purge record. -
Cross-store consistency under failure.
place_record_under_retentionwrites Retention Window first, then Audit Trail.place_holdandrelease_holdwrite Legal Hold first, then Audit Trail.purge_recordwrites Audit Trail (step 5) afterRetentionWindow.purge(step 4) — andRetentionWindow.purgeis irreversible (Purged is terminal by Retention Window’s Invariant 3). A failure between the two writes leaves partial state, but the consequence differs sharply by action: forplace_record_under_retention,place_hold, andrelease_hold, the second write can be retried against intact constituent state, and an orphan composing record (a hold in Legal Hold without an Audit Trail entry, etc.) is a compliance finding the implementation resolves by compensating Audit Trail entry. Forpurge_record, a failed step 5 cannot be reversed — the record is destroyed and norecord_purgedevent exists. This is the composition’s most consequential atomicity hole. The implementation must (a) retry the failedrecord_actionuntil it succeeds, and (b) immediately surface the orphan destroyed record to the compliance dashboard as a high-priority finding alongside theretention_idandrecord_refremoved from the maps in step 6 — the maps are cleaned regardless, since the underlying record is gone and the composition no longer governs it. The compensating Audit Trail entry, once it lands, must reference the gap (e.g.,cascade_recovery = truein its data) so an auditor can distinguish a clean purge from a recovered one. Deployments under FRCP Rule 37(e) / SOX §802 exposure must treat the gap window between step 4 success and step 5 success as a hard alerting condition. -
record_refidentity model at the composition boundary. The composition’s load-bearing gate —LegalHold.read({record_ref: r, state: Active})— evaluates equality onrecord_ref. At the composition boundary,record_refis opaque byte-identity: tworecord_refvalues are the same record if and only if their byte representations are equal. No case-folding, no Unicode normalization, no whitespace trimming is performed at this layer (Legal Hold’s atom-level validation requires at least one non-whitespace character but does not normalize). A consequence: if a deployment renames a record’s reference (URI migration, tenant move, schema change), the newrecord_refdoes not inherit prior holds on the oldrecord_ref— the rename produces a new identity and the gate does not follow. Deployments needing identity continuity across rename are responsible, in a composing layer, for either (a) re-issuing every Active hold against the newrecord_refbefore retiring the old, or (b) running an alias-resolution step ahead ofplace_holdandpurge_recordto canonicalize the reference. The atom-level Legal Hold API summary listsrecord_refas opaque; this composition adds the rename consequence as a composition-boundary discipline. -
Advisory-mode purge leaves a hold Active over a destroyed record. Under
hold_check_mode = advisorywith a non-empty hold check,purge_recordproceeds past the gate and destroys the record (Retention Window → Purged). The blocking holds themselves are not released by the composition; Legal Hold’s atom has no “supersede” surface and the composition does not synthesize one. Result: subsequentLegalHold.read({record_ref: r, state: Active})returns the holds, asserting preservation over a record that no longer exists. The composition declines to issue an automaticrelease_holdin this path because (a) the override authority — whose external authorization permitted the advisory-mode purge — is the same authority that determines whether the underlying preservation obligation has actually ended, and (b) auto-releasing on override would write arelease_reasonthe composition cannot accurately compose. The terminal-state inconsistency is named explicitly: the same external authority that authorized the override is responsible for issuing the appropriaterelease_holdcalls post-purge if the preservation obligations have in fact ended. Until that release lands, the records honestly document an unusual state — a destroyed record with an Active hold — which is materially correct: a court-ordered destruction did not, by itself, end the underlying preservation obligation; it overrode it for the specific record. The discrepancy is observable in any joined query (LegalHold.read({record_ref: r, state: Active})returning a hold whoserecord_refhas no Retention Window record in Retained) and is a compliance-dashboard signal the deployment must surface. -
Dual
purged_atsemantics — Audit Trail timestamp is authoritative.purge_recordstep 4 invokesRetentionWindow.purge(retention_id), which sets the atom’s internalpurged_at(Retention Window’s Invariant 8 —retention_until ≤ purged_at). Step 5 recordspurged_at: nowin the Audit Trail event’s data payload. These are two timestamps captured against the same clock at adjacent (not identical) moments. The composition designates the Audit Trail event’spurged_atas the authoritative system-record-of-destruction timestamp — it is the value Generation acceptance check 1’s hold-vs-purge cross-reference uses, and it is the value forensic queries should read. Retention Window’s atom-levelpurged_atis an internal consistency artifact; it should agree with the Audit Trail value to within the clock’s granularity, and a divergence beyond that is a clock-skew finding under the Clock semantics edge case. Deployments where the divergence has legal force should compose with a Trusted Timestamping pattern; the composition itself does not reconcile the two values further. -
Partial recording on step failure. If
LegalHold.placesucceeds butAuditTrail.record_actionfails, an Active hold exists without a Audit Trail entry — a violation of Invariant 3. The implementation must flag the orphan, returnrejected(recording-failure), and resolve the orphan through a compensating Audit Trail entry once the substrate recovers. This mirrors Audit Trail’s own partial attestation on step failure edge case, which this composition inherits rather than re-derives. -
Failed attribution attempts on non-gate rejections. The composition audits successful state-changing actions and the strict-mode
purge_recordrejection onunder-legal-hold(the load-bearing gate firing). Other rejections —not-known,not-eligible,recording-failure,invalid-requestfrom any action, andplace_hold/release_holdrejections from the Legal Hold atom — produce no Audit Trail event at this layer. The Intent’s coverage claim is bounded accordingly: state-changing decisions and the gate firing are recorded; calls that never advanced to a state change (malformed input, unknown id, eligibility-check failure) are not. Deployments needing a full attempted-action record (e.g., for security-event-correlation purposes) should compose with a Failed-Attempt Log pattern — the same posture Audit Trail names in its Failed attribution attempts edge case. -
Composition-bypass and map staleness. The composition’s emergent state (
record_to_retentions,retention_to_record) reflects only state transitions effected through composition actions. A direct atom-level call againstRetentionWindow.purge(composition bypass — recognized in the Breach forensics adversarial scenario) succeeds against the underlying retention store without informing the composition; the maps go stale, andpurge_eligible()may subsequently iterate aretention_idwhose Retention Window record is already Purged. Detection is via Generation acceptance check 3’s symmetric Retention-Window-to-Audit-Trail check — a Purged retention record with no correspondingrecord_purgedAudit Trail event is the bypass-detection signal. Resolution is a deployment-level forensic and remediation pass, not a composition-level structural change. -
Clock semantics.
nowinpurge_eligible()andpurge_recordcomes from the application’s implicit clock. Clock skew under distributed deployment can cause non-deterministicpurge_eligibleresults. For deployments where retention deadlines have legal force, compose with a Trusted Timestamping pattern (referenced in Audit Trail). The hold check inpurge_recordstep 3 is not clock-sensitive — Active holds are determined by the hold store’s current state, not by timestamp comparison. -
Access control on hold placement, release, and purge. Who may place holds, who may release them, and who may purge records is not defined by this composition. A Permissions composing pattern governs these. The action wiring includes
actor_refandcredentialparameters at every action boundary; the deployment wires Permissions checks per its organizational policy. -
Cryptographic shredding. For records that cannot be directly deleted (append-only — records can be added but never changed or deleted — logs, distributed replicas, immutable storage),
purge_recorddelegates toRetentionWindow.purge, which delegates to the storage layer’s destruction mechanism. Cryptographic shredding is a composing pattern for those storage scenarios; this composition treats both mechanisms as the same state transition. -
GDPR Article 17 adjudication. The composition records the state of holds and retention windows at the time of an erasure request (as shown in the GDPR collision example). It does not adjudicate whether the legal-claims exception under Article 17(3)(e) applies, whether HIPAA retention overrides GDPR erasure for a specific record type, or whether cryptographic shredding satisfies the erasure right. Legal counsel adjudicates. The composition provides the records that inform the decision.
-
Legal hold provenance and authority. The composition records
placed_byfor each hold and verifies the identity via Audit Trail’s Actor Identity attestation. It does not verify thatplaced_byhad legal authority to issue the hold — that the actor is counsel or a designated compliance officer, that the hold was authorized by a chain of command, that thecase_refreferences a real legal matter. These are organizational governance questions that a Permissions composing pattern addresses. The composition records the hold; the organization establishes the authority. -
placed_atbackdating.place_holdaccepts an optionalplaced_attimestamp supplied by the caller. The Audit Trail entry records the implicitnowat the time of the call; the Legal Hold record stores the caller-suppliedplaced_at. Whenplaced_atsignificantly predates the Audit Trail event’s timestamp, the discrepancy is observable in the records — an auditor comparing thehold_placedAudit Trail event timestamp against the Legal Hold record’splaced_atfield sees the gap. The composition does not reject backdatedplaced_atvalues; Legal Hold’s atom accepts them. The Audit Trail event timestamp is the authoritative record of when the hold was entered into the system; aplaced_atvalue represents the caller’s assertion of when the preservation obligation arose, which may legitimately predate the system entry (e.g., oral counsel advice documented retroactively). The discrepancy is a composing-layer integrity concern; a Permissions composing pattern or organizational policy governs whether backdatedplaced_atentries require elevated authorization. -
Retention records’ own retention. The Retention Window records for business records and the Legal Hold records are themselves subject to retention. The Audit Trail instance is configured with
audit_trail_retention_policy(which should be ≥ the longest business record retention policy in use). Meta-retention of Legal Hold records and Retention Window records is a deployment concern; the composition does not loop on itself.
Standards references
- Federal Rules of Civil Procedure Rule 37(e) — preservation duty for electronically stored information. A party that fails to preserve ESI when a hold should have been in place is subject to sanctions including adverse inference. Invariant 1 (hold-blocks-purge) and Invariant 8 (defensible destruction) are the structural forms of the reasonable-steps-to-preserve obligation; the Audit Trail is the evidence.
- Sarbanes-Oxley §802 (18 U.S.C. §1519) — criminal obstruction of justice for destruction of records subject to federal investigation. The hold-blocks-purge gate is the structural defense; the Audit Trail purge record with
hold_check_result: emptyis the evidence that destruction was not obstruction. - Sarbanes-Oxley §404 — internal controls over financial reporting, including record retention and destruction controls. The composition is the structural form of those controls.
- HIPAA §164.530(j) — documentation retention requirements; 6-year federal baseline, longer per state law. The composition governs PHI retention and hold-during-investigation lifecycle; the Audit Trail provides the attribution trail required under HIPAA audit controls.
- SEC Rule 17a-4(f) — broker-dealer record preservation in non-rewriteable, non-erasable format. The Audit Trail substrate (with Tamper Evidence) satisfies the integrity requirement; the hold-blocks-purge gate satisfies the non-premature-destruction requirement.
- GDPR Article 17 (Right to erasure) — the composition answers whether erasure is permissible: Active holds establish the legal-claims exception under Article 17(3)(e); retention obligations establish the legal-obligation basis under Article 17(3)(b). The GDPR collision example demonstrates the structural answer.
- GDPR Article 5(1)(e) (Storage limitation) — personal data must not be kept longer than necessary. The composition’s
purge_eligible()surfaces records past theirretention_until, annotated withpurge_deadlineso the caller can identify overshoot (records wherenow ≥ purge_deadline); the purge record proves timely destruction. - Federal Rules of Civil Procedure Rule 26(b) — proportionality in discovery preservation. The
hold_reasonandcase_reffields in Legal Hold document the proportionality of each hold; the composition preserves the evidentiary record without adjudicating proportionality. - ISO 15489-1 (Records management) — international standard for records-management practice. Section 9.7 (suspension of disposition) maps to the hold-blocks-purge gate; the two-state (Active/Released) hold lifecycle maps to ISO 15489’s hold lifecycle.
The three constituent elements carry their own deep standards inheritance — see each constituent’s Standards references.
Status
grounded on Final Critique 4 — 2026-05-20 — foundation round complete (Passes 1–3, author-led); Round 2 complete (Passes 4–6: GRID + EOS + Linus); Round 3 complete (AI-conducted independent re-run, claude-opus-4-7, 2026-05-13). Under the unified methodology (3×3 baseline rounds with per-round Pass 1/2/3 numbering + Final Critique starting at Round 4), this pattern’s AI-conducted independent re-run is retro-labeled as Final Critique 4. The original cumulative Pass 1–9 numbering in Lineage notes below is preserved as historical record; the per-round mapping in parentheses (e.g., “Pass 7 (Round 3, Pass 1)”) corresponds to the unified convention’s Pass 1 of Final Critique 4. All nine GRID nodes clean across all three rounds; EOS clean; Linus findings across the three rounds all closed in-pattern or named as explicit out-of-scope. Composition logic specified across all three constituents (Legal Hold + Retention Window + Audit Trail); emergent state (record_to_retentions, retention_to_record) named with cleanup discipline on successful purge; action wiring covers retention placement, hold placement, hold release, purge-eligibility query, and gated purge with the load-bearing override-retention-on-hold gate; Primitive policies subsection enumerates composition-boundary input rules; eight application-level invariants stated and defended; walkthrough plus five domain examples (banking SOX, healthcare HIPAA, broker-dealer Rule 17a-4, FRCP e-discovery, pharmaceutical advisory-mode override) plus GDPR Article 17 collision; three regulated adversarial scenarios; fifteen edge cases including purge_record atomicity, record_ref identity, advisory-mode terminal-state inconsistency, dual purged_at semantics, failed-action audit scope, and composition-bypass map staleness; Generation acceptance bar explicit, split between Audit-Trail-traversal-clearable and externally-clearable checks. Seventh entry in compositions/. Survived foundation pass plus two refinement rounds.
Lineage notes
Regulated composition. Conventions — Regulated adversarial scenarios and Generation acceptance — inherited from the methodology directly (PRESSURE_TESTING.md), baked in from the first draft. Audit Trail is the primary structural reference for regulated composition shape. Multi-Party Approval is the secondary structural reference for the substrate-handling pattern (using Audit Trail as a substrate composition rather than re-listing its four constituent atoms at this layer) and for the Generation acceptance split between Audit-Trail-traversal-clearable and externally-clearable checks. Conventions cited from the methodology directly, not re-derived from prior compositions.
Structural milestone. This composition retires three simultaneous forthcoming-link debts: Legal Hold’s Composition notes named Regulated Record Retention & Defensible Deletion (C1) as the composition that wires the purge gate; Retention Window’s edge case on Legal hold named Legal Hold as the composing pattern that intercepts purge; Audit Trail’s edge case Legal hold suspension of purge named this composition as the forthcoming resolution. All three are now resolved.
Pass 1 — Structural completeness (GRID). Three findings, all closed in-pattern.
-
Application state lacked the
retention_to_recordinverse map. The initial draft storedrecord_to_retentionsonly. The hold-check gate inpurge_recordneeds to look uprecord_reffrom aretention_idbefore querying Legal Hold; without the inverse map, the gate required scanning the full forward map. Fixed:retention_to_recordadded as a paired inverse map, mirroring thestep_to_chain/chain_to_stepspairing in Multi-Party Approval. -
purge_eligible()return type underspecified. The initial draft returned only a list of eligibleretention_ids with no indication of hold-blocked records. A compliance dashboard cannot distinguish records ready for purge from records eligible but blocked by active holds without this distinction. Fixed: return type extended to(retention_id, record_ref, retention_until, hold_count)tuples, distinguishing purge-ready from hold-blocked. -
place_holdandrelease_holdcredential parameter asymmetry implicit. Legal Hold’s atom-levelplacetakes an opaqueplaced_byreference; Audit Trail’srecord_actionrequires a verifiablecredential. The composition wires both, takingcredentialat the application boundary and passing it to Audit Trail while passing the opaqueplaced_byto Legal Hold. The asymmetry was implicit in the initial draft. Fixed: the Note on credential paragraph inplace_holdmakes the asymmetry explicit, parallel to how Audit Trail’srecord_actionhandles the actor-credential pairing.
All nine GRID nodes resolved.
Pass 2 — Conceptual independence (EOS — Essence of Software, Daniel Jackson’s framework for specifying software concepts as freestanding, composable units). Clean. Four extraction candidates evaluated; none warranted.
-
Override-precedence rule as its own atom. Could “Legal Hold overrides Retention Window” recur enough to be a freestanding atom? Evaluated: the rule has no state of its own — it is a constraint on the interaction of two specific atoms, enforced at the call site between
LegalHold.readandRetentionWindow.purge. It does not recur across other atom pairings in the current library. A stateless rule with no independent lifecycle is not EOS-grade freestanding. Kept in-composition as the load-bearing wiring decision. This is the same conclusion Multi-Party Approval’s Pass 2 reached for the Quorum evaluation rule. -
Policy reconciliation. Multi-jurisdiction policy selection is named as explicit out-of-scope and belongs to a Policy Reconciliation composing pattern. The composition takes the reconciled
policy_refas input. -
Legal hold provenance and authority. Who authorized the hold — chain of command, matter-type restrictions — is externalized to a Permissions composing pattern and an external case management system.
placed_byis an opaque reference; the composition records and verifies the identity but does not evaluate organizational authority. -
Cross-store consistency primitive. The two-step writes in
place_record_under_retention,place_hold, andrelease_holdrequire partial-state recovery. Could a “two-phase write” primitive be extracted? Evaluated: this concern recurs across every composition that writes to two stores in sequence (Audit Trail itself, Multi-Party Approval); it is correctly named as a deployment-owned transactional concern in each. No new atom warranted; the edge case is named as in prior compositions.
No concerns extracted. Pass 2 clean.
Pass 3 — Adversarial scrutiny (Linus mode), foundation posture. Six findings, all closed in-pattern or named as explicit out-of-scope.
-
Cascade ordering when a hold lands during a purge. A hold placed between
purge_recordstep 3 (hold check) and step 4 (RetentionWindow.purge) can produce a purge that violates Invariant 1 — the race window is invisible to the composition’s sequential checks. Foundation draft was silent. Fixed: Concurrent hold placement and purge edge case added; serialization requirement stated as implementation-owned; FRCP Rule 37(e) deployment posture named as requiring strict serialization. -
verify_recordbehavior for a held-and-past-retention record. Audit Trail’sverify_recordreturnsverifiedfor a record in Retained state — regardless of whether a Legal Hold is active. An Active hold affects purge eligibility, not the record’s integrity or attribution. A record that is pastretention_untilbut hold-blocked remains in Retained state (becausepurge_recordwas correctly rejected);verify_recordreturnsverified, which is correct. Foundation draft was silent. Fixed: the breach forensics adversarial scenario explicitly exercises this case, confirming the expected behavior. -
GDPR Article 17 collision must be explicitly answered. The ROADMAP entry names GDPR Article 17 as a collision case the spec must address. Foundation draft treated it as a non-goal without the structural answer. Fixed: dedicated GDPR collision example (five-domain examples section) and addressed in the adversarial scenarios. The composition does not adjudicate the legal question; it records the state of holds and retention windows at decision time, which is the structural answer the DSAR composition (C7) will build on.
-
place_holdcredential asymmetry initially hidden. Legal Hold’s atom-level API takesplaced_byas an opaque reference with no credential; Audit Trail’srecord_actionneeds a verifiable credential. The foundation draft composed the two without surfacing the asymmetry, leaving an implementer uncertain about where the credential lives. Fixed: the Note on credential paragraph inplace_holdaction wiring (also a Pass 1 finding; closed in-pattern for both passes simultaneously). -
hold_check_mode = advisoryneeded justification. The advisory mode option appeared in the foundation draft without explaining the motivating scenario. Fixed: Configuration section names the court-ordered-destruction scenario and explicitly states that FRCP Rule 37(e), SEC Rule 17a-4, and SOX deployments must usestrict. -
Generation acceptance split not applied. Foundation draft’s Generation acceptance was a flat list mixing Audit-Trail-traversal-clearable checks with externally-clearable questions. Multi-Party Approval’s Round 3 established the split as a structural discipline for regulated compositions. Fixed: split adopted with the same section naming convention as Multi-Party Approval.
Three concerns named as explicit out-of-scope rather than fixed in-pattern: clock semantics under distributed deployment (Trusted Timestamping is the resolution), mass purge under hold release (composing-layer iteration concern, not a composition-level structural question), and GDPR Article 17 adjudication (legal counsel’s question, not the composition’s).
Deferred to Round 2 and Round 3: precise semantics of advisory mode Audit Trail records when hold store is non-empty (is hold_override = true sufficient for court-admissibility, or are additional fields required?); whether business_record_retention_policy should accept a per-call override at place_record_under_retention time; and whether the concurrent-hold-and-purge race condition requires a more formal distributed-systems treatment beyond the current serialization-requirement statement.
Pass 4 (Round 2, Pass 1) — Structural completeness (GRID) re-run. Three related findings, all in the State and Flow nodes; closed in-pattern as a coherent set.
-
retention_untilsource unspecified in Application state and action wiring. The composition usesretention_untilandpurge_deadlineinplace_record_under_retentionstep 3 (Audit Trail event data) and inpurge_eligible()(eligibility filter), but neither the Application state maps nor the action wiring named where these values come from.RetentionWindow.place_under_retention’s atom-level return signature isretention_idonly. Fixed:retention_to_recordenriched fromretention_id → record_reftoretention_id → {record_ref, retention_until, purge_deadline};place_record_under_retentionstep 1 updated to treat the composition-boundary return as{retention_id, retention_until, purge_deadline}— consistent with Retention Window’s Outputs section, which lists both as properties of the retention record even though the atom-level action signature abbreviates; step 2 updated to populate the enriched map. -
purge_recordaction wiring left state maps stale after successful purge. Steps 1-6 (now 1-7) returnedokwithout removing entries fromretention_to_recordorrecord_to_retentions. The State node requires naming what changes and under what condition; these maps are the composition’s primary state artifacts. Without cleanup,purge_eligible()would iterate over entries in terminal Purged state and require a Retention Window read API (which the atom does not define) to filter them. Fixed:purge_recordstep 6 added — removesretention_idfromretention_to_recordand fromrecord_to_retentions[record_ref]; removes therecord_refkey if its set becomes empty. -
purge_eligible()described by output semantics only; step wiring absent. The only action without numbered step wiring. Its implementation was ambiguous without the enrichedretention_to_recordmap (forretention_until) and the cleanup discipline (for knowing that all entries in the map are Retained). Fixed: step wiring added — iteratesretention_to_record, filters onnow ≥ retention_until, callsLegalHold.readper entry forhold_count. The note that all entries in the map are Retained (by virtue of step-6 cleanup) eliminates a Retention Window read-API dependency.
All nine GRID nodes confirmed clean after fixes.
Pass 5 (Round 2, Pass 2) — Conceptual independence (EOS) re-run. Clean. All nine concerns re-evaluated; no new over-absorptions introduced by Round 2 Pass 1 fixes. The four extraction candidates from foundation Pass 2 remain correctly bounded. The enrichment of retention_to_record to {record_ref, retention_until, purge_deadline} is compositional caching of Retention Window data required for purge_eligible() — it does not duplicate the atom; it caches only what the composition’s own query surface needs in the absence of a Retention Window read API. Advisory-mode override authorization correctly bounded to a Permissions composing pattern and named in externally-clearable Generation acceptance checks.
One item deferred to Pass 6: purge_deadline is stored in retention_to_record but not surfaced in purge_eligible() return tuples. The caller cannot currently distinguish records that are past retention_until but within the purge_deadline window from records that have overshot purge_deadline — a distinction the compliance dashboard needs to surface overshoot (see Retention Window’s Feedback section). This is a Pass 6 (Linus) candidate, not an EOS concern — no separate concept warrants extraction; the question is whether purge_deadline should be added to the return tuple.
Pass 6 (Round 2, Pass 3) — Adversarial scrutiny (Linus mode) re-run. Five findings; all closed in-pattern.
-
place_record_under_retentionrejection taxonomy incomplete. The outer action signature showsrejected(invalid-request | recording-failure)but step 1 propagated three Retention Window rejection codes —invalid-request,invalid-policy, andpolicy-not-found— with no specified mapping for the latter two. A caller receiving a rejection from the composition sawinvalid-requestwith no way to know the policy reference was the problem, or received an undocumented code. Fixed: step 1 updated to mapinvalid-policyandpolicy-not-foundexplicitly toinvalid-requestwith a note — from the caller’s perspective both indicate a bad policy reference; the outer signaturerejected(invalid-request | recording-failure)is now correct and complete. -
purge_deadlineabsent frompurge_eligible()return tuple; Standards reference wrong.purge_eligible()returned(retention_id, record_ref, retention_until, hold_count)withoutpurge_deadline. The caller could not distinguish records in the purge window (retention_until ≤ now < purge_deadline) from records in overshoot (now ≥ purge_deadline) — a distinction the GDPR Article 5(1)(e) compliance dashboard requires. Compounding the gap, the GDPR Article 5(1)(e) Standards reference falsely stated the action “surfaces records past theirpurge_deadline” when it surfaced records pastretention_until. Fixed:purge_deadlineadded to the return tuple; step 4 updated with the within-window vs. overshoot distinction; Standards reference corrected. Deferred item from Pass 5 resolved. -
business_record_retention_policyconfiguration knob contradictedpolicy_refaction parameter. Configuration definedbusiness_record_retention_policyas “applied atplace_record_under_retentiontime” and “passed toRetentionWindow.place_under_retention” — but the action already took an explicitpolicy_refparameter, and step 1 used that parameter. The relationship between the configuration knob and the parameter was unspecified. A hidden decision: which wins? Fixed:business_record_retention_policyremoved from Configuration. The per-callpolicy_refparameter is sufficient; deployments wanting a fixed policy pass the same value every time. The policy-reconciliation note moved toaudit_trail_retention_policy. Resolves the deferred concern from foundation Pass 3. -
purge_record(under-legal-hold)rejection never shown explicitly in examples. The walkthrough showedpurge_eligible()returninghold_count = 1but never showed a directpurge_recordcall returningrejected(under-legal-hold)with the named code. The gate is the composition’s defining invariant; showing only the eligibility signal and not the rejection path was happy-path bias at the worst possible node. Fixed: walkthrough step 3 extended with an explicitpurge_recordcall and itsrejected(under-legal-hold)outcome. -
placed_atbackdating risk unnamed.place_holdaccepts an optional caller-suppliedplaced_attimestamp. The Audit Trail entry records the implicitnow; the Legal Hold record storesplaced_at. A discrepancy is observable but nowhere named — an adversary could supplyplaced_atpredating a purge to fabricate a pre-existing hold. Fixed: edge caseplaced_atbackdating added, naming the discrepancy, clarifying that the Audit Trail event timestamp is the authoritative system-entry record, and noting that backdatedplaced_atmay legitimately represent retroactively-documented obligations while the discrepancy itself is a composing-layer integrity concern for Permissions to govern.
Round 3 — AI-conducted adversarial round (claude-opus-4-7, independent re-run). 2026-05-13. Twelve candidate findings produced; two confirmed as artifacts of the round’s information boundary (the round was conducted against the constituent API summaries in the prompt; two of the round’s reference-graph findings reflected those summaries’ incomplete enumeration of constituent Generation acceptance bars and Event Log invariants, both of which verify against the canonical atom and composition specs in the library — no spec change required). Ten findings closed in-pattern.
Pass 7 (Round 3, Pass 1) — Structural completeness (GRID), AI re-run. Three findings; two artifacts of the prompt boundary, one closed in-pattern.
-
Constituent Generation acceptance bar counts unverifiable from the supplied API summaries (artifact). The composition’s Generation acceptance check 5 names “Legal Hold’s six checks, Retention Window’s five checks, Audit Trail’s six checks.” The round’s API summaries listed Legal Hold’s six explicitly, but enumerated only invariants for Retention Window and Audit Trail. Verified against the canonical specs: Retention Window has five Generation acceptance checks (
atoms/compliance/retention-window.md), Audit Trail has six (compositions/audit-trail.md). The counts in the spec are correct; no change required. The round produced the finding from a strict reading of the materials it was given, which is the correct discipline for AI-conducted review; the reconciliation belongs in the lineage record, not in the spec text. -
Event Log Invariant 2 reference unverifiable from supplied API summaries (artifact). Invariant 6 of this composition cites “Event Log’s Invariant 2” for the immutability of the Audit Trail purge event. Event Log was referenced only transitively in the round’s supplied summaries (through Audit Trail’s Constitutes line). Verified against
atoms/temporal/event-log.md: Event Log’s Invariant 2 is Event immutability — after a successfulappend, the event’sevent_id,sequence_number,recorded_at, anddatanever change. The reference resolves; no change required. -
State node silent on composition-bypass map staleness. The composition’s emergent maps (
record_to_retentions,retention_to_record) are populated and cleared only by composition actions. A direct atom-levelRetentionWindow.purge(the Breach forensics adversarial scenario already names this as a composition-bypass finding) succeeds without informing the composition; the maps go stale andpurge_eligible()may iterate aretention_idwhose Retention Window record is already Purged. Foundation State node addressed on-path conditions only. Fixed: new edge case Composition-bypass and map staleness added, naming the bypass-detection signal (Generation acceptance check 3’s symmetric Retention-Window-to-Audit-Trail check — a Purged retention record with no correspondingrecord_purgedevent is the signal) and resolving the staleness as a deployment-level forensic concern rather than a composition-level structural change.
All nine GRID nodes confirmed clean after the bypass-staleness fix and verification of the two prompt-artifact references.
Pass 8 (Round 3, Pass 2) — Conceptual independence (EOS), AI re-run. Clean. The eight foundation/Round-2 extraction candidates re-evaluated; all confirmed. Two new candidates considered fresh by the round:
-
Audit recording of failed actions as a separable concept. Considered: does “audit log every attempted action including rejected ones” recur enough to be its own composition (a Failed-Attempt Log)? Evaluated: the recurrence is real (security-event-correlation, intrusion detection, anomaly review across regulated systems) and the concept has its own retention and integrity surface. Kept out of this composition deliberately, named in the new Failed attribution attempts on non-gate rejections edge case with the composing-pattern resolution. The single exception folded in — the strict-mode
purge_record(under-legal-hold)gate firing — is load-bearing for Generation acceptance check 1’s symmetric reading and is recorded as apurge_blocked_by_holdAudit Trail event, not as part of a generalized failed-attempt log. -
Advisory mode as a separate composition. Considered: is the strict/advisory split structurally two distinct gates with different invariants? Evaluated: the gate’s evaluation is the same in both modes (
LegalHold.read({record_ref: r, state: Active})is consulted in both); the difference is only in what happens on a non-empty result. The state machine is single, not bifurcated; the override pathway is configuration, not a separate concept. Kept in-composition; Round 3 Pass 3 surfaces and resolves the resulting terminal-state inconsistency.
No new atoms or compositions extracted. Pass 8 clean.
Pass 9 (Round 3, Pass 3) — Adversarial scrutiny (Linus mode), AI re-run. Seven findings; all closed in-pattern.
-
purge_recordatomicity gap — destruction without audit evidence. The biggest finding of the round. The Cross-store consistency under failure edge case enumeratedplace_record_under_retention,place_hold, andrelease_holdand conspicuously omittedpurge_record. Step 4 (RetentionWindow.purge) is irreversible; step 5 (AuditTrail.record_action) can fail after step 4 succeeds, leaving the record destroyed with norecord_purgedevent — a direct violation of Invariant 8. The other three cross-store actions are recoverable against intact constituent state;purge_recordis not. Foundation and Round-2 specs were silent on this. Fixed: the Cross-store consistency under failure edge case now distinguishes the recoverable case (the first three actions) from the irrecoverable case (purge_record), requires the implementation to retry the failedrecord_actionuntil it lands and to immediately surface the orphan destroyed record to the compliance dashboard as a high-priority finding, requires the compensating Audit Trail entry to carry acascade_recovery = truesignal so an auditor can distinguish a clean purge from a recovered one, and names FRCP Rule 37(e) / SOX §802 deployments as required to treat the gap window as a hard alerting condition. The maps in step 6 are cleaned regardless (the record is gone and the composition no longer governs it). -
record_refidentity model undefined at composition boundary. The composition’s load-bearing gate evaluatesrecord_refequality, and the spec never said what equality means. Fixed: newrecord_refidentity model at the composition boundary edge case declaresrecord_refas opaque byte-identity at this layer — no case-folding, no normalization, no trim — and names the rename consequence (a rename produces a new identity; prior holds do not follow) along with the two composing-layer disciplines available to deployments that need identity continuity across rename. The Primitive policies subsection cross-references the edge case. -
Advisory-mode purge leaves a hold Active over a destroyed record. Under
hold_check_mode = advisorywith a non-empty hold check, the record is destroyed but the blocking holds remain Active. The spec was silent on this terminal-state inconsistency. Fixed: new Advisory-mode purge leaves a hold Active over a destroyed record edge case names the inconsistency, defends the composition’s decision not to auto-release (the override authority is the same authority that determines whether the underlying preservation obligation has actually ended; auto-releasing on override would write arelease_reasonthe composition cannot accurately compose), and assigns the post-purgerelease_holdresponsibility to the same external authority that authorized the override. The compliance-dashboard discrepancy signal is named explicitly. -
Failed and blocked actions produce no Audit Trail event — Intent claim overstated. The Intent asserted “every retention decision (placement, hold placement, hold release, purge) is attribution-stamped…” but rejection paths produced no audit event, including the load-bearing strict-mode
purge_record(under-legal-hold)gate firing. The composition’s defining gate was visible only in its absence. Fixed at two layers: (a)purge_recordstep 3 now emits apurge_blocked_by_holdAudit Trail event before returningrejected(under-legal-hold)under strict mode — the gate firing is now recorded in the records; (b) the Intent claim is narrowed to “every successful retention state transition (placement, hold placement, hold release, purge) plus every firing of the hold-blocks-purge gate is attribution-stamped…”; (c) Invariant 4 updated to cover both the success and gate-firing audit events with the twohold_check_resultshapes; (d) new Failed attribution attempts on non-gate rejections edge case bounds the audit scope explicitly — other rejections remain unaudited at this composition’s layer, with a Failed-Attempt Log composing pattern named as the resolution for deployments needing full attempted-action coverage. -
Dual
purged_atsemantics unspecified.RetentionWindow.purgesets its ownpurged_atinternally;AuditTrail.record_actionrecordspurged_at: nowin the event data. The two timestamps are captured against the same clock at adjacent moments and can differ by clock granularity. The spec was silent on which is authoritative. Fixed: new Dualpurged_atsemantics — Audit Trail timestamp is authoritative edge case designates the Audit Trail event’spurged_atas the authoritative system-record-of-destruction timestamp (the value Generation acceptance check 1’s cross-reference uses); Retention Window’s atom-levelpurged_atis an internal consistency artifact that should agree to within clock granularity. Divergence beyond granularity is a clock-skew finding under the Clock semantics edge case; deployments with legal-force divergence compose with Trusted Timestamping. -
Composition-boundary input validation rules absent. The composition accepted string-typed inputs at every action boundary without enumerating its own validation discipline;
invalid-requestsurfaced from constituents without indication of where validation occurred. Fixed: new Primitive policies subsection added before Action wiring, naming for each composition-boundary input (record_ref,policy_ref,actor_ref/placed_by/released_by,credential, the three reason fields,case_ref,placed_at/released_at) the validation rule, whether it is enforced at the composition layer or propagated from a constituent, and the resulting rejection code at the composition’s caller surface. -
Action signature contradicted step prose in
place_record_under_retention. The signature was→ retention_id; step 4 said “Returnretention_id”; but step 1 prose said the full retention record was “treated as part of the return here.” Fixed: step 1 prose rewritten to state plainly thatretention_untilandpurge_deadlineare read from the just-created retention record as composition-internal values used in steps 2 and 3, and are not returned to the caller. The signature, step 4, and step 1 now agree; the sleight-of-hand framing is removed. -
hold_check_resultfield shape under advisory + empty was unspecified (minor). Step 5 specified two of four mode-by-presence combinations explicitly; advisory + empty was left implicit. Fixed: step 5 now defineshold_check_resultshape independently of mode (emptywhen the step-3 check returned empty;{hold_ids: [...], count: N}when non-empty), withhold_overrideindependently reflecting the mode-and-decision combination, and enumerates all four combinations explicitly. -
Examples exercised happy paths only; advisory mode never exercised. The five prior examples plus the GDPR collision example covered successful state transitions and the strict-mode block, but never showed advisory mode in action — the configuration was named-and-required-off without a worked example. Fixed: new Advisory-mode override — court-ordered destruction superseding a litigation hold example added (a pharmaceutical-company scenario with a federal-court destruction order narrowing a separate matter’s preservation duty), demonstrating the override pathway, the Audit Trail event shape with
hold_override: true, the deliberate non-auto-release of the blocking hold, and the compliance-dashboard discrepancy signal that follows.
Two concerns surfaced by Round 3 are named as explicit out-of-scope rather than fixed in-pattern: a Failed-Attempt Log composing pattern (the bounded scope of audit coverage is correct for this composition; deployments needing more compose the pattern), and identity-continuity across record_ref rename (the rename consequence is named; the alias-resolution discipline is correctly externalized to a composing layer).
Round 3 validates the core architectural posture of the prior rounds — the load-bearing wiring decision, the constituent boundary discipline, the Generation acceptance split — while closing the atomicity hole the prior rounds missed (the purge_record step-4-to-step-5 gap), the implicit identity assumption (record_ref opaque byte-identity), the terminal-state inconsistency under advisory mode, and the silently-overstated Intent claim. The composition’s records-alone defensibility is materially stronger after the round: the gate’s firings are now visible in the records, the atomicity gap has a named recovery discipline, and the input validation surface is explicit at the composition boundary.
Scheduled rescan: 2026-05-20 — clean. Pass 1 clean: all nine GRID nodes confirmed; constituent atom API cross-references verified against today’s rescanned atoms (tamper-evidence fix affects only the mechanism_credential precondition inside TamperEvidence.seal, which this composition reaches only transitively through Audit Trail — no change required; retention-window, actor-identity, and event-log refining fixes are examples-only and carry no API changes). Pass 2 clean: no over-absorptions. Pass 3 clean: no foundational or refining findings. All invariant references verified against current constituent atom specs.