docs: publish the managed-profile lifecycle routes

The contract already pointed at them: the Guardian roster names
`DELETE /api/managed-profiles/{id}/guardians/{grantId}` as the route its
`grantId` is for, and that route was not in the spec. Creation, deletion,
invitation and removal now are, with the two bodies moved to
`src/lib/validations` so the wire shape has one source.

Three things are stated rather than left to be discovered:

- the two refusals on the invitation that carry no `meta.errorCode`, and the
  order a client must resolve in because of them
- the last-Guardian 409 on the removal route, which nothing can elicit through
  that route today, published with the reason
- that the sentences on the ordinary invitation describe an invited grant only,
  so a client cannot carry its MANAGE rules across to a Guardian grant
This commit is contained in:
Marc-André Bombeck committed 2026-08-07 04:42:51 +02:00
1 parent 26fceca219
commit ec9d93aa24
5 files changed
+640 -28

No files matched your search

+37
View File
@@ -0,0 +1,37 @@
/**
* v1.37.0 — the two bodies the managed-profile family accepts.
*
* Here rather than beside each route because the OpenAPI table publishes them,
* and a schema written twice is a contract that drifts on the third change.
* The route modules import these; nothing imports the route modules.
*
* Both are `.strict()`. A managed profile is a person's health record created
* by somebody else, so a field the sender did not mean to send is refused
* rather than ignored — and the client types name exactly these fields for the
* same reason (`src/lib/queries/use-managed-profiles.ts`).
*/
import { z } from "zod/v4";
import { isValidTimezone } from "@/lib/tz/format";
/** The body `POST /api/managed-profiles` accepts. */
export const createManagedProfileSchema = z
.object({
displayName: z.string().trim().min(1).max(80),
dateOfBirth: z.iso.date().nullable().optional(),
locale: z.enum(["de", "en", "es", "fr", "it", "pl"]),
timezone: z
.string()
.min(1)
.max(64)
.refine(isValidTimezone, "Invalid IANA timezone"),
})
.strict();
/** The body `POST /api/managed-profiles/{id}/guardians` accepts. */
export const inviteManagedProfileGuardianSchema = z
.object({
identifier: z.string().trim().min(1).max(255),
expiresAt: z.iso.datetime({ offset: true }).nullable().optional(),
})
.strict();