feat: sub-verb rows end the proxy-verb idiom (#135) #149

Merged
nalum merged 3 commits from feat/sub-verb-affordances into main 2026-08-18 16:44:00 +00:00
Owner

Closes #135. PR 11 of the #137 stack, based on #148. Three commits — ADR + server rows first, then each client re-gated, per the issue's ordering.

Server (ADR-0031 amended) — three role-only guard halves become matrix rows the guards themselves consult, so the row is the policy for the server and every client alike:

  • ItemService/Update.other (Admin, Member) — svc.RequireItemManager's role half; the ownership half (own or unowned passes) stays in the guard, mirrored client-side.
  • RewardService/Claim.other (Admin) — claiming on another's behalf; the Claim handler consults it.
  • SystemService/ExportUserData.other (Admin) — the ADR-0027 admin half, via a new svc.RequireSubVerb helper.

Web re-gate — ItemService/Delete no longer stands for the manager tier on Jobs/Today (now Update.other + the ownership mirror); the job sheet's points affordances read RewardService/Create and LinkItem (the cross-service proxy the review called sharpest); ListDetail splits its one flag into Rename/UncheckAll/Delete and gates entry reassignment on AssignUser.other and convert on SetList + the manager mirror; Meals gates push-ingredients on PushIngredients and the time-zone chip on SetTimeZone; Rewards splits grant/edit/delete.

Android re-gate — the same, ending the two divergences the review found: the list screen had proxied Create where the web proxied Update (both now read the real verbs), and meal editing had proxied Create where the web read Update.

Live-verified: GetPermissionMatrix serves all five dotted rows; a MEMBER claiming for a CHILD and a MEMBER exporting another member are both denied with the original messages while ADMIN claim-on-behalf still lands; admin affordances render unchanged in a headless-browser pass. make check green.

🤖 Generated with Claude Code

Closes #135. PR 11 of the #137 stack, based on #148. Three commits — ADR + server rows first, then each client re-gated, per the issue's ordering. **Server (ADR-0031 amended)** — three role-only guard halves become matrix rows the guards themselves consult, so the row is the policy for the server and every client alike: - `ItemService/Update.other` (Admin, Member) — `svc.RequireItemManager`'s role half; the ownership half (own or unowned passes) stays in the guard, mirrored client-side. - `RewardService/Claim.other` (Admin) — claiming on another's behalf; the Claim handler consults it. - `SystemService/ExportUserData.other` (Admin) — the ADR-0027 admin half, via a new `svc.RequireSubVerb` helper. **Web re-gate** — `ItemService/Delete` no longer stands for the manager tier on Jobs/Today (now `Update.other` + the ownership mirror); the job sheet's points affordances read `RewardService/Create` and `LinkItem` (the cross-service proxy the review called sharpest); ListDetail splits its one flag into Rename/UncheckAll/Delete and gates entry reassignment on `AssignUser.other` and convert on `SetList` + the manager mirror; Meals gates push-ingredients on `PushIngredients` and the time-zone chip on `SetTimeZone`; Rewards splits grant/edit/delete. **Android re-gate** — the same, ending the two divergences the review found: the list screen had proxied `Create` where the web proxied `Update` (both now read the real verbs), and meal editing had proxied `Create` where the web read `Update`. Live-verified: `GetPermissionMatrix` serves all five dotted rows; a MEMBER claiming for a CHILD and a MEMBER exporting another member are both denied with the original messages while ADMIN claim-on-behalf still lands; admin affordances render unchanged in a headless-browser pass. `make check` green. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Three role-only guard halves lived only inside handlers, so clients
proxied whole tiers through unrelated verbs — ItemService/Delete
standing for job management on three surfaces, web and Android even
disagreeing on which wrong verb to elect (#135). The rows now exist
(ADR-0031 amended): Update.other names RequireItemManager's role half
(Admin|Member), Claim.other names who claims on another's behalf, and
ExportUserData.other names the admin half of non-self export. Each
guard consults its own row, so the row is the policy for the server
and every client alike.
The proxy-verb idiom goes (#135): ItemService/Delete stood for job
management (and even for RewardService/Create on step points),
ItemListService/Update for four list verbs, MealService/Create for
push-ingredients, SetOverride for the time-zone chip, and
RewardService/Create for reward edit and delete. Each control now
reads its own verb — the manager tier through the new Update.other
row plus the guard's ownership mirror — so a future matrix split
changes what appears without touching a client.
fix(android): every control gates on the verb it performs
Some checks failed
check / commits (pull_request) Successful in 6s
check / web (pull_request) Successful in 1m35s
check / go (pull_request) Successful in 2m49s
check / report (pull_request) Successful in 4s
android / build (pull_request) Successful in 5m19s
android / report (pull_request) Successful in 4s
android / report (push) Has been cancelled
check / report (push) Has been cancelled
check / web (push) Has been cancelled
tag / tag (push) Has been cancelled
check / commits (push) Has been cancelled
check / go (push) Has been cancelled
android / build (push) Has been cancelled
1f68cdf941
The Android half of #135, mirroring the web commit: the manager tier
reads the Update.other row (never the Delete proxy), the list screen
splits its one flag into the rename, untick and delete verbs it had
proxied through Create — ending the web/Android disagreement over
which wrong verb to elect — entry reassignment reads AssignUser.other
and convert-to-job the SetList verb with the manager mirror, meal
editing reads Update (not Create), push-ingredients and the time-zone
chip read their own verbs, and reward edit and delete split from the
Create flag.

Test report

Suite Tests Result Skipped
Unit 1370 ✅ pass 1
Integration 86 ✅ pass —

Coverage: 28.0%

Updated by the check workflow · commit 1f68cdf941

<!-- ci-test-report --> ## Test report | Suite | Tests | Result | Skipped | | --- | --: | --- | --: | | Unit | 1370 | ✅ pass | 1 | | Integration | 86 | ✅ pass | — | **Coverage:** 28.0% <sub>Updated by the check workflow · commit 1f68cdf941396d17f2ccaa545220b78c86899e82</sub>

Android test report

Suite Tests Result Skipped
Unit (debug) 31 ✅ pass 0

Coverage: 2.4% of lines

Updated by the android workflow · commit 1f68cdf941

<!-- android-test-report --> ## Android test report | Suite | Tests | Result | Skipped | | --- | --: | --- | --: | | Unit (debug) | 31 | ✅ pass | 0 | **Coverage:** 2.4% of lines <sub>Updated by the android workflow · commit 1f68cdf941396d17f2ccaa545220b78c86899e82</sub>
nalum changed target branch from feat/job-card-redesign to main 2026-08-18 16:43:51 +00:00
nalum merged commit 1f68cdf941 into main 2026-08-18 16:44:00 +00:00
nalum deleted branch feat/sub-verb-affordances 2026-08-18 16:44:00 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
eagraiclainne/app!149
No description provided.