Admins never inhabit a seat + count chips bypass tn() ("1 members", "1 rules") #426

Closed
opened 2026-09-05 01:32:25 +00:00 by Cordy · 3 comments
Owner

Two findings from the #346 live verification pass (2026-09-05), parked deliberately — cut as decided, not yet to build.

1. Seat accounting: admins are invisible to seat usage

Observed: bao Overview shows Seats in use 0 of 60 while two admins are signed in / active.

Root cause (internal/license/provider.go):

if !u.Admin && !p.mgr.Admit(u.Username) {
    return nil, ErrSeatLimit
}

For u.Admin == true (including every adminGroups member), Admit() is never called — and Admit() is the only writer of SeenUsers, which feeds both the Seats tile (SeatsUsed) and the seatsUsed figure reported at licence check-in. Admins therefore neither consume nor even register a seat.

Two fused concerns to separate:

  • Admission (cap enforcement): admins must always pass — deliberate break-glass, an instance at cap must never lock out its operators. Keep exactly as is.
  • Recording: everyone who signs in arguably inhabits a seat, admins included (Nikola's position). Currently undercounted — with a revenue dimension, since check-in reports seatsUsed.

Fix shape: a record-always / cap-only-for-non-admins path in the Manager (e.g. Admit(username, isAdmin) or a separate Record), wired in seatProvider.Authenticate; tests for admin-records-but-never-blocked, at-cap admin sign-in, and existing-user idempotency. Note Admit's internal m.admins bypass only covers static auth.admins, not group admins — the wrapper's u.Admin is the correct authority; the internal map check becomes redundant once the split exists.

Decision to bless before building: do admins consume a paid seat, or only get counted? (Recording is unambiguous; whether they occupy one of the licensed N when at cap is a business call — though with always-pass admission, at-cap admins would overshoot the cap in the count rather than be denied, which is honest reporting.)

2. Count chips bypass tn(): singular/plural bugs

Two confirmed instances, found minutes apart:

  • Users & access, admin-group chip: "1 members" (seen live during the #346 group-removal round trip)
  • Admin Overview, retention row: "1 rules" (Nikola's screenshot)

Both interpolate a raw count into a pluralised string instead of going through tn(). Fix ×4 locales for both, plus a sweep of index.html for siblings — two found casually implies more (any {n} <plural-noun> chip/row built with tf() instead of tn()).

Verification

  • Seats: sign in as admin on a fresh instance → Seats in use = 1; non-admin at cap still rejected; admin at cap still admitted.
  • Copy: retention row and admin-group chip read "1 member" / "1 rule" in all four locales; sweep results listed in the PR.
Two findings from the #346 live verification pass (2026-09-05), parked deliberately — cut as decided, not yet to build. ## 1. Seat accounting: admins are invisible to seat usage Observed: bao Overview shows **Seats in use 0 of 60** while two admins are signed in / active. Root cause (`internal/license/provider.go`): ```go if !u.Admin && !p.mgr.Admit(u.Username) { return nil, ErrSeatLimit } ``` For `u.Admin == true` (including every `adminGroups` member), `Admit()` is never called — and `Admit()` is the **only** writer of `SeenUsers`, which feeds both the Seats tile (`SeatsUsed`) and the `seatsUsed` figure reported at licence check-in. Admins therefore neither consume nor even register a seat. Two fused concerns to separate: - **Admission** (cap enforcement): admins must always pass — deliberate break-glass, an instance at cap must never lock out its operators. Keep exactly as is. - **Recording**: everyone who signs in arguably inhabits a seat, admins included (Nikola's position). Currently undercounted — with a revenue dimension, since check-in reports `seatsUsed`. Fix shape: a record-always / cap-only-for-non-admins path in the Manager (e.g. `Admit(username, isAdmin)` or a separate `Record`), wired in `seatProvider.Authenticate`; tests for admin-records-but-never-blocked, at-cap admin sign-in, and existing-user idempotency. Note `Admit`'s internal `m.admins` bypass only covers static `auth.admins`, not group admins — the wrapper's `u.Admin` is the correct authority; the internal map check becomes redundant once the split exists. Decision to bless before building: **do admins consume a paid seat, or only get counted?** (Recording is unambiguous; whether they occupy one of the licensed N when at cap is a business call — though with always-pass admission, at-cap admins would overshoot the cap in the count rather than be denied, which is honest reporting.) ## 2. Count chips bypass tn(): singular/plural bugs Two confirmed instances, found minutes apart: - Users & access, admin-group chip: "**1 members**" (seen live during the #346 group-removal round trip) - Admin Overview, retention row: "**1 rules**" (Nikola's screenshot) Both interpolate a raw count into a pluralised string instead of going through `tn()`. Fix ×4 locales for both, plus a **sweep of index.html for siblings** — two found casually implies more (any `{n} <plural-noun>` chip/row built with `tf()` instead of `tn()`). ## Verification - Seats: sign in as admin on a fresh instance → Seats in use = 1; non-admin at cap still rejected; admin at cap still admitted. - Copy: retention row and admin-group chip read "1 member" / "1 rule" in all four locales; sweep results listed in the PR.
Author
Owner

Item 3 (from the #346 broken-secret live test): the Admin client row shows config presence, not live health.

With a deliberately wrong auth.groups.keycloakClientSecret, the Users & access page correctly fail-closes the Administrators list (gone entirely, no static-name degradation) and drops the member-count chip — but the Admin client row keeps saying "Configured · group membership can be resolved" with a green On chip while every resolve is failing. The failure is signalled only by omission.

Fix shape: the /admin/users endpoint already knows the resolve failed (that is what suppresses the list) — surface it. When configured-but-failing: row copy along the lines of "Configured · the directory refused the credentials — group membership cannot be resolved" with an amber chip, distinct from both "Not configured" and healthy "On". i18n ×4. A dogfound-state test: endpoint returns configured=true + resolveError=true → UI renders the amber row and no Administrators section.

**Item 3 (from the #346 broken-secret live test): the Admin client row shows config presence, not live health.** With a deliberately wrong `auth.groups.keycloakClientSecret`, the Users & access page correctly fail-closes the Administrators list (gone entirely, no static-name degradation) and drops the member-count chip — but the Admin client row **keeps saying "Configured · group membership can be resolved" with a green On chip** while every resolve is failing. The failure is signalled only by omission. Fix shape: the `/admin/users` endpoint already knows the resolve failed (that is what suppresses the list) — surface it. When configured-but-failing: row copy along the lines of *"Configured · the directory refused the credentials — group membership cannot be resolved"* with an amber chip, distinct from both "Not configured" and healthy "On". i18n ×4. A dogfound-state test: endpoint returns configured=true + resolveError=true → UI renders the amber row and no Administrators section.
Author
Owner

Decision blessed (Nikola, 2026-09-05): admins consume a paid seat, and remain always able to sign in. At cap, an admin sign-in records honestly past the cap rather than being denied — enforcement never locks out operators, reporting never lies.

Research note: industry practice on admin seats genuinely varies by product/market; the consistent best practices are (a) a clearly documented seat definition, (b) honest usage reporting, (c) enforcement that cannot lock out administrators. This decision satisfies all three. A line stating the definition ("every distinct signed-in account occupies a seat, administrators included; administrators are never denied sign-in by the cap") goes into licensing-faq.md.

Implementation starting, TDD with a witnessed red phase: failing tests for Admit(username, isAdmin) (admin recorded; admin at cap admitted and recorded over cap; idempotency; non-admin at cap still refused) pushed and observed failing on the runner before the implementation commit. Item 2 (tn() sweep) rides the same release. Item 3 (admin-client health row) is UI: mockup first, built only after approval.

**Decision blessed (Nikola, 2026-09-05): admins consume a paid seat, and remain always able to sign in.** At cap, an admin sign-in records honestly past the cap rather than being denied — enforcement never locks out operators, reporting never lies. Research note: industry practice on admin seats genuinely varies by product/market; the consistent best practices are (a) a clearly documented seat definition, (b) honest usage reporting, (c) enforcement that cannot lock out administrators. This decision satisfies all three. A line stating the definition ("every distinct signed-in account occupies a seat, administrators included; administrators are never denied sign-in by the cap") goes into licensing-faq.md. **Implementation starting, TDD with a witnessed red phase:** failing tests for `Admit(username, isAdmin)` (admin recorded; admin at cap admitted and recorded over cap; idempotency; non-admin at cap still refused) pushed and observed failing on the runner before the implementation commit. Item 2 (tn() sweep) rides the same release. Item 3 (admin-client health row) is UI: mockup first, built only after approval.
Cordy closed this issue 2026-09-05 02:05:34 +00:00
Author
Owner

Shipped and live-verified — v0.6.128 (PR #427; both dogfoods rolled 02:09 UTC).

All three items built strictly TDD with the red phase witnessed on the runner before each implementation (run logs show e.g. SeatsUsed = 0, want 1 and SeatsUsed = 50, want 51 failing, then green in the same job):

  1. Seats count adminsAdmit(username, isAdmin): record always, cap only for non-admins; admission untouched (break-glass preserved; honest overshoot at cap). Seat definition documented in licensing-faq.md. Live proof minutes after rollout: bao Overview went from 0 of 60 to "Seats in use 1 of 60 · 59 unassigned" on the first admin sign-in.
  2. tn() sweep — 14 singular forms ×4 locales. Live on the same page: "1 rule" (was "1 rules") and "1 peer registered" (was "1 peers registered"). Deliberately skipped, recorded here: transient progress counters (keyscan/drain chips and sub-labels) and multi-variable summaries (tpuSummary, tlgSummary, audShownOf, licSeatsFoot) — revisit only if one ever shows up ugly in practice.
  3. Admin-client health row (Option B action copy, blessed) — adminClientFailing endpoint field; amber "Failing" chip with "check the admin client secret and that the IdP is reachable"; explanatory note where the Administrators section would be. Endpoint behaviour covered by three tests (failing / healthy / unconfigured), fail-closed list retained.

Closing.

**Shipped and live-verified — v0.6.128** (PR #427; both dogfoods rolled 02:09 UTC). All three items built strictly TDD with the red phase witnessed on the runner before each implementation (run logs show e.g. `SeatsUsed = 0, want 1` and `SeatsUsed = 50, want 51` failing, then green in the same job): 1. **Seats count admins** — `Admit(username, isAdmin)`: record always, cap only for non-admins; admission untouched (break-glass preserved; honest overshoot at cap). Seat definition documented in licensing-faq.md. **Live proof minutes after rollout:** bao Overview went from *0 of 60* to **"Seats in use 1 of 60 · 59 unassigned"** on the first admin sign-in. 2. **tn() sweep** — 14 singular forms ×4 locales. Live on the same page: **"1 rule"** (was "1 rules") and **"1 peer registered"** (was "1 peers registered"). Deliberately skipped, recorded here: transient progress counters (keyscan/drain chips and sub-labels) and multi-variable summaries (`tpuSummary`, `tlgSummary`, `audShownOf`, `licSeatsFoot`) — revisit only if one ever shows up ugly in practice. 3. **Admin-client health row** (Option B action copy, blessed) — `adminClientFailing` endpoint field; amber "Failing" chip with *"check the admin client secret and that the IdP is reachable"*; explanatory note where the Administrators section would be. Endpoint behaviour covered by three tests (failing / healthy / unconfigured), fail-closed list retained. Closing.
Sign in to join this conversation.
No labels
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#426
No description provided.