mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 06:06:10 +00:00
fix(vaccinations): keep the booster mint inside the sections the grant opens
`POST /api/vaccinations/{id}/booster` is declared `("write", "profile")`, and
that is right for the act — filing the Impfpass and arming the booster it
suggests are one thing to the person doing them. The row it writes is not: a
`MeasurementReminder` belongs to the measurements section, every direct route
over the model declares it there, and creating one needs MANAGE. So a WRITE
grant scoped to the health background alone produced a reminder on the owner's
checkup list that the delegate could neither read nor change through the
section that owns it.
The seam is fenced the way the visits service fences a checkup closure, with
one difference stated at the call site: this refuses rather than skips. There
the closure is one effect of a larger save and reporting it as not done keeps
the save honest; here the reminder is the whole request, and a 201 over a row
that was never written would tell the person their booster is planned while
nothing rings. The prompt that raises the call asks the same question client-
side, so a delegate outside the section is not offered it in the first place;
the dose itself still logs either way.
`vaccination-booster-scoped-delegate.test.ts` drives the owner, a delegate
holding both sections, a profile-only delegate and a whole-record one. The
scoped-refusal leg fails against the previous handler, which answered 201 and
left the row.
This commit is contained in:
1 parent
0ee26625be
commit
1b74bf8b54
5 files changed
+231
-7
No files matched your search
@@ -362,14 +362,17 @@ export const vaccinationPaths: NonNullable<ZodOpenApiObject["paths"]> = {
|
||||
tags: ["Records"],
|
||||
summary: "Plan a booster reminder from a dose",
|
||||
description:
|
||||
"Mints (or re-anchors) the booster reminder a logged dose suggests. The minted row is an ordinary `origin: VORSORGE` measurement reminder carrying the dose's primary antigen as a server-side match key; it lists on the preventive-care surface and rings through the same engine as every other checkup. Re-confirming re-anchors the existing reminder instead of minting a second one: 201 carries a fresh mint (`minted: true`), 200 a re-anchor. A free-text-only dose has no antigen to remind on and is refused with 422. Audits as `vaccination.booster.planned`.",
|
||||
"Mints (or re-anchors) the booster reminder a logged dose suggests. The minted row is an ordinary `origin: VORSORGE` measurement reminder carrying the dose's primary antigen as a server-side match key; it lists on the preventive-care surface and rings through the same engine as every other checkup. Re-confirming re-anchors the existing reminder instead of minting a second one: 201 carries a fresh mint (`minted: true`), 200 a re-anchor. A free-text-only dose has no antigen to remind on and is refused with 422. Audits as `vaccination.booster.planned`.\n\n" +
|
||||
"The operation is declared under the health background and the row it mints belongs to the measurements section, so it crosses a section boundary: a delegate whose grant does not also open `measurements` is refused with 403 `vaccination.booster-out-of-scope` and nothing is written. The dose itself still logs — only the booster does not.",
|
||||
requestParams: idPath,
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: { "application/json": { schema: vaccinationBoosterRequest } },
|
||||
},
|
||||
responses: {
|
||||
...recordRefusal(),
|
||||
...recordRefusal(
|
||||
"Refused: the caller's grant does not open this record's measurements, and the booster reminder lives there (`meta.errorCode` = `vaccination.booster-out-of-scope`). Nothing was written.",
|
||||
),
|
||||
"200": {
|
||||
description: "Existing booster reminder re-anchored.",
|
||||
content: {
|
||||
|
||||
Reference in new issue
Block a user