feat(mental-health): make the screener score rows server-owned COMPUTED measurements

Each completed PHQ-9 / GAD-7 administration already projects its total
onto a PHQ9_SCORE / GAD7_SCORE measurement row (unit score, linked via
externalId assessment:<id>). Those rows were attributed MANUAL — a
client-writable source. The projection is a server-derived value, so it
now writes source COMPUTED, the same server-owned source RECOVERY_SCORE
uses: no client write surface can attribute COMPUTED, so a forged score
trend point cannot enter through the measurement POST. Migration 0225
re-attributes the existing assessment-linked projection rows; a score a
user typed into the generic measurement form keeps MANUAL. OpenAPI
description updated and the spec regenerated.
This commit is contained in:
Marc-André Bombeck committed 2026-07-05 14:54:17 +02:00
1 parent f7920d81a6
commit b6aedfeb61
7 files changed
+64 -10

No files matched your search

@@ -470,6 +470,24 @@ describe("measurement validation", () => {
}
});
it("rejects a COMPUTED-attributed screener score row on a client write", () => {
// v1.27.6 — the mental-wellbeing screener projects each completed
// administration onto a COMPUTED-source PHQ9_SCORE / GAD7_SCORE row
// (the RECOVERY_SCORE precedent). COMPUTED is not client-attributable,
// so a client can never mint a server-owned score trend point.
for (const type of ["PHQ9_SCORE", "GAD7_SCORE"]) {
const parsed = createMeasurementSchema.safeParse({
type,
value: 5,
measuredAt: "2026-06-02T12:00:00Z",
source: "COMPUTED",
});
expect(parsed.success, `expected COMPUTED ${type} to be rejected`).toBe(
false,
);
}
});
it("accepts the client-writable sources MANUAL and APPLE_HEALTH", () => {
for (const source of ["MANUAL", "APPLE_HEALTH"]) {
const parsed = createMeasurementSchema.safeParse({