Skip to Content
Living documentation — last reviewed 2026-05-28
FeaturesManaged MembersManaged Members — Children and Dependent Accounts

Managed Members — Children and Dependent Accounts

Ticket: TKN-382  · Status: phase 1 implemented (PR #526 API/web, taikan-mobile PR #128), behind the PostHog flag managed-members, Pro tier and above. One migration: libs/db/drizzle/0168_managed_members.sql. Work packages: TKN-396 … TKN-414 , 19 sub-issues of TKN-382 Review page: TKN-382 Plan Review  (Notion, block-level comments) Origin: Discovery and Product Brief, 2026-09-30  — עולם המים, an 8–12-site Israeli swim network, ~150 staff, currently on Arbox + Fizikal + a clinic-derived system.

What this is

A model in which a responsible adult (אחראי) manages a member who has no email and no login. It separates five roles that Taikan currently collapses into one row.

RoleWhoWhere it lives
Managed memberthe person who trains, with no loginusers.kind = 'managed'
Account holderthe person who holds the loginusers + Clerk
Guardian / אחראיan adult authorised to act for someone they are notguardianships
Payerthe party financially liablesubscriptions.payerUserId
Actorthe human who performed a given actionbookings.bookedByUserId, audit_events.actorCapacity

Revision 2: no new person entity

Revision 1 proposed a participants table with a participant_identity view to coalesce it against users. That was rejected in review, and the objection was right.

users is already a person table. clerkId is already nullable; login-less “shell users” already exist, carry memberships, form instances and legal consents, and have their own scrub lifecycle (apps/api/src/memberships/memberships.service.ts:1587-1601); and users holds name, birthDate, gender, phone, emergency contact and national ID. The only thing making it an “account” table is email NOT NULL UNIQUE.

So a managed member is a users row with no email, no Clerk identity, and kind = 'managed'. The tell that revision 1 was wrong: it needed a database view whose only purpose was to paper over having two person tables.

What that collapses:

Revision 1Revision 2
memberships.participantId added, userId nullable, unique constraint swappedNo change beyond one additive participation column
member_profiles re-keyedNo change
form_instances.assigneeUserId relaxed and backfilledNo change; only signer columns added
Member search repointed off users.nameEmbeddingNo change
A coalescing view plus a single write path to stop two tables driftingDeleted
11 migrations6 migrations (later squashed into one, 0168_managed_members)

Cross-branch identity survives for free: one child is one users row with one membership per branch, exactly as an adult is today. data-model.md §2 records the full reasoning so this is not re-proposed in six months.

Documents

DocumentNormative forRead it when
data-model.mdSchema. The decision and why revision 1 was wrong, guardianships, the migration, the one real risk.Before touching the database or writing a query.
behavior.mdBehaviour. Vocabulary, 13 capabilities, establishment, coverage, product flows, the feature flag, edge cases, acceptance criteria.Before implementing any endpoint.
design-spec.mdUI. Acting context and the five surfaces.Before building any screen.
code-map.mdWhere every part lives: API, schema, shared, web, mobile, tests.To find the code.
qa-plan.mdScenarios to verify, and which were verified for phase 1.Before a release or a pilot.
rollout-runbook.mdMigration, deploy, turning the flag on per gym, and turning it off.Before production.
research.mdCompetitor analysis, 15-pattern catalogue, compliance constraint map.To understand why a decision was made, or before overriding one.
risks.mdRisks, blindspots, resolved and open questions.At every wave boundary.
execution.mdHow the Linear work packages map to waves, and the rules every package obeys. Historical plan.To start work or hand a slice to an agent.

Phase 1: what shipped, what didn’t

Shipped (WP-00 … WP-14, WP-10b):

  • Managed members as users rows (kind = 'managed', no email, no login), guardianships with 13 capabilities, memberships.participation (trains / guardian_only).
  • Staff-only enrolment. Staff add a child from Members → Add child or from an adult’s record; the adult confirms the link in the app (typed name, attestation; 21+ checked from their birth date, asked for on the form if missing). Parents cannot create children.
  • View as child on web and mobile (switcher, delegation not impersonation), booking and cancelling for a child with attribution, guardian form signing, youth parental consent, child plans paid by the adult.
  • Notifications about a child route to their confirmed adults; the child is never contacted.
  • Staff surfaces: the household section on member records, the Responsible adults filter, Message {guardian} about {child}.
  • Safety guards: no progress photos, no promotional sends, no minisite exposure, analytics and lead matching skip children; the teen sign-up block.
  • CSV import creates children with pending links.

Not built yet: a dashboard screen for a second adult, capability edits, revocation and suppression (the API exists, WP-15); the org policy settings in behavior.md §6 (WP-16); a teen claiming a login (§5.2); an import matching-queue screen; Arbox direct sync for children; WP-17 … WP-19.

How each scenario works end to end, from the staff, parent and child side: behavior.md §3.5.

Reviewed decisions

  • No new person entity. A managed member is a users row with kind = 'managed'.
  • Guardianship is global by default, with an optional org-scoped grant. It is not a membership — a membership relates a person to an organization, a guardianship relates a person to another person — but authority from both must resolve through one service.
  • Per-child subscription on one payment method. Split billing across households and consolidated invoicing are out of phase 0.
  • Minimum guardian age 21. Stricter than the market, and cheap.
  • No automatic age-triggered access changes. No majority cron. selfAccessMinAge is an org setting defaulting to off; claiming a login is guardian-initiated with guardian veto.
  • No messaging for managed members in phase 0. This removes the legal blocker from phase 0 entirely.
  • Progress photos hard-blocked for managed members: no toggle, no consent path, no override.
  • Hebrew term: אחראי. אפוטרופוס is retained only in legal documents and the staff record.
  • Tier: Pro.

Where the risk is

One package. Making users.email nullable breaks every path that assumes a member has an address — and that is the mitigation, not the danger: the regenerated Drizzle type makes npx nx build api enumerate every unsafe read as a compile error. TKN-397 is gated on a clean type-check plus a parity proof that every existing adult member’s data reads back byte-identical.

Two rules while working that list: never silence a null with ! or ?? '' (an empty-string email is a silent delivery failure, or a cross-member leak if used as a matching key), and a managed member with no email is a legitimate terminal state for a send rather than an error.

In scope because it is already broken

MembershipsService.getMemberDetail (apps/api/src/memberships/memberships.service.ts:1181-1205) gates only on org membership, so any plain member can read any other member’s full PII — masked national ID, emergency contact, coaching signals — by changing a membership ID in the URL. This ticket’s own acceptance criterion A4 is already false for adults. TKN-399.

Two things worth knowing before the next customer conversation

We are behind, not ahead. Boostapp already ships a first-class לקוח קטין requiring neither email nor phone, with multiple children per parent, guardian signing and guardian payment. And our roster importer silently discards every emailless row (apps/api/src/import/import.service.ts:629-630), so the pilot customer cannot migrate to Taikan at all today. TKN-406 is the business unlock.

The differentiators are specific. No Israeli incumbent supports multiple guardians with different rights; Boostapp fuses guardian and payer into a single לקוח משלם; none has an age-derived minor status or an 18-year transition; none records which guardian signed a waiver. And the pilot customer’s own children’s waiver is written in the first person of an adult participant — «הלקוח מצהיר כי מצבו הבריאותי מאפשר השתתפות» — with no guardian clause and photo consent handled by verbally asking the photographer. That artefact is the most persuasive single thing this feature fixes.

TicketRelationship
TKN-383 Level and age eligibility. Consumes users.birthDate, which this ticket guarantees non-null for managed members.
TKN-385 Make-up booking. Depends on entitlement attaching to the child. The pilot customer’s two plan tiers differ only by make-up entitlement.
TKN-388 Escort authorisation. Seam: guardianships.canAuthorizeEscorts. No product in the category has built this — an unoccupied position, not catch-up work.
TKN-313 Staff scopes. Picks up the finding that police clearance and minors-instructor qualification are bookable constraints, not HR attributes.
TKN-384  · TKN-390 AI surfaces. Constrained by this ticket: exclude managed members from AI training and inference-heavy features until a privacy impact assessment exists.
TKN-386 Network topology. One child at two branches is one users row with two memberships, which keeps this open.