Skip to Content
Living documentation — last reviewed 2026-05-28
FeaturesManaged MembersManaged Members — Execution

Managed Members — Execution

Ticket: TKN-382  Work packages: TKN-396 … TKN-414, sub-issues of TKN-382. Status: phase 1 shipped WP-00 … WP-14 and WP-10b (PR #526, taikan-mobile #128). WP-15 … WP-19 are deferred. The waves below are the plan as executed; they are kept as history.

Linear is the source of truth for work-package scope and acceptance. Each ticket carries its own full brief. This document holds only what is true across all of them: the wave order, the concurrency rules, and the constraints every package obeys. It deliberately does not restate the tickets — two copies of a work package drift within a week.


Ground rules for every package

Repeated in each ticket because these are the things that get dropped first.

  1. Do not run pnpm build, pnpm lint, or any test suite mid-flight. Run verification once, at the end of your package.
  2. Never run pnpm db:migrate. Generate migrations, review the SQL, hand them over. Execution needs explicit owner approval. Never db:push.
  3. Every user-facing string goes in libs/shared/src/lib/i18n/dictionaries/{en,he,ru}.json before it is referenced. No hardcoded English, including in error messages you author. en.json is the typed canonical; he/ru are deep-merged over it, so a missing Hebrew key ships as visible English rather than failing CI — it is a shipped defect, not a caught one.
  4. Tests use data-testid only. No getByText, no queryByText, no CSS or structural selectors. Every web test goes through a co-located *.driver.tsx; specs never import screen. vi.mock() lives in spec files, never drivers.
  5. Behavioural changes sit behind the PostHog flag managed-members, following apps/api/src/payments/services/webhook-enforcement.service.ts:30,51-55 exactly: a static FLAG_KEY, distinctId = orgId, groups: { organization: orgId }, and only === true proceeds. isFeatureEnabled returns undefined when it cannot decide; treat that as off.
  6. No console.log / console.error / console.warn in committed code.
  7. Do not create git commits. Stage with git add and report that the work is ready for review.
  8. Follow existing conventions rather than establishing a second one. Controllers: @ApiTags + @ApiBearerAuth, @Controller('organizations/:orgId/...'), param order (params…, @CurrentUser('userId') clerkId, @Body dto), always return { data: … }. DTOs: class-validator at the HTTP boundary plus a Zod schema re-validated in the service. Copy apps/api/src/check-ins/check-ins.controller.ts and dto/assign-check-in.dto.ts.
  9. Do not reach into a sibling service’s private properties. apps/api/src/bookings/bookings.controller.ts:82 does this.bookingsService['membershipsService'] — a bug to avoid, not a pattern to copy. Inject properly.
  10. Read data-model.md before writing schema and behavior.md before writing an endpoint. data-model.md §2 records why the participants design was rejected; do not re-propose it.

Waves

WavePackagesConcurrency
0 — Decisions—Complete. All phase-0 blockers resolved in review; no counsel dependency remains for phase 0.
1 — SchemaTKN-396 (WP-01)Serial.
2 — The gateTKN-397 (WP-02)Runs alone. Nothing else may touch the API while the null sweep is open.
3 — AuthorizationTKN-398 (WP-03) → TKN-399 (WP-04)Serial; WP-04 depends on the resolver.
4 — DomainTKN-400 (WP-05), TKN-401 (WP-06), TKN-402 (WP-07), TKN-403 (WP-08), TKN-404 (WP-09), TKN-405 (WP-10), TKN-415 (WP-10b), TKN-406 (WP-11)Eight concurrent. WP-10 and WP-10b depend only on WP-01/WP-02; WP-11 depends on WP-05.
5 — WebTKN-407 (WP-12), TKN-408 (WP-13), TKN-409 (WP-14), TKN-410 (WP-15), TKN-411 (WP-16)Five concurrent. WP-13 needs WP-07 + WP-08; WP-14 needs WP-06 + WP-12; WP-15 needs WP-04 + WP-05.
6 — HardeningTKN-412 (WP-17) → TKN-413 (WP-18) → TKN-414 (WP-19)Serial. WP-17 is a convergence point by construction.

Two concurrency rules that are not negotiable

TKN-397 runs alone. It works a compiler error list across the whole API. A concurrent package adding code will add errors to that list, and the two agents will fight over the same files.

TKN-412 (i18n) has exactly one owner. Five web packages all add keys to en.json. Converging them into one package is the only way three agents do not overwrite each other’s dictionary edits.


The gate

Nothing past wave 2 starts until TKN-397 satisfies both:

  1. npx nx build api clean, with zero null-assertion escapes added. Not “the errors are gone” — gone without ! or ?? ''.
  2. adult-migration-parity.int.spec.ts passing: a fully-populated adult member’s every member-facing API response is byte-identical before and after the migration. Not written in phase 1: the gate was held by item 1 plus every existing suite passing on the migrated schema (data-model.md §5).

That is acceptance criterion A9, and it is the only thing standing between this feature and a silent regression for every existing paying member.


Sub-agent dispatch

The Linear ticket body is the task brief. Dispatch shape:

context: # Goal TKN-382 managed members, Taikan monorepo at /Users/saarku/dev/taikan. Read docs/features/managed-members/{data-model,behavior,design-spec}.md first. data-model.md is normative for schema; behavior.md for behaviour; design-spec.md for UI. # Constraints <paste "Ground rules for every package" from execution.md verbatim> # Contract <the exported interfaces this package provides or consumes — e.g. GuardianCapabilities from libs/shared, GuardianAccessService.can()'s signature> task: <the Linear ticket body verbatim, plus: report every path:line you changed>

Two dispatch rules. One integration owner per file — if two packages would touch the same file they belong in different waves or the same package; there is no third option. And siblings coordinate through the parent via write agent://<id>, never by editing each other’s files.


Deferred, with the trigger that would revive each

Recorded so they are not silently forgotten or silently re-added.

DeferredTrigger
Messaging for managed membersA phase-1 decision plus counsel on the safeguarding escalation path. Guardian-visible is the right default; the carve-out is that guardian visibility is wrong when the disclosure concerns the guardian.
Age tiering and the claim flowExplicit product decision. No automatic age-triggered behaviour ships; selfAccessMinAge defaults to off.
Split billing across two householdsMaterial volume of separately-paying divorced parents at the pilot. Shape is known — DanceStudio-Pro’s percentage-split autopay — and the unsolved part is partial-failure dunning against two payers.
Consolidated family invoiceOperator demand. Would make a household a real aggregate with real invariants, which is when a households table earns its place.
Pooled family quota across siblingsA family plan product. Boostapp’s pooled-versus-per-head switch is the reference.
Structured medical facts and a printable roster summaryIts own ticket. canViewHealth already exists so the permission is ready when the data is.
attachments.organizationIdPre-existing scoping weakness, not introduced here. Needs an org backfill across four polymorphic owner types.