App reference

ADR-0014: Job ownership and editing

Metadata

  • Status: Accepted
  • Date: 2026-07-25
  • Deciders: EagraΓ­ Clainne Team (decisions taken in a design interview, 2026-07-25)
  • Related: ADR-0009 (service-layer authorization β€” the owner guard this supersedes for items); ADR-0008 (role matrix, as amended 2026-07-25); ADR-0011 (CLI, which gains an item unassign command)

Context

Jobs (items) were create-only after birth. The Jobs page's Everyone view groups by person with a "Not assigned yet" bucket, but a job card carries exactly one action β€” Done, on your own jobs. Nothing in the UI could take an unassigned job, hand a job to someone, change a title or deadline, or delete a job; the owner was fixed at creation or via the CLI.

The backend was close but not right for this either. Under ADR-0009's owner-or-admin rule, a member could not reassign or edit anyone else's job β€” so a parent on the MEMBER role could not move a chore between children. And after the 2026-07-25 matrix widening admitted CHILD to AssignUser, the "unowned items are open" rule would have let a child assign an unassigned job to anyone β€” sibling warfare by teatime. Read visibility had the same skew: non-admins were filtered to own-plus-unowned, so a member's Everyone view couldn't even see the jobs they ought to manage.

Decision

One rule per role: managers and takers

  • Admins and members are job managers. They may assign or reassign any job to anyone, unassign it, edit any job's title, description and deadline, and delete any job. They see every job.
  • Children are takers. A child may take an unassigned job for themselves, put a job they own back to unassigned, and edit the fields of a job they own. A child may not assign a job to anyone else, take a job someone else owns, or delete a job. Children continue to see their own jobs plus the unassigned pool.

Enforcement moves with the rule

  • A new service guard, svc.RequireItemManager, replaces the ADR-0009 owner-or-admin guard on item Read/Update/SetDeadline/Delete: ADMIN and MEMBER pass outright; everyone else must own the item. Unowned items remain readable/editable by takers only insofar as the specific rules above allow. Denials are counted under the existing eagraiclainne.authz.denials metric with guard item_manager.
  • AssignUser gets role-shaped branches: managers may set any target (or none); a child's target must be themselves (and only onto an unassigned or already-theirs job) or empty on a job they own (put-back).
  • ItemService/Delete leaves the CHILD row of the matrix β€” a flat "no" needs no resource context, so it belongs at the role gate, not a guard.
  • ListItems visibility: ADMIN and MEMBER see everything; CHILD keeps the own-plus-unassigned filter.

Unassigning is AssignUser with an empty target

AssignUser with an empty user_uid now means unassign (previously a missing-field error). No new RPC: the operation is "set the owner", and "no owner" is a legal value of that. The CLI grows an item unassign <uid> command so the empty string never has to be typed.

The UI: a job sheet plus one-tap Take

  • Tapping any job card (Mine or Everyone) opens a job sheet β€” the same pattern as the Family member sheet: the job's title, description and deadline as editable fields, an owner row showing every member's mark as a picker (managers only; a child sees Take / Put back on their own), and Delete for managers. Save issues only the calls that changed (Update / SetDeadline / AssignUser).
  • Unassigned cards in the Everyone view also carry a one-tap "Take this job" button β€” the everyday action shouldn't cost a sheet-open.
  • Quality floor as everywhere: keyboard access, visible focus, 44px targets, sentence-case copy.

Consequences

Positive

  • Jobs become fully manageable after creation, from the page where they live; the "Not assigned yet" bucket becomes a pool people can actually draw from.
  • The role story is one sentence per role, and it matches the reward and event policy set on the same day: adults run the household, children act on their own things.
  • The child take-only rule closes the assign-to-sibling hole the matrix widening opened, before any child ever saw it.
  • Members finally see the whole board, which the manager role requires.

Negative / trade-offs

  • Members can now read and edit every job β€” ADR-0009's item privacy (owner-only reads) is deliberately traded away for household manageability. Items were never private in practice (admins and the audit trail saw all), but the ADR-0009 text no longer describes items.
  • Empty-target AssignUser is a semantic change to an existing RPC; old clients that relied on the missing-field error now perform an unassign.
  • The child take/put-back rules live in the AssignUser handler as explicit branches β€” more conditional authorization logic than the one-guard pattern elsewhere.

Alternatives considered

  • A separate TakeJob RPC β€” cleaner audit label, but it duplicates AssignUser with the target hardwired; the branch in AssignUser is smaller than a new RPC across proto, client, CLI and web.
  • Children may assign to anyone (keep the open rule) β€” rejected in the interview: guaranteed misuse, no family value.
  • Keep owner-or-admin for members β€” rejected: a member parent must be able to move a chore between kids without borrowing the admin account.
  • Editing stays owner-only while reassignment widens β€” rejected: adjacent buttons with different rules is confusion by design.
  • Ownership-only ADR, editing later β€” rejected: the job sheet would be built twice.