Case study
Governed personal health data
A self-hosted health data platform with no database port by design: three data streams landed provenance-first, every write dry-run and backup-gated, metric definitions pinned and test-enforced, and the most sensitive artifacts placed where the report web server cannot reach them.A private store for my own health data, running on my own hardware, built so it cannot be quietly corrupted: nothing can connect to the database directly, corrections never erase the original reading, the meaning of each measurement is locked down and tested, and the most personal files are kept somewhere the website cannot reach.
Maturity: OperationalProblem
Personal health data is the hardest category I hold. It arrives from incompatible sources — a strength-training machine, a wearable, my own typed notes — in different shapes, on different clocks, with different ideas of what a unit is. Joining it is the easy part. The hard part is building somewhere to put it that I would still trust in a year: where a correction cannot quietly destroy the original reading, where a metric’s definition cannot drift beneath the narrative written about it, and where the most sensitive artifacts cannot leak through the same web server that publishes the reports.
My own health data is the most sensitive thing I keep. It comes from sources that do not agree with each other — a gym machine, a watch, notes I type myself — in different formats, on different clocks, and with different ideas of what a unit even means. Getting them to line up is the easy part. The hard part is building somewhere to keep it all that I would still trust a year later: where fixing a mistake cannot wipe out the original reading, where the meaning of a measurement cannot shift underneath the conclusions already written about it, and where the most personal files cannot escape through the same website that publishes the reports.
Constraints
In plain terms first: no health company holds this data, nothing can reach the database directly, fixing a wrong figure never destroys the one it replaced, and if a measurement changes meaning that breaks a test rather than quietly changing a conclusion. The precise ground rules:
- No cloud health service. Everything self-hosted; the data never leaves the home network.
- No open database port. If a component needs the data, it goes through the audited interface like everything else.
- Corrections must be non-destructive. A wrong reading is superseded, never overwritten.
- A definition is a versioned artifact. If a metric changes meaning, that must break a test, not a conclusion.
- The narrative on this page carries no health values — no biometrics, physiology, nutrition, or body composition; architecture only. The screenshot below is the one deliberate exception: a real training report of my own, captured from the shareable copy with internal identifiers removed — training figures I have chosen to publish.
Architecture
The store is a single self-hosted PostgreSQL database, and the notable thing about it is what is missing:
Everything lives in one database on my own hardware, and the interesting thing about it is what has been left out:
- No database port is published. The only path in is a bearer-gated service on an internal-only network.
- Ad-hoc queries run as a least-privileged read-only role, separate from the role that writes.
- Every mutating operation is dry-run by default and backup-gated, serialized by both a service mutex and a cross-process lock so two agents cannot write at once.
Data moves through layers rather than landing in final form:
Nothing arrives in its finished state; it passes through stages, and each stage keeps the one before it intact:
- Raw — immutable payloads paired with manifests, keyed by hash so the same file ingested twice changes nothing.
- Canonical — unit-explicit, reconciled across sources. Corrections create a new generation; the original stays readable.
- Derived — a curated view layer and star marts that reporting reads.
Two defences sit on the seams. Unknown input headers hard-stop the ingest rather than being guessed at, and metric definitions are version-pinned in catalogues and enforced by tests against the views actually deployed — on both database engines the platform has run on.
Two safeguards sit at the joins. If an incoming file contains a column the system does not recognise, it stops rather than guessing what it means. And the definition of every measurement is written down, version-controlled, and checked by automated tests against what is actually running — on both of the database engines this platform has lived on.
AI and agent workflow
Agents operate the platform through the same gated interface a human would use, with the same defaults:
AI agents work the platform through exactly the same guarded doorway a person would, with exactly the same safety settings:
- Read freely, write deliberately — a mutating verb runs dry by default and reports what it would do; committing is a separate act.
- Backups gate the writes — no mutation proceeds without its backup precondition satisfied.
- Concurrency is refused, not managed — a cross-process lock means the second agent waits rather than racing.
The published artifacts are deliberately two-layer:
The reports it produces are deliberately built in two separate halves:
- Numbers are refreshed by a scheduled job.
- Interpretation is injected verbatim from a frozen layer.
- No language model runs in the nightly path, so a report cannot re-narrate itself between one night and the next. The narrative changes only when a human changes it.
Explore 'Mission Control' in Guest Mode
Human governance
What keeps the platform answerable rather than merely working:
- A written runtime contract is the authority, and it is model-neutral — when an adapter and the contract disagree, the contract wins.
- Documentation is generated where it can be — the schema reference is produced from the database with a staleness check, not hand-maintained.
- Backups are drilled, not assumed — restores run into a disposable database, pass a multi-point verification, and the verdict is written back to the backup manifest. The procedure has been exercised on a multi-gigabyte archive.
- The engine migration kept an escape route — the move to PostgreSQL shipped with a frozen pre-cutover artifact and a hash-audited forward path. When the documented rollback stopped being a flag flip and became a restore, the document was corrected rather than left comforting and wrong.
- The most sensitive artifacts are placed out of reach — body-composition images are content-hashed onto a private volume the report web server has no access to, and the ingest tool refuses rather than guessing a destination if one is not configured.
Evidence
The platform evidences itself, privately:
The platform keeps its own proof, privately:
- Schema and migrations — a versioned migration history, applied and verified, with a generated schema reference.
- Tests — a suite that includes the definition-pinning checks, so a view that drifts from its catalogue fails rather than reports.
- A delivery history — an issue tracker recording what shipped, what broke, and what was reversed, plus a wiki carrying the architecture pages.
Stated plainly, because this page is about trustworthiness: the platform, its gated interface, its ingestion, and its backup discipline are operational and artifact-verified. The weekly training review is manual-run only — the nightly profile is the scheduled one. The approval-policy mechanism is nearly unused: one automatic grant exists and everything else is manual by absence rather than by design. The wearable side is integrated and normalized, not built — the bridge is a self-hosted third-party component, and my contribution is the compatibility work and the credential boundary around it. Body-composition interpretation has a schema but no writing tool yet.
Outcome
- A personal health store with no database port to attack, one audited way in, and a least-privileged role for casual reading.
- Ingestion that cannot silently corrupt history: hash idempotency, superseding corrections, and a hard stop on unrecognized input.
- Metric definitions that cannot drift unnoticed, because a drifting definition fails a test on both engines.
- Reports that cannot re-narrate themselves, because the nightly path contains no model.
- A restore procedure that has actually been run, scored, and recorded against a real archive.
Lessons
What I would carry into the next system of this kind:
- The strongest privacy control was a subtraction: not publishing the database port at all removed an entire category of risk that no amount of access-control configuration would have.
- Corrections should supersede, not overwrite. Every destructive fix is a future question you can no longer answer.
- Pin the definition, not just the number. A metric that changes meaning quietly is worse than a metric that is missing.
- A backup you have never restored is a hypothesis. Drilling it into a disposable copy is what turns it into a fact.
- Keep the model out of the paths that must be reproducible. Intelligence belongs where judgment is wanted, not where consistency is.
