ADR-0028: Children run shared lists and meals
Metadata
- Status: Accepted
- Date: 2026-08-14
- Deciders: Eagraí Clainne Team (design interview, 2026-08-14)
- Related: ADR-0014 (job ownership — the ownership model that lists and meals deliberately do not share); ADR-0015 (item lists); ADR-0002 (centralised RPC authorization — the role gate these verbs use)
Context
The permission matrix treated the container operations of ItemListService
(create, rename, delete, uncheck-all) and MealService (create, edit,
delete, push-ingredients) as manager verbs, open to ADMIN and MEMBER only.
A child could read lists and the meal library, and add items into a list
through ItemService.Create, but could not create a list or a meal.
Two problems surfaced together.
First, the user interface hid the gap badly. The web gated every create
affordance on one coarse flag, canEdit = ADMIN || MEMBER, so a child saw
no create controls at all — even for events and jobs, which the matrix
already permits. That defect is tracked and fixed separately; it is what
made the missing list and meal rights visible.
Second, and the subject of this ADR: should a child be able to create and
manage lists and meals at all? The obvious analogy is ADR-0014, where a
child is a "taker" scoped to jobs they own. But that analogy does not hold.
Jobs carry an owner. Lists and meals do not — ItemList and Meal are flat
records with no owner or creator field. There is nothing to scope a child
to.
Decision
Admit CHILD to the full verb set of both ownerless container services, the same rights a MEMBER holds:
ItemListService: Create, Read, Update, Delete, List, UncheckAll.MealService: Create, Read, Update, Delete, List, PushIngredients.
A child is a trusted member of the household for the family's shared lists and meal library. Because these records have no owner, no service-layer guard is added or needed — enforcement stays entirely at the interceptor's role gate, as before.
The boundary is drawn at two nearby surfaces that are deliberately left unchanged:
- Rewards stay member-and-up. A child claims rewards, but does not create or grant them (ADR-0003). Granting points is a parental act.
- The meal rota board (
MealRotaServiceslot, zone and override verbs) stays member-and-up. Editing the standing who-cooks board is a scheduling act; a child still reads the resolved board and the raw board.
GUEST remains excluded from every verb on both services.
Consequences
- A child can create a shopping list, rename it, clear it, and delete it; and can add, edit and remove meals in the library and push a meal's ingredients onto a list. This matches how a family actually shares these surfaces.
- No schema change.
ItemListandMealstay ownerless, so backup, export and the JSONB query paths are untouched. - A child could delete a list or meal another member relies on. This is the accepted cost of a shared, ownerless surface — the same exposure a member already carries. The mutation trail and audit log record who did it.
- The permission-matrix completeness test still passes; only the allowed roles on existing entries changed, no entries were added or removed.
- If the family later wants per-child ownership of these containers, that is a separate, larger change: an owner field on both entities, a Create that sets it, an ownership guard, and a rule for the existing ownerless rows. It is out of scope here and was rejected as heavier than the problem.
Alternatives considered
- Create-only for a child. Let a child create a list or meal but not rename, delete or manage it afterward. Rejected: it leaves a child unable to fix their own typo or clear their own list, an odd half-right, and it still needs the same matrix touch.
- Own-only, mirroring ADR-0014. Give a child rights over the lists and meals they created, scoped by a new owner field. Rejected for this issue: it forces a schema change on two entities, a new guard, and a migration decision for existing ownerless rows — far heavier than the problem, and these surfaces are shared by design, not personal like a job.
- Leave it adult-only. Rejected: it does not match how a household runs a shared shopping list or menu, and it was the status quo the issue set out to correct.