fix(auth): answer the setup state for the record the payload describes

The account payload resolved the setup state for the actor while its module
map, its cycle flag and every count the checklist reads beside it answer for
the active record. A guardian looking at a child's dashboard therefore got the
child's rows ordered by their own answers, and the child's reading count
tested against their own flow state. The two halves of that component have to
agree about whose record they describe, so the field is record-scoped now,
like the module map next to it.

Under a grant narrowed to one section the answers come back empty, for the
reason the module map is masked: they name the domains the record tracks, and
a grant that opens readings does not open that. The shape of the flow still
comes through.

The three write routes stay actor-only and say why: a delegable declaration is
a reviewed entry in the sharing surface guard, and a read that is wider than
its write is the honest shape while that entry is still to come.
This commit is contained in:
Marc-André Bombeck committed 2026-09-10 12:06:02 +02:00
1 parent b10a3ef22e
commit f90307fd22
7 files changed
+162 -31

No files matched your search

+5 -4
View File
@@ -466,10 +466,11 @@ const accountPayload = z
// v1.39 (C1) — enumerated for the same reason `notificationPrefs` is: a
// client DECIDES on it. The getting-started checklist orders its rows from
// `needs` and stays on screen while the flow is unfinished, and a future
// native client renders the steps it knows. Always present, resolved for
// the CALLER's own record rather than the active one — the three write
// routes are actor-only, and publishing a setup state the caller cannot
// write would be a read a browser could act on and never complete.
// native client renders the steps it knows. Always present, and resolved
// for the RECORD the payload describes — the same scoping as `modules`,
// because the checklist reads it beside that record's counts. Under a
// SCOPED grant the answers come back empty, for the reason the module map
// is masked. The three write routes stay actor-only.
onboarding: onboardingStateResource,
})
.meta({
+1 -1
View File
@@ -129,7 +129,7 @@ export const onboardingStateResource = z
.meta({
id: "OnboardingState",
description:
"The needs-based setup flow for one record. Always present on the account payload — a record that never entered the flow reads as empty answers with nine `pending` steps, so a client branches on the step statuses rather than on the field's existence.",
"The needs-based setup flow for one record. Always present on the account payload — a record that never entered the flow reads as empty answers with nine `pending` steps, so a client branches on the step statuses rather than on the field's existence. On the account payload it describes the ACTIVE record, so a client under an account switch reads the same record its counts and module map describe; under a scoped grant the answers come back empty.",
});
const onboardingStateEnvelope = dataEnvelope(