Skip to content
Updated Jun 9, 2026

Data Protection

How Olly handles identity, PHI/PII, consent, erasure, and audit. This page separates two registers and leads with what is actually implemented today, because the at-rest story in particular has been over-claimed elsewhere. Design-stage work is marked as such.

Current state vs design

Everything under "Implemented today" is verified in code. Everything under "Designed, not yet built" is draft architecture (see Designs → Access Control) with no running implementation.

Authentication (implemented)

Identity is Keycloak, one realm named olly (not the multi-realm split described in older drafts). Keycloak runs at auth.dev.hiolly.com.

ClientGrantAppJWT claim
web-employerPKCE (auth code)employer SPAorg_locator
web-memberROPC (password)member SPAparty_locator
web-admindirect grantadmin console
web-providerpublicprovider portal
mobile-appdirect grantmobile

So PKCE is the employer flow; the member app uses ROPC. The JWT's org_locator (employer) or party_locator (member) scopes every request. nginx fronts dev-1; APISIX (:9080) is the gateway for the Go services. See the API Quickstart for the concrete token flow.

PHI / PII at rest (implemented)

PHI is not application-encrypted today

Party PII (name, email, phone, address, date of birth) is stored as plaintext columns in policy_admin.parties via plain GORM writes; List() even does plaintext ILIKE search on name/email. A grep for encrypt|kms|vault|aes|pgcrypto across policy-admin and consent returns zero matches. At-rest protection therefore relies only on database/disk-level encryption, not column or field-level application encryption. Do not rely on app-layer encryption when reasoning about data exposure.

This matches the canonical Party concept caveat in the catalog; the field-level encryption design below is aspirational.

consent.consent_records is keyed (party_locator, consent_type) with a unique constraint (at most one record per pair) and a granted boolean (default false). consent_type is free TEXT (no DB CHECK); conventional values are NHS_DATA_SHARING, WEARABLE_SYNC, MARKETING_COMMUNICATIONS. PUT /consent/{partyLocator} upserts; the member app reads GET /me/consent. See the Consent domain page.

consent.consent_audit is the real audit table: append-only (a row per consent change, never updated or deleted), with old_value/new_value/changed_by/changed_by_type/reason/occurred_at, read via GET /consent/{partyLocator}/audit. It is consent-scoped only: a single cross-domain audit log for every sensitive action is a goal, not a built system. (Note a SOFT erasure nullifies the reason column for that party, so value history is preserved but the free-text reason is erasable.)

GDPR Article 17 erasure (implemented)

Right-to-erasure is implemented in the consent service.

  • POST /consent/{partyLocator}/deletion accepts deleteType SOFT | HARD and requestedBy MEMBER | EMPLOYER; a duplicate active request (PENDING/PROCESSING) is rejected with 409.
  • A background ErasureJob polls pending requests (default every minute), marks them PROCESSING, then: SOFT nullifies the free-text reason columns and retains the rest; HARD transactionally deletes the party's consent_records, consent_audit, and deletion_requests. It retries up to 3 attempts, then marks FAILED, else COMPLETED.
  • Fan-out is event-driven, not a direct cross-service purge. The consent service erases only its own tables and emits consent.deletion.requested (then deletion.completed/deletion.failed) on Kafka; other PHI-holding services are expected to react. A SOFT delete does not anonymise the policy_admin party PII.

Retention and PHI audit logging (claimed, not yet built)

architecture/auth.md describes 6-year WORM audit retention (CloudTrail + S3 Object Lock), an OpenSearch audit-logs index with KMS encryption, and per-response PHI access logging for HIPAA §164.312(b). That describes an aspirational AWS/Istio/OPA target, not the current stack (dev runs Docker Compose on Hetzner, one realm, no service mesh). The consent tables carry no modelled retention policy of their own.

Designed, not yet built

These live in Designs → Access Control at draft status, with zero Go implementation:

  • Field-level encryption / PII vault: AES-256-GCM per-record DEKs, KEK in OpenBao Transit, Shamir k-of-n unseal (designs/authz/at-rest.md).
  • ABAC + tokenisation access face: attribute-based authorization and blind-index tokenisation (designs/authz/abac.md).
  • Platform-wide audit + PHI access logging to a central store with OPA decisions.

Olly Health Insurance Platform