Skip to main content

L4 — Code (key aggregates)

L4 covers the invariant-bearing aggregates (where the type relationships are the design) plus the household structure and the chore shape. As of the 2026-06-23 domain-completion + P1/P2 work, the SDK domain model now carries the full POC + North Star shape — the assignment, per-room, per-member-step, incentive, and goal-lifecycle fields that were previously gaps are built (solid nodes below). Dashed nodes are the few things still genuinely unbuilt at the domain level: the recommend() AI service (the models exist; the facade returns an empty result until the AI sub-project), a ChoreSubmission photo / media field, and Kudos (no model yet). See the domain-coverage audit. UI wiring of these built fields is complete: the role-filtered tab shell (MainShellRoute — adults Home/Earn/Rewards/Household/Profile, children Home/Earn/Rewards/Profile), the Catalog page (chore list + FAB + Recommend action), the full-page chore editor (ChoreEditorRoute — all assignment / steps / tempBonus / multiPerDay / estimateMin fields), the Members editor under Household, and the Today/Home page wired to the real SDK (assignment filter, by-room grouping, per-member resolved steps). The GoalsRepository + Goals/MemberProfile UI (T7) remain planned — see below.

The reward/activity catalog entities also carry a restored description: String? (CB-01), and the authorization model is shipped: Approval.resolvedBy is {parentManual, helperManual, autoPolicy} (helper completion-approval, CB-02) and MemberKind is {parent, coParent, otherAdult, child} (a tracked non-guardian adult, CB-03) — see Personas.

Economy aggregate

Balances are projected from an append-only ledger, never mutated in place. The zero-floor invariant is enforced in the service and a SQL trigger.

Chore-flow aggregate

A completion is a request for approval, not a direct balance change — the parent's nod is what triggers the economy credit.

Why / changed from the POC: the POC mutated wallet balances directly, which made history and corrections fragile. The rebuild makes LedgerEntry append-only and projects balances from it, with invariants enforced in the service AND the schema. Completions route through Approval so nothing credits tokens without a decision — but the decision can be pre-made: an ApprovalPolicy.auto chore resolves on submission (stamped autoPolicy, attributed to the parent who configured it), skipping the pending queue while still flowing through the same single token path. See the POC domain gap analysis.

Household & structure

Members unify parents + kids (no legacy Kid); rooms are Places; goals are fundable ledger envelopes — progress is a fold over goal_id-stamped ledger legs, not a derivation from the save bucket. The home-room, watch-only, and goal-lifecycle attributes are now built in the SDK domain.

Why / changed from the POC: a member's home-room (homePlaceId, FK → places, ON DELETE SET NULL) and watch-only flag (excluded from assignment + active-roster surfaces) are now carried over from the POC. A goal's request→approve lifecycle is built: GoalStatus { active, requested, complete, archived } (migrated from the old is_active boolean, with isActive retained as a getter), plus imageUrl / dueLabel. EconomyService exposes the requestGoal → approveGoal → completeGoal → archiveGoal transitions. The MembersBloc + Household tab (members list / editor with name, emoji, homePlaceId, watch-only) are built, as are the Goals UI, MemberProfile page, and GoalsRepository (T7 Household restructure + G-1 fund/withdraw).

Goal envelopes (G-1): a goal's balance is an envelope on the append-only ledger — a nullable goal_id ownership dimension on ledger_entries, folded the same way a member Wallet is (never stored). member_id on an envelope leg is the contributor. LedgerService.fundGoal / withdrawFromGoal are the only verbs (withdraw is the undo); completeGoal / archiveGoal release the envelope back to each contributor's general bucket. See the goal-envelopes ADR and the domain-coverage audit.

Chore — assignment, steps & incentives (built)

The assignment / step / incentive shape the POC had and the North Star implies is now built on Chore (solid). The one dashed node is the AI recommender service — the models exist but recommend() returns an empty result until the AI sub-project.

Why / changed from the POC: the Chore aggregate now covers expectations + bounties with shared subtasks, a single place, and bounty claim plus the full multi-child shape: assignedMemberIds (empty = all eligible by minAge), roomAssignees (per-room placeId → [memberId], with assignedMemberIds as the union and a room-owner resolution helper), stepsPerMember (per-member step overrides resolved via stepsForMember(), closing the POC's per-kid steps), temp bonuses (tempBonusTokens / tempBonusUntileffectiveTokens, with a service-side expiry sweep; the explicit invariant-1 amendment lets a temp bonus pay on an expectation), multiPerDay / maxPerDay, and estimateMin. These gate the multi-child Today experience and are fully wired across the UI: Catalog page, full-page chore editor, Today/Home (assignment filter via assignedMemberIdsUnion + minAge, by-room grouping via membersForRoom, per-member resolved steps via stepsForMember pre-resolved in bloc state). The daily tempBonus expiry sweep (sweepExpiredTempBonuses) fires on app resume/init via WidgetsBindingObserver, once-per-day gated by SharedPrefs (app.last_sweep_date). The only domain-level gap left here is the AI recommender service (recommend() stub — the Catalog Recommend action is wired but returns an empty result). Full classification in the domain-coverage audit.