Project · Lifebot V2

lifebot-v2 — Low-Level Design

type lldstatus activelifebot · health · zero-knowledge · crypto

Components

Area Path
Crypto envelope (TS, libsodium injected) packages/crypto/src/{envelope,vault}.ts
Node libsodium loader packages/crypto/src/loaders/node.ts
Python verifier (PyNaCl, cross-language) services/verify/verify.py
Sync engine + aggregation + coach packages/client/src/{engine,aggregate,coach}.ts
Design tokens + primitives packages/ui/{tokens.json,tailwind-preset.cjs,src/}
ZK sync API apps/api/app/{main,auth,db,sync,models}.py, routers/{vault,sync,health}.py
DB schema + RLS apps/api/app/schema.sql
Dashboard SPA apps/web/src/{App.tsx,sodium.ts}
Control plane (vendored SaaS-Boilerplate) apps/platform/
RN scaffold apps/mobile/{App.tsx,src/}
Prod compose + tunnel + backups docker-compose.prod.yml, ops/backup.sh, docs/stage2-linux-runbook.md

Data model

lifebot DB (data plane; role lifebot_app, FORCE RLS on app.user_id GUC per-transaction)
  users(id uuid, iss, sub, email)            -- identity keyed on immutable (iss,sub), UNIQUE
  vaults(user_id PK→users, v, kdf jsonb, salt, wrapped_key jsonb, verifier jsonb)
  records(user_id, dedupe PK, vv jsonb, lamport, device, tombstone, ct, seq bigint)
    -- ct = opaque "nonce.ct" blob; dedupe = HMAC-derived, server can't recover record id
platform DB (control plane; Drizzle migrations from apps/platform — separate database by design)

Crypto envelope (pinned in spec/envelope.md)

  • Argon2id (ops=3, mem=64MiB, 16B salt) → master key; wraps CSPRNG 32B data key (XChaCha20, AAD lifebot/wrap/v1).
  • verifier seals the constant lifebot/verify/v1 — wrong password fails the AEAD tag, no oracle.
  • Records: XChaCha20-Poly1305-IETF, 24B random nonce (multi-device safe), AAD-authenticated header.
  • Same bytes on every platform: libsodium sumo (JS/WASM), PyNaCl (server verify), RN native later.

Configuration (API)

Env Purpose
ADMIN_DATABASE_URL / DATABASE_URL schema apply (admin) / runtime as non-owner lifebot_app
AUTH_DEV_BYPASS local only; prod sets 0
CLERK_JWKS_URL / CLERK_ISSUER / CLERK_AUDIENCE JWT verification (RS256, JWKS)
CORS_ORIGINS, REDIS_URL, OTEL_EXPORTER_OTLP_ENDPOINT edges, cache, telemetry

Interfaces (HTTP)

Method + path Auth Purpose
GET /health none liveness (DB ping)
GET/PUT /v1/vault Bearer JWT fetch/store encrypted key material
POST /v1/sync Bearer JWT push encrypted deltas; version-vector conflict resolution
GET /v1/sync?since= Bearer JWT pull ciphertext deltas by server seq cursor

Sequences

sequenceDiagram
  participant C as Client (web/RN)
  participant I as Clerk
  participant A as API (ZK)
  C->>I: OIDC sign-in (Google SSO)
  I-->>C: JWT
  C->>A: GET /v1/vault (Bearer)
  A-->>C: salt + wrapped key + verifier (no key)
  Note over C: Argon2id(password, salt) → unwrap data key<br/>wrong password = AEAD tag failure
  C->>C: encrypt metrics (XChaCha20, AAD)
  C->>A: POST /v1/sync {dedupe, vv, lamport, ct}
  A-->>C: actions (created/duplicate/stale/conflict) + cursor
  C->>C: decrypt + aggregate + render (client-side only)

Sync conflict rules (apps/api/app/sync.py, pure + unit-tested)

  • Version-vector compare: equal→duplicate · dominates→update · dominated→stale · concurrent→conflict.
  • Conflicts resolve deterministically LWW by (lamport, device); replicas converge order-independently.
  • Deletes propagate as tombstones; server bumps seq (pull cursor) on every write.

Tests (required)

  • npm run spike — TS↔Python crypto interop + nonce uniqueness + AAD tamper + sync harness (8 checks).
  • apps/api/tests/test_sync.py — conflict-logic unit tests.
  • packages/client/test/integration.ts — E2E vs live API: vault create, 24 metrics round-trip, wrong-password reject.
  • RLS negative test: wrong tenant GUC sees 0 rows (manual, promote to CI).
Compiled from wiki/projects/lifebot-v2/LLD.md · git is the source of truth