mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
An owner can now narrow a grant to the parts of their record they mean to share, and can offer the third level. Both decisions are made once, on the invitation, and neither widens afterwards. The invite schema takes `scope`: absent or null is the entire record, which is what every stored grant means and what an owner who does not narrow still gets. An empty array, a key outside the vocabulary and the whole-record sentinel are each refused with a 422 naming the field; `inviteGrant` refuses the same three underneath, and its `invalid_scope` now has an HTTP mapping instead of falling into the generic refusal. Offering MANAGE asks for a fresh second factor when the account has one. That gate resolves through the session cookie and has no Bearer fall-through, so a token caller is refused first with `sharing.invite.manage_browser_only` rather than being told it is not authenticated by a route that had just authenticated it. The consequence is deliberate: management of a health record is offered from a browser. The account payload gains `sections` and `level` beside `access` and `canWrite`, resolved server-side and ordered as the consent screen reads. `access` was `grant.access === "WRITE" ? "write" : "read"`, which would have published a grant that can delete entries as read-only; it is a total map now, so the next level cannot ship silent. The capabilities hook carries both facts and turns neither into an affordance yet — the edit and delete controls come back in the release where the routes behind them answer. The consent surface asks two questions in order rather than laying out a nineteen-cell grid. The level is three radios, narrowest preselected. What they can open is two radios, the entire record preselected, and only the narrow branch expands the eight sections with a sentence each. Both honest limits are stated above the checkboxes, where the decision is being made: a section shares everything written in it, and the cross-record surfaces do not appear for a narrowed grant. Documents carries its own warning, cycle appears only for an owner who tracks it, and Manage collapses the question to the one line that answers it. The Manage sentence says the other person can add, change and remove entries, including ones the owner entered themselves, and says in the same breath what it does not reach. The acceptance side gets its own responsibility line rather than the write one wearing a stronger adjective. Six locales.
src/lib/
The server and runtime toolkit. Everything that is not a page, component, or route handler lives here: the API kit, auth, crypto, the rollup tier, AI providers, notifications, background jobs, and the integration clients. Route handlers and RSC pages compose these modules; the modules never reach back into app/.
Core request kit
api-handler.ts— theapiHandlerwrapper every route uses. Auth resolver (cookie OR Bearer),requireAuth/requireAdmin(admin is cookie-only by construction), idempotency plumbing, Wide-Event builder, GlitchTip forwarder.api-response.ts— the{ data, error, meta? }envelope:apiSuccess/apiError,safeJson, trusted-proxy IP resolver,returnAllZodIssues+sanitiseZodIssues.rate-limit.ts— Postgres-backed sliding-window limiter (single atomic upsert); multi-instance correct.idempotency.ts— user-scoped idempotency keys that refuse to cache secret-shaped responses.query-keys/— the only legal source of TanStack QueryqueryKey/mutationKeyarrays (enforced by an ESLint rule); per-feature files behind theindex.tsbarrel.safe-fetch.ts— the one documented outbound-egress entry (redirect: "manual"+ timeout; optional connect-time DNS-rebinding pin). Rawfetch(underlib/+app/is banned by ESLint.db.ts— the Prisma client singleton.crypto.ts— AES-256-GCM at rest with versioned key ids, fail-closed loader, rotation primitives.
Subsystems
auth/— Postgres sessions, secure-cookie flag policy, passkey (WebAuthn) + Argon2id password, per-device refresh-token rotation, HMAC Bearer tokens.ai/— multi-provider insight + Coach engine:provider-chain.ts, hand-rolled{openai,anthropic,local,codex,mock}-client.ts,coach/,prompts/, citation + confidence helpers.rollups/— DAY/WEEK/MONTH/YEAR pre-aggregations for measurements, mood, and medication compliance. Read-swap pattern: probe the rollup, fall back to live SQL on a coverage miss.notifications/— the dispatcher cascade (APNs → Telegram → ntfy → Web Push), per-channelsenders/, retry policy, VAPID config.jobs/— pg-boss workers: reminders, insight pre-generation, backups, retention sweeps, integration backfills. Recurring work belongs here, not inscripts/.integrations/,withings/,whoop/,fitbit/— OAuth clients and sync services for the device providers;sync/holds the shared delta-feed plumbing.analytics/— trend calculations, cadence-aware medicationcompliance.ts, correlations.insights/,fhir/,doctor-report-*.ts— insight pipeline, FHIR R4 export, client-side doctor-report PDF.openapi/— Zod registry that is the source of truth fordocs/api/openapi.yaml.logging/,observability/,monitoring/— Wide-Event transports and the central redaction denylists (redact at the egress boundary, not the call site).validations/— shared Zod schemas, including the SSRFisPublicUrlfloor.i18n/,tz/+timezone.ts,format*.ts— translations, timezone helpers (Berlin for display, UTC in DB), locale formatting.
Conventions
Kebab-case filenames. See ../../CLAUDE.md "Code conventions" and "Security-relevant patterns"; the critical-files map there cites the exact entry points. Most modules carry tests in a sibling __tests__/.