mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 06:06:10 +00:00
feat: lay down the shared v1.42 contract
Land every file the v1.42 work shares before any of it is built, so the parts built side by side never edit the same file. Schema and migrations 0379-0386: the HEALTH_CONNECT measurement source, ImportJob.kind, the air-quality columns and the per-user switch on the environment tables, sealed environment locations with nullable plaintext, managed-profile handover links, the per-type HealthKit arrival ledger, lab unit-conversion provenance, and user-defined vaccines. Every new column is annotated for the column-reader sweep. HEALTH_CONNECT reaches every exhaustive list: the source labels, the read enum (not the write allowlist), the source-priority ladder cap, now the number of sources rather than a fixed eight, and the write-path smoke test. RMSSD gets its own priority class and a 300 ms ceiling. The batch syncTrigger gains `manual`, with its label on the Apple Health card. Queues for the Health Connect import, the compaction-tombstone purge and the measurement-table maintenance are registered with stub handlers; host-metric sampling moves to every five minutes. Environment and work_mem variables reach the compose whitelist, the production example and the manifest. The proxy admits the handover link and redirects it at the edge, and the Health Connect upload bypasses the proxy body limit. The new routes answer 501 behind their final authentication and are published in the contract; backup, wipe and key rotation classify the new models and columns; the seven message bundles carry the base copy. Guards that list release-pending keys and columns fail once an entry is wired, so those lists empty themselves before the tag. Also re-anchor a login-throttle fixture to the wall clock, where its expiry is compared, and re-base the aggregate bundle cap on a measured 12 KB of new locale strings.
This commit is contained in:
1 parent
125b15bfb7
commit
e63be67b5a
82 files changed
+5099
-727
No files matched your search
@@ -402,6 +402,11 @@ const EXEMPT_ROUTES: ReadonlyArray<string> = [
|
||||
"src/app/api/vaccinations/[id]/route.ts",
|
||||
"src/app/api/vaccinations/[id]/restore/route.ts",
|
||||
"src/app/api/vaccinations/[id]/links/route.ts",
|
||||
// v1.42 (#1005) — the record's own vaccine definitions. Same posture: a
|
||||
// restore must bring back the definitions its doses point at with the
|
||||
// surface hidden.
|
||||
"src/app/api/vaccinations/custom/route.ts",
|
||||
"src/app/api/vaccinations/custom/[id]/route.ts",
|
||||
// The booster mint and the upload suggestion are the same posture: a restore
|
||||
// or an import that arms a booster, or a document review that files a scan,
|
||||
// must keep working with the surface hidden, so the data routes stay exempt
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* `POST /api/admin/maintenance/measurements` — queue the operator-triggered
|
||||
* `VACUUM (ANALYZE)` / `REINDEX INDEX CONCURRENTLY` pass over `measurements`
|
||||
* (v1.42). Cookie-only admin: `requireAdmin()` refuses every Bearer.
|
||||
*
|
||||
* Contract stub: authenticates as the finished route will, then answers 501.
|
||||
*/
|
||||
import { apiHandler, requireAdmin } from "@/lib/api-handler";
|
||||
import { notImplemented } from "@/lib/http/not-implemented";
|
||||
|
||||
export const POST = apiHandler(async () => {
|
||||
await requireAdmin();
|
||||
return notImplemented();
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* `POST /api/auth/claim/preview` — what a handover link would hand over: the
|
||||
* profile's display name and each guardian's proposal (v1.42, #959).
|
||||
* Anonymous, token in the body, the same uniform 404 as the claim itself.
|
||||
*
|
||||
* Contract stub: answers 501.
|
||||
*/
|
||||
import { apiHandler } from "@/lib/api-handler";
|
||||
import { notImplemented } from "@/lib/http/not-implemented";
|
||||
|
||||
export const POST = apiHandler(async () => notImplemented());
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* `POST /api/auth/claim` — claim a managed profile through its handover link
|
||||
* (v1.42, #959). Anonymous by design: the person claiming has no credentials
|
||||
* yet. The one-time `hlp_` token travels in the body, never in the URL of
|
||||
* this request, and every failure answers the same 404.
|
||||
*
|
||||
* Contract stub: answers 501.
|
||||
*/
|
||||
import { apiHandler } from "@/lib/api-handler";
|
||||
import { notImplemented } from "@/lib/http/not-implemented";
|
||||
|
||||
export const POST = apiHandler(async () => notImplemented());
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* `PATCH /api/environment/preferences` — the caller's environment switches,
|
||||
* today `airQualityEnabled` (v1.42, #615). Module-gated like the rest of the
|
||||
* environment surface.
|
||||
*
|
||||
* Contract stub: authenticates as the finished route will, then answers 501.
|
||||
*/
|
||||
import { apiHandler, requireAuth } from "@/lib/api-handler";
|
||||
import { notImplemented } from "@/lib/http/not-implemented";
|
||||
import { requireModuleEnabled } from "@/lib/modules/gate";
|
||||
|
||||
export const PATCH = apiHandler(async () => {
|
||||
const { user } = await requireAuth();
|
||||
const gate = await requireModuleEnabled(user.id, "environment");
|
||||
if (!gate.enabled) return gate.response;
|
||||
return notImplemented();
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* `POST /api/import/health-connect-export` — upload an Android Health Connect
|
||||
* export ZIP for background import (v1.42, #972). Outside the proxy matcher
|
||||
* like the Apple Health upload (`src/lib/http/proxy-bypass-routes.ts`), so the
|
||||
* body is streamed to disk after authentication instead of being buffered.
|
||||
*
|
||||
* Contract stub: authenticates as the finished route will, then answers 501.
|
||||
*/
|
||||
import { apiHandler, requireAuth } from "@/lib/api-handler";
|
||||
import { notImplemented } from "@/lib/http/not-implemented";
|
||||
|
||||
export const POST = apiHandler(async () => {
|
||||
await requireAuth();
|
||||
return notImplemented();
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* `GET /api/import/health-connect-export/status` — the caller's most recent
|
||||
* Health Connect import job (v1.42, #972).
|
||||
*
|
||||
* Contract stub: authenticates as the finished route will, then answers 501.
|
||||
*/
|
||||
import { apiHandler, requireAuth } from "@/lib/api-handler";
|
||||
import { notImplemented } from "@/lib/http/not-implemented";
|
||||
|
||||
export const GET = apiHandler(async () => {
|
||||
await requireAuth();
|
||||
return notImplemented();
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* `POST /api/managed-profiles/[id]/handover` — mint the one-time link that
|
||||
* hands a managed profile over to the person it describes, with a proposal
|
||||
* per guardian; `DELETE` withdraws the open link (v1.42, #959). Cookie-only
|
||||
* like every act on a managed profile; minting is step-up gated, withdrawing
|
||||
* is not.
|
||||
*
|
||||
* Contract stub: authenticates as the finished route will, then answers 501.
|
||||
*/
|
||||
import { apiHandler, requireCookieAuth } from "@/lib/api-handler";
|
||||
import { notImplemented } from "@/lib/http/not-implemented";
|
||||
|
||||
export const POST = apiHandler(async () => {
|
||||
await requireCookieAuth();
|
||||
return notImplemented();
|
||||
});
|
||||
|
||||
export const DELETE = apiHandler(async () => {
|
||||
await requireCookieAuth();
|
||||
return notImplemented();
|
||||
});
|
||||
@@ -181,7 +181,9 @@ const batchEntrySchema = z.object({
|
||||
// client knows its own wake reason, so it is client-asserted by necessity;
|
||||
// being diagnostic-only, that carries no trust surface. Optional so every
|
||||
// pre-#66 caller stays byte-for-byte unchanged.
|
||||
const syncTriggerEnum = z.enum(["foreground", "background", "push"]);
|
||||
// v1.42 (#1173) — `manual` names a user-initiated "Sync all" run, as opposed to
|
||||
// the app simply being open (`foreground`).
|
||||
const syncTriggerEnum = z.enum(["foreground", "background", "push", "manual"]);
|
||||
|
||||
const batchPayloadSchema = z.object({
|
||||
entries: z.array(batchEntrySchema).min(1),
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* `PATCH /api/vaccinations/custom/[id]` edits one of the record's own vaccine
|
||||
* definitions; `DELETE` soft-deletes it, leaving every dose logged against it
|
||||
* on its `vaccineName` (v1.42, #1005). Record-scoped like the dose log.
|
||||
*
|
||||
* Contract stub: owner-only until implemented, then answers 501. The
|
||||
* finished routes resolve the record through `requireRecordAuth` like the
|
||||
* dose log and join the sharing guard's delegable lists with their audit
|
||||
* rows; a stub cannot carry those, so it does not claim the admission.
|
||||
*/
|
||||
import { apiHandler, requireAuth } from "@/lib/api-handler";
|
||||
import { notImplemented } from "@/lib/http/not-implemented";
|
||||
|
||||
export const PATCH = apiHandler(async () => {
|
||||
await requireAuth();
|
||||
return notImplemented();
|
||||
});
|
||||
|
||||
export const DELETE = apiHandler(async () => {
|
||||
await requireAuth();
|
||||
return notImplemented();
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* `GET /api/vaccinations/custom` lists the record's own vaccine definitions;
|
||||
* `POST` adds one (v1.42, #1005). Record-scoped like the dose log.
|
||||
*
|
||||
* Contract stub: owner-only until implemented, then answers 501. The
|
||||
* finished routes resolve the record through `requireRecordAuth` like the
|
||||
* dose log and join the sharing guard's delegable lists with their audit
|
||||
* rows; a stub cannot carry those, so it does not claim the admission.
|
||||
*/
|
||||
import { apiHandler, requireAuth } from "@/lib/api-handler";
|
||||
import { notImplemented } from "@/lib/http/not-implemented";
|
||||
|
||||
export const GET = apiHandler(async () => {
|
||||
await requireAuth();
|
||||
return notImplemented();
|
||||
});
|
||||
|
||||
export const POST = apiHandler(async () => {
|
||||
await requireAuth();
|
||||
return notImplemented();
|
||||
});
|
||||
Reference in new issue
Block a user