MVP-1 Sub-project ① — Personas & Authorization: build spec
Status: approved design, ready for planning · Date: 2026-06-25
Design source of truth (read first): the rendered architecture page
app/test-gallery/authored/developer/architecture/authorization.md
(live at developer.eldr-labs.duckdns.org/developer/architecture/authorization).
Requirements: personas-authz
(authoritative) + the decisions log
(D-PERSONA-1…4, D-ACCOUNT-1/2, D-COPPA-1, D-CONSENT-1…5).
Review gates: flutter-reviewer (Dart), database-reviewer (PostgREST/RLS/migration),
security-reviewer (zero-trust / secrets / consent gate).
Goal
Build the access + COPPA-consent substrate everything else depends on: a
status-aware owner/role/kind capability matrix enforced in the SDK service
and Postgres RLS (the dual gate), an append-only consent substrate (adult
ToS + child VPC) with the child-PII write gate, the supervised-action path
over the already-built approval policy, and the authz observability events.
Full vertical: SDK + schema + RLS + app-UI. This is sub-project ① of two; ②
(Money Envelopes) consumes the ②-tagged capabilities + the consentState signal.
Architecture summary
The authorization.md page is the canonical design. In brief: an authenticated
adult principal acts as a household member (kind/roles/owner/status/
consentState); the pure Authorizer resolves capabilitiesFor(member) —
union over kind + roles + owner, then filtered by the status + expiry + parental-
kind gate; every service mutation opens with authorizer.require(cap, target);
RLS mirrors each capability via SECURITY DEFINER helpers. Consent gates whether a
child member's data may be persisted at all (service and schema, online and
offline). Supervised action reuses the built single token-moving path.
Global Constraints (every task inherits these)
client_sdkstays pure Dart —package:supabasecore only; noflutter_*dep in the SDK. App-UI work lives inapp/.- No service-role key — the app uses the anon/publishable key
(
sb_publishable_…) only; RLS-enforced. Any privileged op goes through an Edge Function, never a service key in the client. Reuse the existingSupabaseClientbuilt in SP2. - Dual gate is mandatory — every capability and the consent gate are enforced in the SDK service AND in schema/RLS. A task that adds a service guard adds the mirrored RLS policy/trigger in the same task; RLS-parity test required.
- Household-scoped — zero cross-household access; helpers keyed to
auth.uid(). - No child PII in analytics/authz events — opaque household-scoped IDs only; events tagged internal-BI; pass the pre-emit scrub.
- Append-only consents —
consentsrows are immutable;Member.consentStateis a projection; the consent event survives child-PII erasure (opaque ref). - Offline parity — the consent gate holds against the local Drift store, not only the cloud.
- Tests: async-throw uses
await expectLater(..., throwsA(...))(notexpect(() => future, …)); TRUE exit codes (cmd > /tmp/t.txt 2>&1; echo EXIT=$?), never pipe test runs totail; runbuild_runnerscoped frompackages/client_sdkand restore any clobbered.g.dartsiblings; suite count ≥ baseline; verify every new column against liveinformation_schemavia the Supabase MCP (projectbgedvvmihygwxhjxlvfu). - Anti-corruption git guardrails — no destructive git, explicit
git add(never-Ablindly), no*_test.dartdeletions. - Verify built-vs-net-new with graphify first before touching code.
Net-new surface (from the built-vs-net-new gap map)
SDK (create): models/capability.dart (Capability enum), services/authorizer.dart,
models/consent_state.dart, models/consent_method.dart, models/consent_record.dart,
models/terms_acceptance.dart, models/member_access.dart, services/consent_service.dart.
SDK (modify): models/household_member.dart (owner, consentState, consentRef;
status already present), services/household_service.dart (owner/role mutations,
transfer, zero-last guards, removeMember, bootstrap), services/approval_service.dart
(actingMemberId supervised extension; manual/auto already built), models/exceptions.dart
(new domain exceptions), the local + cloud adapters for the new columns/tables.
Schema (new migrations): household_members add owner + consent_state +
consent_ref (FK); consents table; terms_acceptances table; member_access
(expiry); admin_household_ids()/helper_household_ids()/owner_household_ids()
helpers; RLS policies per surface; guard triggers (zero-last-owner/admin, child-PII
gate, status/expiry).
App-UI (create): repositories + blocs + pages for role/owner management, consent
lifecycle (+ adult ToS gate), supervised-action UX, and capability-aware auto_route
guards.
Already built (leverage, do not rebuild): MemberKind/isParental,
MemberRole, MemberStatus, the ledger + zero-floor, ApprovalPolicy/
autoApproveCompletion/resolvedBy.
Phase decomposition (input to writing-plans)
Each phase ends with an independently testable deliverable; tests-first (TDD).
Capability+Authorizer(pure). The capability enum + matrix + the status/expiry/parental-kind gate. Produces:Authorizer.capabilitiesFor/can/require. Tests:authorizer_test.dart— full matrix incl. helper- cannot-approve-spend/goal, non-active→ empty, expiredMemberAccess→ empty. No I/O.- Schema + RLS. Migrations: columns +
consent_refFK +consents+terms_acceptances+member_access+ the three*_household_ids()helpers + RLS policies + guard triggers. Tests: RLS-parity (cloud, guarded creds flag, extends the SP3 smoke);information_schemafield verification via Supabase MCP. - Service guards +
HouseholdService.require(...)across mutations; owner/ role mutations;transferOwnership; zero-last-owner/admin guards;removeMember; thecreateHouseholdbootstrap grant. New exceptions. Tests: guard unit tests- zero-last + RLS parity.
ConsentService. Child-PII write gate (online service + schema; offline Drift gate + revocation tombstone); adultTermsAcceptance+termsAcceptanceRequired; lifecycle (checkConsentCurrency→ re-consent; deadline mechanism — numbers ⚖️; post-erasure retention; shadow-member ToS ordering). Tests: consent gate (granted/none/revoked), offline gate, ToS session block, currency transition.- Supervised-action. Extend
ApprovalServicewithactingMemberId≠principalAccountId; reuse the built manual/auto single-token path; route earn by consent state. Tests: supervised over manual and auto chores;superviseActionrequired. - Authz observability. SDK service emits
authz_decision,authz_escalation_blocked,consent_state_changed,child_pii_write_rejected,supervised_action_committed— opaque IDs, internal-BI, pre-emit scrubbed. Tests: payload-scrub (no child identifier) + bucket tag. - App-UI surfaces. Repositories + blocs + pages: role/owner management; consent
lifecycle + adult ToS gate; supervised-action UX; capability-aware route guards +
badges (
authorizer.can(...), not rawisParental). Tests: flow tests per surface (mock repositories). - Integration / RLS-parity + docs. Full cross-layer + RLS-parity sweep; flip
authorization.mdproposed → built;graphify update .. Tests: suite ≥ baseline, green analyze.
Testing strategy
Flow tests (mock repositories) for UI; integration tests (in-memory adapter, real services) for guards + consent gate online and offline; RLS-parity tests proving the schema half denies identically (the dual gate); explicit coverage for zero-last-owner/admin, helper-cannot-approve-spend/goal, status/expiry denial, and supervised over manual + auto. Suite count ≥ baseline.
Out of scope (this sub-project)
- ② Money Envelopes — envelope generalization,
moveFunds, Unallocated, goal balances,EarningsSplitwiring, the supervised-attribution household envelope. (① defines the ②-tagged capabilities + theconsentStatesignal; ② builds the behaviors.) - ⚖️ counsel-confirm numbers — the 14-day/85% re-consent SLA and the VPC method details (the mechanism is in ①; the numbers/method land via counsel).
- Self-governed mode — non-parental admin, co-parent delegated scopes, multi-household session switching.
- TV/kiosk auth wiring (D-PERSONA-4) — the capability scope is defined; the device auth wiring is deferred.
Review routing
flutter-reviewer for Dart (SDK + app); database-reviewer for the migrations / RLS / helpers / triggers; security-reviewer for the consent gate, dual-gate completeness, no-service-role-key, and no-child-PII-in-events. Each phase routes to the relevant reviewer(s) before the next begins.