| """ |
| Primordial OS Runtime — Audit Store (Phase 10) |
| Safe JSONL file writer and reader for hash-chained audit events. |
| |
| Writes one JSON object per line (JSONL). Parent directory must exist before |
| writing. Hidden file paths are rejected. File is created on first append; |
| existing content is preserved on subsequent appends. |
| |
| No raw prompts, user input, PHI, PII, or clinical content is written — |
| only the structural runtime fields that already exist in AuditEvent. |
| |
| Pre-validation architecture. Not clinical software. Not a medical device. |
| No health-domain inference, treatment, or decision-making functionality |
| is present or intended. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import json |
| from pathlib import Path |
|
|
| from primordial_os.audit_log import AuditEvent, verify_audit_chain |
|
|
|
|
| |
| |
| |
|
|
| def audit_event_to_dict(event: AuditEvent) -> dict: |
| """Return a plain dict of structural runtime fields only. |
| |
| Explicit field enumeration ensures no unexpected keys are written. |
| No raw prompts, user input, PHI, PII, or clinical content is included. |
| """ |
| return { |
| "timestamp": event.timestamp, |
| "event_type": event.event_type, |
| "final_gate_state": event.final_gate_state, |
| "hard_stop": event.hard_stop, |
| "resonance": event.resonance, |
| "pressure_adjusted_stability": event.pressure_adjusted_stability, |
| "max_oam_severity": event.max_oam_severity, |
| "summary": event.summary, |
| "previous_hash": event.previous_hash, |
| "current_hash": event.current_hash, |
| } |
|
|
|
|
| def audit_event_from_dict(data: dict) -> AuditEvent: |
| """Reconstruct an AuditEvent from a plain dict (e.g. parsed from JSONL).""" |
| return AuditEvent( |
| timestamp=data["timestamp"], |
| event_type=data["event_type"], |
| final_gate_state=data["final_gate_state"], |
| hard_stop=data["hard_stop"], |
| resonance=data["resonance"], |
| pressure_adjusted_stability=data["pressure_adjusted_stability"], |
| max_oam_severity=data["max_oam_severity"], |
| summary=data["summary"], |
| previous_hash=data["previous_hash"], |
| current_hash=data["current_hash"], |
| ) |
|
|
|
|
| |
| |
| |
|
|
| def _validate_write_path(path: Path) -> None: |
| """Raise for unsafe or invalid write targets. |
| |
| Checks: |
| - hidden file paths (name starts with '.') |
| - parent directory does not exist |
| """ |
| if path.name.startswith("."): |
| raise ValueError(f"Hidden file paths are not permitted: {path}") |
| if not path.parent.exists(): |
| raise FileNotFoundError( |
| f"Parent directory does not exist: {path.parent}" |
| ) |
|
|
|
|
| |
| |
| |
|
|
| def append_audit_event_jsonl(path: str | Path, event: AuditEvent) -> None: |
| """Append one AuditEvent as a single JSON line to the JSONL file at path. |
| |
| Safety requirements: |
| - Parent directory must exist; will not be created automatically. |
| - Hidden file paths (name starts with '.') are rejected. |
| - File is created if absent; existing lines are never modified. |
| - Encoding: UTF-8. |
| - Format: one compact JSON object per line, keys sorted, no trailing spaces. |
| - No raw prompts, PHI, PII, or clinical content is written. |
| """ |
| p = Path(path) |
| _validate_write_path(p) |
| line = json.dumps( |
| audit_event_to_dict(event), |
| sort_keys=True, |
| separators=(",", ":"), |
| ensure_ascii=False, |
| ) |
| with p.open("a", encoding="utf-8") as f: |
| f.write(line + "\n") |
|
|
|
|
| def read_audit_events_jsonl(path: str | Path) -> tuple[AuditEvent, ...]: |
| """Read all AuditEvents from a JSONL file and return them as a tuple. |
| |
| Events are returned in file order (append order). |
| |
| Raises: |
| FileNotFoundError — if the file does not exist. |
| ValueError — if any line contains malformed JSON. |
| """ |
| p = Path(path) |
| if not p.exists(): |
| raise FileNotFoundError(f"Audit log file not found: {p}") |
| events: list[AuditEvent] = [] |
| with p.open("r", encoding="utf-8") as f: |
| for line_num, raw_line in enumerate(f, start=1): |
| line = raw_line.strip() |
| if not line: |
| continue |
| try: |
| data = json.loads(line) |
| except json.JSONDecodeError as exc: |
| raise ValueError( |
| f"Malformed JSON on line {line_num} of {p}: {exc}" |
| ) from exc |
| events.append(audit_event_from_dict(data)) |
| return tuple(events) |
|
|
|
|
| def verify_audit_jsonl(path: str | Path) -> bool: |
| """Return True if the JSONL file at path contains a valid, unbroken audit chain. |
| |
| Returns False — without raising — when: |
| - the file is missing |
| - the chain hash linkage is broken |
| - any event hash does not match its stored fields |
| |
| An empty file is treated as a valid (empty) chain and returns True. |
| """ |
| p = Path(path) |
| try: |
| events = read_audit_events_jsonl(p) |
| except FileNotFoundError: |
| return False |
| return verify_audit_chain(events) |
|
|