Give the intake toast one decision, and stop admitting two writes nothing calls

Three surfaces record a dose and all three had to reach the same two
conclusions inside somebody else's record: name the record, and drop an
Undo the server refuses. Two learned it and the dose ledger did not,
because the ternary was written out three times. It is written once now,
in the file the other two already share, with its own test.

POST /api/allergies and POST /api/family-history leave the frozen
delegable write set. The argument that admitted them still stands — an
allergy is the single most useful thing a caregiver can contribute — but
the only surface that posts to either lives in Settings, which a switch
closes, so no delegate could reach the form at any level. That is a
permission frozen ahead of the caller for it, and the list is built the
other way round on purpose.

Both delegable READ arms stay. A caregiver reading the allergy list is
what the feature is for; only the contribute step waits, and it comes
back in the same diff as the surface that offers it.
This commit is contained in:
Marc-André Bombeck committed 2026-08-03 18:47:42 +02:00
1 parent e26a8f3824
commit ebf032158e
9 files changed
+262 -65

No files matched your search

+32 -5
View File
@@ -11,7 +11,7 @@
import { NextRequest } from "next/server";
import { prisma } from "@/lib/db";
import { apiHandler, requireRecordAuth } from "@/lib/api-handler";
import { apiHandler, requireAuth, requireRecordAuth } from "@/lib/api-handler";
import { annotate } from "@/lib/logging/context";
import { auditLog } from "@/lib/auth/audit";
import {
@@ -67,10 +67,37 @@ export const GET = apiHandler(async (request: NextRequest) => {
export const POST = apiHandler(withIdempotency<[NextRequest]>(postAllergy));
async function postAllergy(request: NextRequest): Promise<Response> {
// v1.36.x — a delegated write, and the cleanest of them: the row is a plain
// statement about the record's own body, and the caller appears in the audit
// trail rather than in the row.
const { user } = await requireRecordAuth("write");
// v1.36.x — the GET above is delegable and this is not, which is the
// opposite of where the classification landed and worth the paragraph.
//
// Nothing about the row changed: it is still a plain statement about the
// record's own body, still the single most useful thing a caregiver could
// contribute, and the argument for admitting it still holds. What it does
// not have is a caller. The only place in the product that posts here is the
// allergy manager in Settings → Anamnese, and `/settings/*` is not a
// shared-record destination — the shell shows the "not part of what was
// shared" panel there, so no delegate can reach the form at any level.
//
// An admitted write with no reachable surface is the one-ended change this
// repository keeps rediscovering (CLAUDE.md, "A two-ended change carries
// both ends"): the permission ships, the consumer is the follow-up, and
// nothing in the gate notices because every other check proves the other
// end. The frozen list is built the other way round on purpose — its own
// actor-surface note says the rest "arrive as their own diffs; naming them
// before they exist would freeze a guess."
//
// So this arm waits for the surface that would exercise it, and the two
// land together. Choosing that surface is design work, not a fix: allergies
// and family history have exactly one home today, that home is a personal
// account surface a switch rightly closes, and bolting a second copy onto a
// shared page would split one concept across two places. Re-admitting is
// one line here plus one entry in `delegable-surface-guard.test.ts` plus the
// paragraph that argues it — which is exactly the reviewed diff that guard
// exists to force.
//
// The half that was always the point is untouched: a caregiver can still
// READ the allergy list inside the record.
const { user } = await requireAuth();
const { data: rawBody, error: jsonError } = await safeJson(request, {
maxBytes: 16 * 1024,
+17 -6
View File
@@ -12,7 +12,7 @@
import { NextRequest } from "next/server";
import { prisma } from "@/lib/db";
import { apiHandler, requireRecordAuth } from "@/lib/api-handler";
import { apiHandler, requireAuth, requireRecordAuth } from "@/lib/api-handler";
import { annotate } from "@/lib/logging/context";
import { auditLog } from "@/lib/auth/audit";
import {
@@ -62,11 +62,22 @@ export const POST = apiHandler(
);
async function postFamilyHistory(request: NextRequest): Promise<Response> {
// v1.36.x — a delegated write, and the one where third-party health data is
// present by design: the row describes the record's relatives. It is stored
// as the record's own, exactly as the delegable READ arm above already
// serves it, and the audit trail names who entered it.
const { user } = await requireRecordAuth("write");
// v1.36.x — not a delegated write, though the GET above is delegable. The
// reasoning is written out at the sibling arm in `api/allergies/route.ts`
// and is the same here: the only surface that posts to either route is the
// manager in Settings → Anamnese, and `/settings/*` is closed inside a
// shared record, so no delegate can reach the form at any level. An
// admitted write with no reachable caller is a permission frozen ahead of
// the surface that would exercise it.
//
// This arm carried an extra reason to wait. The row describes the record's
// RELATIVES, so it is the one admitted write where third-party health
// information is present by design — and frequently the delegate is that
// relative, asserting a condition about themselves into somebody else's
// record. The classification admitted it and named the discomfort. Landing
// it together with the surface that offers it means the copy on that surface
// can say whose statement the row is, which no route comment can.
const { user } = await requireAuth();
const { data: rawBody, error: jsonError } = await safeJson(request, {
maxBytes: 16 * 1024,