Skip to main content

Personas & access model

Status: ✅ Authorization model shipped (Authorizer + Capability in client_sdk) · 🔨 Admin UI for role assignment partial. The capability sets below are transcribed from services/authorizer.dart — they are the shipped model, not a proposal.

The four personas

Every account holder is first a Member — the base layer. The other three personas are composable overlays: a member can hold any combination of them. For example a grandparent (MemberKind.otherAdult — a tracked non-guardian adult) is typically Member + Helper — a tracked person with their own profile/wallet/goals who also approves completed jobs (babysitting). A parent is Member + Admin (+ Owner if they handle billing). So "Helper", "Admin", and "Owner" describe what a member can additionally do (role / flag overlays), not a separate kind of user.

Member (base)

The universal persona: kids, parents, other adults, roommates, anyone in the household. MemberKind ∈ {parent, coParent, otherAdult, child} and MemberRole.member by default. Members own a personal wallet, buckets, goals, and budget (see Money & Envelopes). "Roommate" is a Member in the deferred peer/self-governed house mode (see Open questions).

MemberKind.otherAdult is a tracked adult who is not a legal guardian — a grandparent, babysitter, or tutor. It carries an adult identity (own profile/wallet/goals) but isParental is false, so the Authorizer's intrinsic parental-kind gate strips the consent / supervise / approve-spend / approve-goal / erase verbs from it. An otherAdult can hold the Helper role overlay (the typical babysitter/grandparent shape) but, by the MVP-1 rules that admin ⇒ isParental (code-backed: enforced at HouseholdService.setRole/setMemberRole via _requireRoleKind, which throws DomainRuleException on an admin grant to a non-parental kind) and owner ⇒ isParental, can never be an Admin or Owner — and, because a consent target must be a MemberKind.child, can never be a consent target. (Closes persona-lens H1.)

Watch-only is a boolean flag on a Member (HouseholdMember.watchOnly), not a MemberStatus value. A watch-only member is tracked (in the roster, has a profile) but is excluded from chore assignment, the active roster, and the earning surfaces, and has no login. It is a Member sub-state for someone represented in the household without participating in the token economy (e.g. a young child or an elder you are only tracking). It is a generic flag — not kid-only.

Helper

A trusted non-admin adult: babysitter, grandparent, tutor. MemberRole.helper — a role overlay decoupled from MemberKind (it layers on top of any kind, typically otherAdult). Narrowest management set by design: the helper overlay grants exactly approveCompletion + viewHouseholdAll (verified in authorizer.dart _helperCaps). A Helper can approve or deny completed chore submissions and see the household roster — no Admin access, no fund moves, no config, and never spend-request or goal approvals. approveCompletion is deliberately not a parental-only verb, so a non-parental otherAdult Helper keeps it. This is the explicit decision from the Admin architecture (see Admin).

Admin

A legal guardian or designated household manager. MemberKind.isParental (i.e. parent or coParent) AND MemberRole.admin (the admin ⇒ isParental rule is kept for MVP-1; decoupling non-parental admins is deferred — see Open questions). The admin overlay (_adminCaps) controls everything in the Admin area: catalog CRUD, member management, roles, economy config, feature flags, default buckets, house settings, plus the completion / spend-request / goal approvals and createFamilyGoal / completeGoal. Note that capturing / revoking COPPA consent is NOT an admin verb — it is owner-only (see Owner, and the consents_insert RLS).

Owner

The billing / ownership overlay: the owner: bool flag on a member — a distinct overlay, NOT an Admin superset. The owner flag grants exactly manageBilling, deleteHousehold, manageOwners, captureConsent, revokeConsent (verified in authorizer.dart _ownerCaps) and nothing else — a pure owner (owner without admin) cannot configure the household. owner ⇒ isParental is an invariant enforced upstream (schema trigger + HouseholdService). Multiple owners allowed (co-parents can both be owners). The realistic "account owner" persona is Member + Admin + Owner (the household creator is assigned owner + admin at setup); the Owner column below shows the overlay in isolation, not that composite.

Capability matrix (effective — transcribed from authorizer.dart)

Read each column as the effective capability of a person holding that overlay, except Owner, which is shown as the billing / ownership overlay in isolation (it is not an Admin superset — the headline O1 fix). = holds the verb, = does not. The realistic account owner is Member + Admin + Owner.

Capability (verb)MemberHelperAdminOwner
Do / claim chores, view own wallet (_baseSelfCaps)
Own wallet moves, goals & redemptions (_selfEconomyCaps) †
View whole household (viewHouseholdAll)
Approve completed jobs (approveCompletion)
Approve spend requests (approveSpendRequest)
Approve goal requests (approveGoalRequest)
Move any member's funds (manageBudget)
Configure household (catalog/economy/flags/rooms/members/roles)
Capture / revoke COPPA consent (captureConsent/revokeConsent)
Billing (manageBilling)
Delete household / manage owners (deleteHousehold/manageOwners)

Self-economy is adult/consent-gated. _selfEconomyCaps (createOwnGoal, editOwnGoal, archiveGoal, requestGoal, redeemReward, moveOwnFunds, manageOwnEnvelopes) is held by every adult kind (parent/coParent/otherAdult) unconditionally and by a child only when ConsentState.granted (the COPPA gate). As shipped the gate is isAdult || consent == granted (CB-14), so a default otherAdult (non-parental but still an adult, consentState = none) holds self-economy unconditionally — the consent-conditional applies only to a child. The Helper cell therefore resolves to for the typical otherAdult Helper and is consent-gated only when the underlying member is a child. A non-active member (shadow/invited) and a lapsed time-boxed access window both resolve to no capabilities at all.

What each overlay adds

The Authorizer unions these grants (owner flag + each role + the base self-row), then applies an intrinsic parental-kind gate that strips {captureConsent, revokeConsent, eraseChildData, superviseAction, approveSpendRequest, approveGoalRequest} for any non-parental kind — even when a role would otherwise grant them. approveCompletion is not in that stripped set.

  • Member (base, every active member): submitChore, viewOwnWallet, viewSharedFamilyGoals, editOwnPreferences, adjustOwnRunEstimate; editOwnProfile for adult kinds (parent/ coParent/otherAdult); the adult/consent-gated _selfEconomyCaps (see †).
  • Helper overlay (MemberRole.helper): adds approveCompletion + viewHouseholdAll. Nothing else, ever.
  • Admin overlay (MemberRole.admin, MVP-1 requires isParental): adds removeMember, manageRoles, manageHousehold, manageCatalog, manageRoutines, manageBudget, inviteMember, manageMemberProfile, manageNotificationPrefs, printList, eraseChildData, approveCompletion, approveSpendRequest, approveGoalRequest, superviseAction, viewHouseholdAll, createFamilyGoal, completeGoal.
  • Owner overlay (owner flag): adds manageBilling, deleteHousehold, manageOwners, captureConsent, revokeConsent. Not an Admin superset.

Contested-stance pointer. This matrix reflects the shipped (conservative as-shipped) code. Four HIGH-STAKES ratifications — Owner-vs-Admin as-shipped, intra-household RLS visibility, guardian-vs-admin decoupling, and earn auto-split vs an "Unallocated" bucket — are tracked in the consolidated backlog (docs/superpowers/plans/2026-06-28-p1p2-consolidated-backlog.md, HS-1..HS-4) and remain awaiting user sign-off. The docs describe the current code, not a final ruling on those four.

Active-member lens ≠ actor

The Profile-page active-member switcher lets a capable viewer proxy into another household member's view (companion, avatar, catalogs, My Cosmos). This is a display lens only (SelectedMemberRepository): the authenticated member (CurrentMemberRepository.current) remains the sole actor for every gated call and for RLS — proxying never elevates authorization. While proxied (active member ≠ authenticated self), member-owned surfaces are read-only (the SDK's self-only companion mutation enforces it). Full "act-as" IAM is a future phase, out of scope. See Authorization ("Viewing as" is not authorization) for the invariant-lock detail.

MemberStatus overlay

MemberStatus ∈ {shadow, invited, active} is a lifecycle state, not a permission tier. A shadow member is a placeholder created by the admin (no login credentials yet). invited = credentials issued, acceptance pending. active = fully onboarded.

Roles are per-household membership

Roles (MemberRole) and the owner flag are per-HouseholdMember membership, not global to an account. The same person can be admin in household A and a plain member in household B. One subscription is per account (an account may manage N homes); ownership/billing scope and the zero-admin / zero-owner guards are evaluated per household. See Roles & permissions and Household (multi-household model).

Open questions

  • Peer/self-governed mode. Roommate households with no single admin are a deferred research direction. The multi-house member model (member ↔ many households) is forward-compatible, but the permission rules for a "peer" house need design.
  • Non-parental admin (teen manager). Whether admin can be granted without the isParental gate is deferred — it is bound up with self-governed mode and COPPA guardian semantics (consolidated backlog HS-3 / DF-6). MVP-1 keeps admin ⇒ isParental.
  • Helper invite flow. How a standalone (non-member, time-boxed) babysitter Helper joins — full account creation vs a time-limited PIN/link reusing the MemberAccess expiry window — is deferred (backlog DF-1). Does a helper ever graduate to member?

Resolved: the zero-admin / zero-owner self-demotion guards are now enforced (per household, in the service AND a schema trigger) alongside a transfer-ownership flow — no longer open questions.

Relationship

  • Admin: the Admin area is the entire config surface for the Admin/Owner personas.
  • Money & Envelopes: Member-scoped wallets, buckets, and goals; move-funds permissions.
  • Roles & permissions: the SDK model, Admin UI for assignment, invite flows.