mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 14:16:12 +00:00
fix(insights): let a read grant read the record's targets
The weight band and the BMI chart read `/api/insights/targets` inside a shared record, and the route asked for a manage grant, so a read or write delegate got a 403 and saw no band and no BMI. The targets are the bands the dashboard snapshot already paints on a whole-record read grant, computed with no provider, so the read now asks for the same level. A section-scoped grant is still refused.
This commit is contained in:
1 parent
ce01d455be
commit
f76e5cdafd
3 files changed
+7
-11
No files matched your search
@@ -1553,7 +1553,7 @@ const DELEGABLE_ROUTES: Record<string, DelegableEntry> = {
|
||||
},
|
||||
"app/api/insights/targets/route.ts": {
|
||||
domain: "record",
|
||||
why: "The record's resolved targets. Reached only through this module's MANAGE arm; the argument for admitting it is the manage literal's reason line, and the entry here is what leg (a) freezes and leg (f) checks the section against.",
|
||||
why: "The record's resolved targets: the bands and reference ranges the dashboard snapshot already paints on the whole-record grant, read the same way, with no provider on the path. GET only; nothing is written here.",
|
||||
},
|
||||
"app/api/insights/ecg/route.ts": {
|
||||
domain: "record",
|
||||
@@ -2099,11 +2099,6 @@ const DELEGABLE_MANAGE_ROUTES: Record<string, ManageEntry> = {
|
||||
conditions: [],
|
||||
why: "A score's daily course, read from the same rows the score pages and the day view read. Deterministic, no provider, nothing enqueued.",
|
||||
},
|
||||
"app/api/insights/targets/route.ts": {
|
||||
domain: "record",
|
||||
conditions: [],
|
||||
why: "The record's resolved targets.",
|
||||
},
|
||||
"app/api/insights/ecg/route.ts": {
|
||||
domain: "record",
|
||||
conditions: [],
|
||||
@@ -2305,7 +2300,7 @@ const ACTOR_ROUTES: Record<string, string> = {
|
||||
* make, or a managed profile's guardian's, never an adult record's delegate's.
|
||||
* 276 -> 278.
|
||||
*/
|
||||
const FROZEN_ENTRY_COUNT = 278;
|
||||
const FROZEN_ENTRY_COUNT = 277;
|
||||
|
||||
/**
|
||||
* The two surfaces that authenticate a Bearer token outside `requireAuth` —
|
||||
|
||||
@@ -15,9 +15,10 @@ import { buildTargetsResponse } from "@/lib/targets/build-response";
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export const GET = apiHandler(async () => {
|
||||
// v1.37.0 — MANAGE-level read: computed over the whole record, with no
|
||||
// provider anywhere on the path.
|
||||
const { user } = await requireRecordAuth("manage", "record");
|
||||
// A read of the whole record, with no provider anywhere on the path, at the
|
||||
// level the dashboard snapshot answers: the weight band and the BMI chart
|
||||
// read it inside a shared record, and a read or write grant got a 403.
|
||||
const { user } = await requireRecordAuth("read", "record");
|
||||
// v1.16.8 — stale-while-revalidate read. The cold build is the multi-query
|
||||
// walk in `buildTargetsResponse`; past the 60 s fresh TTL the prior body
|
||||
// serves immediately while ONE background rebuild warms the cell. Writes
|
||||
|
||||
@@ -541,7 +541,7 @@ export const insightsPaths: NonNullable<ZodOpenApiObject["paths"]> = {
|
||||
tags: ["Insights"],
|
||||
summary: "Per-metric target tiles + consistency strips",
|
||||
description:
|
||||
"The whole targets page in one read: a tile per metric with its band, its current and 30-day-average values, and the seven-day consistency strip behind it, plus the page summary, the diastolic companion figures and the profile facts every band was resolved from. Pure compute over the record's own data — no provider anywhere on the path. Stale-while-revalidate: past the fresh window the prior body serves immediately while one background rebuild warms the cell, and a write hard-evicts the bucket so the account's own action lands on the next read. Delegable at MANAGE level over the whole record: the walk spans vitals, sleep, medications, mood and glucose, so a section-scoped grant is refused rather than served a filtered page. Not module-gated as a whole; a tile whose data does not exist is simply absent. Cookie or Bearer auth.",
|
||||
"The whole targets page in one read: a tile per metric with its band, its current and 30-day-average values, and the seven-day consistency strip behind it, plus the page summary, the diastolic companion figures and the profile facts every band was resolved from. Pure compute over the record's own data — no provider anywhere on the path. Stale-while-revalidate: past the fresh window the prior body serves immediately while one background rebuild warms the cell, and a write hard-evicts the bucket so the account's own action lands on the next read. Delegable at READ level over the whole record, like the dashboard snapshot: the walk spans vitals, sleep, medications, mood and glucose, so a section-scoped grant is refused rather than served a filtered page. Not module-gated as a whole; a tile whose data does not exist is simply absent. Cookie or Bearer auth.",
|
||||
responses: {
|
||||
...recordRefusal(),
|
||||
"200": {
|
||||
|
||||
Reference in new issue
Block a user