ADR-003 — Raw data lake as gzip JSON Lines on disk
Status: accepted (2026-09-08)
Context
Provenance-first means every canonical or derived value must be traceable to the exact upstream payload that produced it (CLAUDE.md §1, §26). Payloads are heterogeneous JSON (and TSV converted to JSON records), volumes range from hundreds of rows (OncoTree) to millions (ClinicalTrials.gov, PubMed), and connectors run repeatedly. Storing raw payloads in PostgreSQL would bloat the canonical database and slow backups; an object store adds an external dependency the single-node deployment does not otherwise need.
Decision
Raw payloads are written by RawLake (packages/connectors/src/sdk/lake.ts) to the local
filesystem under CI_DATA_DIR/raw, organised as
data/raw/{source}/{YYYY-MM-DD}/{entity}/{runId}-{part}.jsonl.gzOne record per line, gzip-compressed, files rotated by size. source_records keeps, per upstream
record, the sha256 payload_hash and the raw_path of the file that contains it, plus the first and
last run that saw it. Manifests declare rawRetention: full | summary | none; dry_run mode never
writes to the lake. The lake directory is git-ignored and excluded from deployment syncs.
Consequences
- Idempotency and change detection are cheap (hash compare) and independent of the lake.
traceValue()can point to the exact file for any observation; re-ingestion or re-derivation is possible without hitting the upstream API.- Files are append-only per run; nothing in the lake is ever rewritten. Retention policies (e.g. keep the last N runs of high-volume sources) are an operational decision handled by a cleanup job, not by connectors.
- JSON Lines gzip is directly readable by DuckDB (
read_json_auto) and Python, which keeps the door open for the analytical workers of ADR-002 and for a later Parquet conversion. - Backups must include both the PostgreSQL dump and
data/raw; the deploy notes list the lake as a path to back up on the node (it is not synced bymld).