Skip to content

Wellbeing history was browsable by every staff role - #144

Merged
github-actions[bot] merged 1 commit into
masterfrom
fix/leitung-and-sozialarbeit
Aug 31, 2026
Merged

Wellbeing history was browsable by every staff role#144
github-actions[bot] merged 1 commit into
masterfrom
fix/leitung-and-sozialarbeit

Conversation

@catomean

Copy link
Copy Markdown
Collaborator

Found by walking the Sozialarbeit role in production — the one care role AOZ is now hiring for, and the only one never exercised.

Three findings, all the same shape: a permission that exists, that the nav honours, and that nothing on the server enforces.

1. A client's wellbeing history was open to every role

SatisfactionHistory on residents/[id] had no permission check at all. Any staff role saw a client's full check-in history just by opening their page — confirmed against a Jobcoach, who holds neither placements:read nor incidents:read.

This is the complaint this whole refactor started from: staff seeing the smiley scale as a property of the client rather than of an appointment they conducted. Moving capture into closing an appointment fixed half of it and left reading open.

Now gated on placements:read, matching /placements and /analytics. A role without it is not cut off from someone in trouble — a check-in of 1 or 2 already raises a WELLBEING incident, which carries its own permission. The signal still travels; browsing someone's mood history does not.

2. Maintenance declared permissions it never enforced

The three maintenance pages had no guard, and all five server actions checked requireStaffAuth() — signed in, nothing more. The nav gates Wartung on maintenance:read.

Verified in production as a Sozialarbeiter*in holding neither maintenance permission: the nav correctly hid Wartung, and the URL served her the whole board with working Neue Anfrage, Zuweisen and Abschliessen buttons.

Pages and actions now enforce read/write. Residents are unaffected/api/portal/report writes MaintenanceRequest via Prisma directly, not through these actions.

If other roles should be able to report a defect they noticed on a visit — plausible in distributed housing, where whoever is there is who sees it — that is a deliberate ROLE_PERMISSIONS change, not an unguarded page. Flagged for you, not decided by me.

3. My own gate was too weak to catch #2

admin-page-guards.test.ts said in a comment that a page both enforcing a permission and sitting on the session-is-enough list is a stale entry — then asserted an OR that allowed exactly that. Tightened to exactly-one.

Also: "There is no Leitung" was wrong

Asserted as settled fact in CLAUDE.md, role-policy.ts and schema.prisma. AOZ was recruiting a Programmleiter*in and a Teamleiter*in Betreuung for the «Begleitung im regulären Wohnraum» pilot this product is named after. Only those three named people have no lead among them.

The split already expresses a team lead — BETREUUNG + ALL_DOMAINS + not isSystemAdmin — so the correction must not become a LEITUNG enum value, which would rebuild the bundled role ADMIN was retired for. Pinned by a test.


All three fixes mutation-proven. 199 suites / 3511 tests green.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Cd183M6472xBgTKWA2is6h

…role

Found by walking the Sozialarbeit role in production — the one care role
AOZ is now hiring for and the only one never exercised.

Three findings, all the same shape: a permission that exists, that the nav
honours, and that nothing on the server enforces.

1. SatisfactionHistory on residents/[id] had NO permission check. Every staff
   role saw a client's full wellbeing check-in history by opening their page.
   Confirmed earlier this session against a Jobcoach, who holds neither
   `placements:read` nor `incidents:read` and still got "Zufriedenheits-
   Check-ins" on any client he opened.

   This is the complaint the whole refactor started from — staff seeing the
   smiley scale as a property of the client rather than of an appointment they
   conducted. Moving CAPTURE into closing an appointment fixed half of it and
   left READING wide open. Now gated on `placements:read`, matching
   /placements and /analytics.

   A role without it is not cut off from someone in trouble: a check-in of 1
   or 2 already raises a WELLBEING incident, which carries its own permission.
   The signal still travels; browsing someone's mood history does not.

2. /maintenance, /maintenance/new and /maintenance/[id] had no guard at all,
   and all five server actions checked `requireStaffAuth()` — signed in,
   nothing more. The nav gates Wartung on `maintenance:read`. Verified in
   production as a Sozialarbeiter*in holding neither maintenance permission:
   the nav hid Wartung and the URL served her the board, with working
   "Neue Anfrage", "Zuweisen" and "Abschliessen" buttons.

   Pages and actions now enforce read/write. Residents are unaffected —
   /api/portal/report writes MaintenanceRequest via Prisma directly, not
   through these actions.

   If other roles SHOULD be able to report a defect they noticed on a visit —
   plausible in distributed housing — that is a deliberate ROLE_PERMISSIONS
   change, not an unguarded page. Flagged, not decided.

3. My own gate was too weak to catch #2. `admin-page-guards.test.ts` said in a
   comment that a page both enforcing a permission AND sitting on the
   session-is-enough list is a stale entry, then asserted an OR that allowed
   exactly that. Tightened to exactly-one, which is what the comment claimed.

Also corrects "There is no Leitung", asserted as settled fact in CLAUDE.md,
role-policy.ts and schema.prisma. AOZ was recruiting a Programmleiter*in and a
Teamleiter*in Betreuung for the `Begleitung im regulären Wohnraum` pilot this
product is named after; only those three named people have no lead among them.
The split already expresses a team lead — BETREUUNG + ALL_DOMAINS + not
isSystemAdmin — so the correction must not become a LEITUNG enum value, which
would rebuild the bundled role ADMIN was retired for. Pinned by a test.

All three fixes mutation-proven.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cd183M6472xBgTKWA2is6h
@github-actions
github-actions Bot merged commit fa2de86 into master Aug 31, 2026
4 checks passed
@github-actions
github-actions Bot deleted the fix/leitung-and-sozialarbeit branch August 31, 2026 17:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant