Skip to main content

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). acceptInvite is 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 no accept_invite SECURITY DEFINER RPC / Edge Function anywhere in the migrations. Production runs DataMode.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-invite is a .gitkeep stub). 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 isAdult guard is the seam).

Prioritized recommendations (R1–R8, do NOT implement here)

  1. 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.
  2. R2 — Surface the co-parent invite code (G2): show the share-code view after inviteCoParent and resend; stop discarding the returned member.
  3. R3 — Harden the token (G3 + G10): enforce expiry + email-binding (or an explicit no-bind decision) in the RPC; stop selecting invite_token into general roster reads (view/column grant or hash storage).
  4. R4 — Invite hygiene (G6, G7, G9): pending-email dedupe, already-member accept guard, legacy {admin} invited-row backfill.
  5. R5 — Mom-incident durability (G4): dependent-row report before any household delete + an invite event trail; schedule a first-class invites table as a post-MVP structural slice.
  6. 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.
  7. R7 — Dual-gate symmetry (G8): acting-member gating on inviteCoParent/resendInvite/revokeInvite; admin-scoped RLS for invite-column mutations; affected-row check in updateEq.
  8. R8 — Deferred (tracked, not MVP): email/deep-link delivery via the send-invite Edge 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 invites table (decoupled from household lifecycle) now, or after MVP with the interim audit-trail mitigation?