feat: describe managed sharing activity

Every action a manage-level route files now renders as its own sentence in
the owner's activity view, in all six locales. A write delegate can only add,
so the generic "made a change" was at least the right shape of fact; a manager
changes and removes entries the owner made themselves, and the generic line
withholds the only part that matters — whether a reading was corrected or
deleted, whether a dose history was purged.

The inventory reads the routes and calls the map rather than matching `case`
in its source, so an action that falls through to the fallback fails, and it
follows each resolved key into all six bundles. Every sentence interpolates
the actor's name and nothing else: the audit table is not encrypted and a
value in a verb line would be a health figure in a second store.

The revoke dialog's retention disclosure no longer depends on which of two
requests answers first. `retentionDays` rides the grant list, so a client
holding a row to revoke holds the window it must state, and the branch that
omitted the sentence is gone.
This commit is contained in:
Marc-André Bombeck committed 2026-08-06 23:30:07 +02:00
1 parent 25b1b0eab6
commit c92a80ee4c
15 files changed
+1131 -61

No files matched your search

+16
View File
@@ -56,6 +56,7 @@ import {
} from "@/lib/api-response";
import { auditLog } from "@/lib/auth/audit";
import { prisma } from "@/lib/db";
import { getAuditLogRetentionDays } from "@/lib/jobs/audit-log-cleanup";
import { annotate } from "@/lib/logging/context";
import { checkRateLimit } from "@/lib/rate-limit";
import { GRANT_PARTY_SELECT, toGrantView } from "@/lib/sharing/grant-view";
@@ -102,6 +103,21 @@ export const GET = apiHandler(async () => {
return apiSuccess({
given: given.map((g) => toGrantView(g, g.grantee, now)),
received: received.map((g) => toGrantView(g, g.grantor, now)),
// v1.37.0 — the audit window, on the payload that carries the rows.
//
// The revoke dialog tells the owner how long "who entered what" stays
// answerable after the access ends, and it can only say that with the
// number the operator configured. It used to read that number off the
// activity feed, a second and independent request, so whether the sentence
// appeared depended on which of two reads finished first — and an owner
// who clicked quickly ended a manage grant having been told less than one
// who clicked slowly, with nothing on screen to say a sentence was
// missing. Riding it here removes the race by construction: a client
// holding a grant to revoke is holding the window it must disclose.
//
// Same resolver as `/api/account/activity` and as the purge job, so the
// two surfaces cannot state different numbers.
retentionDays: getAuditLogRetentionDays(),
});
});