Audit trail

A complete, immutable audit trail is a regulatory requirement for medical AI systems operating under FDA 21 CFR Part 11 and EU MDR. AEGIS mandates that every iteration of the governance loop produces a structured log entry capturing all inputs, computations, and outputs of the CDM evaluation.

Why audit trail matters

The audit trail serves three critical functions:

Function Regulatory context What it demonstrates
Traceability FDA PCCP, EU MDR Art. 83 Every model version can be traced from data to decision
Reproducibility ISO 14971:2019 Any iteration can be re-evaluated with the same inputs
Accountability AI Act Art. 14 Human oversight of automated decisions is documented

Without a complete audit trail, the iterative governance loop cannot demonstrate compliance with post-market surveillance obligations. Regulators expect to see the full history of model versions, their performance evaluations, and the decisions made at each step.

**Sepsis example:** The sepsis case study produced 11 audit trail entries (k = 0 through k = 10), documenting the complete lifecycle from initial deployment through catastrophic failure. The audit trail showed the following progression per Table VIII: six consecutive APPROVE decisions (k = 0--5), one CONDITIONAL APPROVAL (k = 6, cross-site drift), one CLINICAL REVIEW (k = 7, regression from P^ref), one APPROVE + ALARM (k = 8, extreme drift but small batch did not corrupt the model), one APPROVE (k = 9, recovery), and one REJECT + ALARM (k = 10, catastrophic corruption). This documented trajectory would be essential for regulatory review, demonstrating that the framework correctly escalated its response as data quality degraded.

Required log fields

Every iteration must log the following fields. These are derived from the CDM specification (Manuscript Section II-B-3) and Supplementary S3.6.3.

# Field Type Description
1 timestamp string (ISO 8601) When the CDM evaluation was executed
2 iteration_id integer Iteration index k
3 model_version string Model hash, version tag, or unique identifier
4 dataset_hash string SHA-256 hash of D_D,k for integrity verification
5 decision string Decision category (APPROVE / CONDITIONAL APPROVAL / CLINICAL REVIEW / REJECT)
6 trigger_reason string Priority level and condition that triggered the decision
7 metrics object All computed metric values with names, values, thresholds, and pass/fail
8 thresholds object P_fail, R_G, tau values used for this evaluation
9 drift_results object Drift score, method, per-feature p-values from KS tests
10 tai_scores object MLcps and other composite scores
11 gold_comparison object Per-metric deviation from gold standard and within-tolerance flag
12 confidence object Confidence intervals or uncertainty estimates
13 reviewer string Operator or system identifier responsible for the evaluation

Additional recommended (but not mandatory) fields:

Field When to include
alarm_state Object with alarm flag (true/false) and list of triggered conditions (A1/A2/A3)
review_notes When decision is CLINICAL REVIEW (human assessment required)
capa_reference When decision is REJECT or alarm is triggered
dataset_sizes Sizes of D_T,k, D_G, and D_D,k

Recommended JSON format

AEGIS recommends JSON as the log format for machine-readability and interoperability. Each iteration produces one JSON object appended to a log file or inserted into a database.

{
  "iteration": 7,
  "timestamp": "2026-03-15T14:32:18Z",
  "dataset_sizes": {
    "D_T_k": 38254,
    "D_G": 4067,
    "D_D_k": 5000
  },
  "metrics": {
    "sensitivity": {
      "value": 0.653,
      "thresholds": {
        "P_fail": 0.65,
        "R_G": 0.66
      },
      "pass_pfail": true,
      "pass_rg": false
    },
    "specificity": {
      "value": 0.720,
      "thresholds": {
        "R_G": 0.60
      },
      "pass_rg": true
    },
    "roc_auc": {
      "value": 0.847,
      "thresholds": {},
      "pass_rg": true
    },
    "balanced_accuracy": {
      "value": 0.687,
      "thresholds": {},
      "pass_rg": true
    }
  },
  "mlcps": 0.607,
  "drift_score": 0.441,
  "drift_details": {
    "method": "KS + Bonferroni",
    "K": 34,
    "alpha": 0.05,
    "alpha_prime": 0.00147,
    "n_drifted": 15,
    "significant_features": [
      "HR", "Temp", "SBP", "MAP", "DBP", "Resp",
      "BaseExcess", "HCO3", "BUN", "Creatinine",
      "Glucose", "Lactate", "Potassium", "WBC", "Platelets"
    ]
  },
  "cdm_decision": "CONDITIONAL APPROVAL",
  "cdm_details": {
    "priority": "P3",
    "trigger": "sensitivity = 0.653 < R_G = 0.66"
  },
  "alarm_state": {
    "alarm": false,
    "alarm_conditions": []
  },
  "gold_standard_comparison": {
    "sensitivity": {
      "gold": 0.723,
      "current": 0.653,
      "deviation": 0.237,
      "within_tolerance": false
    },
    "specificity": {
      "gold": 0.798,
      "current": 0.720,
      "deviation": 0.078,
      "within_tolerance": false
    }
  },
  "model_version": "rf_sepsis_v7_sha256:b2e4f1a9",
  "operator_id": "aegis-system",
  "data_hash": "sha256:8c3d2e1f..."
}
**Sepsis example:** The JSON entry above represents iteration 7 of the sepsis experiment. At this point, sensitivity had dropped to 0.653 -- above P_fail (0.65) but below R_G (0.66), placing it in the performance buffer zone. The CDM correctly assigned CONDITIONAL APPROVAL at priority P3. The drift score of 0.441 indicated that 15 out of 34 features showed significant distribution shift, but this was below theta_major (0.90), so no alarm was triggered. The gold standard comparison showed that sensitivity had deviated 0.237 from the baseline of 0.890, far exceeding tau = 0.015, but since drift had not reached the major threshold, P2 did not fire. This entry demonstrates the CDM's nuanced behaviour: the model was not rejected (it still met the critical safety floor) but was flagged for conditional use and heightened monitoring.

Storage and retention

Storage options

Option Pros Cons
JSON Lines file (.jsonl) Simple; version-controllable; portable No query capability
Database (PostgreSQL/MongoDB) Queryable; scalable Infrastructure overhead
Cloud logging (AWS CloudWatch, GCP Logging) Managed; searchable; retention policies Vendor lock-in; cost

Retention requirements

Immutability

Audit trail entries must be immutable once written. Use one of the following mechanisms:

import hashlib
import json

def compute_entry_hash(entry: dict, prev_hash: str = "") -> str:
    """Chain audit entries cryptographically."""
    content = json.dumps(entry, sort_keys=True) + prev_hash
    return hashlib.sha256(content.encode()).hexdigest()

Integration with CAPA

When the CDM produces a REJECT or ALARM output, the audit trail entry should include a reference to the initiated CAPA (Corrective and Preventive Action) process. The CAPA form captures the root cause analysis, corrective actions, and regulatory notification assessment.

The link between audit trail and CAPA should be bidirectional:

Source

Manuscript Section II-B-3 (CDM specification and logging requirements). The required log fields are specified in Supplementary S3.6.3. Retention requirements are derived from the regulatory frameworks discussed in Section II-A (Table I). The JSON format is a recommended implementation approach consistent with the reference Python implementation in Supplementary S3.3.