#552: admin grant source through the API, honest revoke affordance (v0.6.205) #558

Merged
Cordy merged 6 commits from fix-552a into main 2026-09-16 00:33:30 +00:00
Owner

Fixes the silent no-op found in the mockup audit: on a directory account whose admin role comes from the admin group, Remove admin was offered, wrote a revocation to admins.json, and changed nothing — the group kept granting it.

Root cause (traced before fixing): adminNames() flattened four grant sources — config allow-list, dashboard grant, local flag, IdP group — into map[string]bool, so the grant source was discarded at the API boundary and neither the page nor the endpoint could tell a revocable grant from one the directory owns. Fixing the button alone would have been a symptom fix.

Fix, both halves (the house pattern the last-admin guard already uses — server refuses and client disables):

  • AccessAdmin.Source (group / config / local / grant) from a single adminSources() lookup; adminNames() stays as a thin wrapper so existing callers and tests are untouched. A person holding several sources keeps the strongest, since that's what decides whether revoking here would do anything.
  • The directory row shows the origin ("via the cairn-admins group") and renders Remove admin disabled with an explanatory tooltip.
  • directoryAdminSet refuses such a revocation with 409 naming the owner. An unreadable directory proves nothing, so it doesn't block.

TDD: three tests written first and watched fail — source empty, and the revoke returning 200 where 409 was required (the bug, reproduced). The third pins that dashboard grants stay revocable, guarding against over-reach. All green, full suite green, fence gate + node checks clean. i18n ×4.

Fixes the silent no-op found in the mockup audit: on a directory account whose admin role comes from the admin group, **Remove admin** was offered, wrote a revocation to `admins.json`, and changed nothing — the group kept granting it. **Root cause** (traced before fixing): `adminNames()` flattened four grant sources — config allow-list, dashboard grant, local flag, IdP group — into `map[string]bool`, so the grant source was discarded at the API boundary and neither the page nor the endpoint could tell a revocable grant from one the directory owns. Fixing the button alone would have been a symptom fix. **Fix, both halves** (the house pattern the last-admin guard already uses — server refuses *and* client disables): - `AccessAdmin.Source` (`group` / `config` / `local` / `grant`) from a single `adminSources()` lookup; `adminNames()` stays as a thin wrapper so existing callers and tests are untouched. A person holding several sources keeps the strongest, since that's what decides whether revoking here would do anything. - The directory row shows the origin ("via the cairn-admins group") and renders Remove admin disabled with an explanatory tooltip. - `directoryAdminSet` refuses such a revocation with 409 naming the owner. An unreadable directory proves nothing, so it doesn't block. **TDD:** three tests written first and watched fail — source empty, and the revoke returning 200 where 409 was required (the bug, reproduced). The third pins that dashboard grants stay revocable, guarding against over-reach. All green, full suite green, fence gate + node checks clean. i18n ×4.
Red phase for the silent no-op found in the mockup audit: revoking a
directory admin whose role comes from the admin group writes to
admins.json and changes nothing, because the access payload flattens
four grant sources into names. Pins: AccessAdmin.Source reports
config/group/grant, directoryAdminSet refuses a group-granted
revocation with 409 naming the group, and a dashboard grant stays
revocable.
fix(#552): admin grant source through the API, honest revoke affordance (v0.6.205)
All checks were successful
ci / test-and-build (pull_request) Successful in 58s
ceee5445af
Root cause of the silent no-op: adminNames flattened four grant
sources into names, so neither the page nor the endpoint could tell a
revocable dashboard grant from one the admin group owns. AccessAdmin
now carries Source (group/config/local/grant) from a single
adminSources lookup; the directory row shows the origin and disables
Remove admin with a tooltip when the role is owned elsewhere; and
directoryAdminSet refuses such a revocation with 409 naming the
owner rather than writing a file that changes nothing. Green on the
three tests from the previous commit.
Cordy scheduled this pull request to auto merge when all checks succeed 2026-09-16 00:32:24 +00:00
Cordy merged commit 37f112494f into main 2026-09-16 00:33:30 +00:00
Cordy deleted branch fix-552a 2026-09-16 00:33:33 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
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: Cordy/Cairn#558
No description provided.