Invite process — cloud accept gap (scope fork, decision pending)
Date: 2026-07-11
Status: Decision pending — the invite feature is NOT MVP-complete; the cloud
acceptance leg is architecturally broken. Captured here so it stays durable for the owner.
Source of truth: .superpowers/sdd/invite-process-deep-dive.md
(full end-to-end lifecycle map, gap register G1–G14, file:line index).
Related: role-governance ADR (invite design intent),
household-resolution ADR (Mom-incident cause).
Context
A read-only deep-dive traced the member / co-parent invite process across every layer (UI → bloc → repository → facade → service → adapters → RLS → Edge Functions → tests). The inviter-side domain model is well-built and well-tested (two invite shapes, strong token, expiry, resend, revoke, single-use accept, role/link guards). But it is only tested against in-memory / local storage, and the acceptance leg does not work in the production cloud topology.
Verdict
The invite feature is not fully implemented for MVP. The load-bearing break:
- Cloud accept path does not exist (G1, CRITICAL).
acceptInviteis a caller-household read-modify-write. Under RLS a not-yet-member invitee cannot see the household, read the invited row, or update it; and there is noaccept_inviteSECURITY DEFINER RPC / Edge Function anywhere in the migrations. Production runsDataMode.cloud, so a real cross-account co-parent/helper join is currently impossible — the invitee always sees "invalid code." Accept only works where there is no RLS (in-memory tests, single-device local Drift), which is exactly why CI is green. - Co-parent invite token never surfaced (G2, CRITICAL). The co-parent token is
generated and stored but discarded by the bloc and rendered nowhere; there is no
email/deep-link delivery (
send-inviteis a.gitkeepstub). The flagship "invite Mom" flow dead-ends — nobody can learn the code.
The role-governance ADR intends code-entry acceptance and defers email/deep-link — a coherent intent, but the cloud execution path for that intent was never built. The deferral note masks a functional break, not just a UX downgrade.
Additional gaps (from the register)
- G3 (HIGH) token not bound to invitee identity and SELECTable household-wide (leaked code ⇒ silent seat takeover).
- G4 (HIGH) invite state is just a member row with
on delete cascade— the Mom-incident class: duplicate-household cleanup silently destroyed a pending invite, no audit, no re-issue path. - G5 (HIGH) no accept surface for an account that already has a household (the code dialog lives only on Setup) — blocks the separated-co-parents scenario.
- G6/G7 (MEDIUM) no dedupe of pending invites for the same email; accept doesn't guard "already a member of this household."
- G9 (MEDIUM) legacy
{admin}invited rows not backfilled — accepting a stale one grants admin instantly. - G10 (MEDIUM) expiry not enforced server-side; no cleanup.
- G13 (positive) the COPPA boundary holds — children cannot be invited/linked through
this path (the
isAdultguard is the seam).
Prioritized recommendations (R1–R8, do NOT implement here)
- R1 — Build the cloud accept path (G1):
accept_invite(token)SECURITY DEFINER RPC (token lookup, expiry + unlinked + not-already-member checks, atomic link+activate+clear), SDK cloud routing, RLS-twin tests with two auth identities. Everything else is blocked on this. - R2 — Surface the co-parent invite code (G2): show the share-code view after
inviteCoParentand resend; stop discarding the returned member. - R3 — Harden the token (G3 + G10): enforce expiry + email-binding (or an explicit
no-bind decision) in the RPC; stop selecting
invite_tokeninto general roster reads (view/column grant or hash storage). - R4 — Invite hygiene (G6, G7, G9): pending-email dedupe, already-member accept guard,
legacy
{admin}invited-row backfill. - R5 — Mom-incident durability (G4): dependent-row report before any household delete +
an invite event trail; schedule a first-class
invitestable as a post-MVP structural slice. - R6 — Accept-surface + UX (G5, G11, G12): "Join with code" for onboarded accounts (or explicit descope), typed accept-error copy, revoke/expiry surface for the existing-member shape, multi-household join confirmation.
- R7 — Dual-gate symmetry (G8): acting-member gating on
inviteCoParent/resendInvite/revokeInvite; admin-scoped RLS for invite-column mutations; affected-row check inupdateEq. - R8 — Deferred (tracked, not MVP): email/deep-link delivery via the
send-inviteEdge Function +app_links; child-account invites behind COPPA hard gates.
Decision needed (owner)
- Is cross-device / cross-account invite acceptance in MVP scope? If yes, R1 is a launch blocker. If no, the invite feature must be explicitly descoped in the ledger (single-device / local-only), because the current UI implies a working invite that the cloud cannot honor.
- The root fragility (G4): should invites be promoted to a first-class
invitestable (decoupled from household lifecycle) now, or after MVP with the interim audit-trail mitigation?