ADORSYS-GIS / ADORSYS-GIS/lightbridge-authz
[Ticket]: No membership concept exists for Account — /settings/accounts/<id> cannot show real Members data
- Lingua principale
- Rust
- Stelle
- 0
- Fork
- 1
- Merge medio
- 6h 42m
- PR unite (30g)
- 246
Descrizione
## Type
Feature
## Summary
We need a way to read who has access to an account because the new `/settings/accounts/` screen (converse-frontends#368, IA v3 phase E) has a "Members" block with nothing to fetch — `Account` carries no membership concept at all today, only a single owner (`userId`).
Expected result:
> A `budget:read-own`-tier-equivalent, ownership-gated procedure returns the people who have access to a given account, OR — if per-account membership is deliberately out of scope (see Intent below) — this ticket is closed as won't-fix with that decision recorded, so the frontend's honest "unavailable" caption can point at a permanent answer instead of an open question.
## Intent
`/settings/accounts/` (converse-frontends#368, IA v3 phase E — the settings/accounts information-architecture move) is the new per-account settings screen: rename, id/status/quota-tier facts, and a "Members" block the owner's own directive named explicitly ("`/settings/accounts/` would be for account related settings like e.g members"). Implementing that block surfaced a real ambiguity worth resolving here rather than guessing at in the frontend: **`Account` has no membership model at all.**
Per `authz.cstack`'s own `Account` model doc comment (the NOTE above its `@@allow("delete", ...)` clause): *"per ADR-0006 there is no more membership/role concept to gate it with -- one account is one person."* The only relationship `Account` carries is `userId` (a single owner). Membership as a concept exists exactly once in this schema, on `ProjectMember` (`authz.cstack:528`, read via `procedure.listProjectRoster`, `authz.cstack:903`) — scoped to a PROJECT, not an account.
So this ticket is genuinely two possible outcomes, and we need lightbridge-authz's own call on which:
1. **Account-level membership is a real, wanted concept** (e.g. "who on my team can see this account's billing/budget") — in which case this ticket is a real feature request: a new `AccountMember`-shaped model/table plus a `listAccountMembers` (or similar) procedure, ownership-gated the same way `listProjectRoster` is scoped to project ownership/membership today.
2. **Account-level membership is intentionally out of scope** (ADR-0006 reads as a deliberate simplification, not an oversight — "one account is one person") — in which case the console's own Members block should say so as a permanent, honest fact ("Accounts have no membership concept — only projects do"), not a "coming soon" caption implying a read API is merely missing.
Either answer is useful; what is not useful is the frontend guessing. The block ships today as a real, disabled-with-reason row (same pattern as the "Roles" nav row's own honest gap, converse-frontends#368/lightbridge-authz#571) rather than fabricated or omitted, until this is resolved.
## Source of truth
- converse-frontends#368 (epic: Console UI/UX revamp, phase E — "the settings/accounts move")
- Owner directive (verbatim, in the phase E build brief): "And /settings/accounts/ would be for account related settings like e.g members."
- ADR-0006 (one account is one person — the account/user 1:1 collapse this schema's own `Account` model doc comment cites)
- `crates/lightbridge-authz-api/schema/authz.cstack:132-175` (`Account` model, `userId`, and the "no membership/role concept" NOTE above its `@@allow("delete", ...)` clause)
- `crates/lightbridge-authz-api/schema/authz.cstack:528` (`ProjectMember` — the one membership model that DOES exist, scoped to `Project`)
- `crates/lightbridge-authz-api/schema/authz.cstack:903` (`procedure listProjectRoster` — the project-scoped precedent an account-scoped equivalent would follow)
## Current Behavior
- `Account` has exactly one relationship field: `userId` (the owner). No `AccountMember` model, no roster, no procedure that lists "who has access to this account" exists anywhere in `authz.cstack`.
- `procedure.listProjectRoster(args: ListProjectRosterInput): ProjectMember[]` exists, but answers for a PROJECT's membership, not an account's — it cannot be repurposed to answer "who has access to this account" without also enumerating and de-duplicating across every project the account owns, which is a workaround, not the real answer, and would misrepresent project-level access as account-level access.
- The console's `/settings/accounts/` screen therefore renders its "Members" block disabled, with an inline reason, rather than fabricating a roster or silently omitting the block (converse-frontends' own honesty doctrine, ADR 0012 D8 / ADR 0013 D2, extended from navigation to page content).
## Expected Behavior
One of:
- A new `budget:read-own`-tier, ownership-gated procedure (e.g. `listAccountMembers(accountId) -> AccountMember[]`) backed by a real `AccountMember`-shaped relation, mirroring `ProjectMember`'s own shape and `listProjectRoster`'s own ownership/membership gating — OR
- An explicit, recorded decision that per-account membership is out of scope for the foreseeable future, so the frontend's caption can say so permanently rather than "not yet available."
## Acceptance Criteria
- [ ] Given this ticket is triaged, when lightbridge-authz decides which of the two Intent outcomes applies, then that decision is recorded here (a comment or a ticket-status change is enough — no schema change is required to close this as "decided: out of scope").
- [ ] If outcome 1 (a real feature): a procedure exists that returns account membership for an `accountId` the caller owns (or, if members-of-members is meaningful here, is a member of), refused for any other caller — never a 403-shaped silent success with someone else's roster.
- [ ] If outcome 1: existing `Account`/`Project`/`ProjectMember` behavior is unchanged (additive only).
- [ ] Relevant tests are added or updated if a procedure is implemented (schema `@@allow` coverage + service-layer test).
- [ ] Verification evidence is provided either way (a design decision recorded, or a tested procedure).
## Out of Scope
- Any change to `ProjectMember`/`listProjectRoster`'s own existing behavior.
- The console UI's own Members block wiring, once a real procedure exists — that is a follow-up frontend ticket, not part of this one.
## Technical Context
- `crates/lightbridge-authz-api/schema/authz.cstack:132-175` (`Account` — `userId`, the "no membership/role concept" NOTE).
- `crates/lightbridge-authz-api/schema/authz.cstack:528` (`ProjectMember`) and `:903` (`listProjectRoster`) — the closest existing precedent for shape and gating, if outcome 1 is chosen.
- Frontend context: `apps/console/src/containers/use-account-detail-screen.ts` (`MEMBERS_DISABLED_REASON`), `apps/console/src/app/(console)/settings/accounts/[accountId]/page.tsx` — converse-frontends, IA v3 phase E.
## Risks
- Building a real account-membership feature without a clear product decision first risks a model that duplicates or conflicts with `ProjectMember`'s existing role. This ticket is deliberately structured to force that decision before any schema work, not after.
- Leaving this open indefinitely without a decision leaves the console's Members block honestly disabled but permanently vague ("not yet available" when the true answer might be "never will be") — closing with a recorded decision either way is the actual deliverable, not necessarily new code.
## Test Plan
- If outcome 1: schema-level `@@allow` test(s) for the new procedure/model, mirroring `ProjectMember`'s own coverage; service-layer test for ownership gating; `cargo test` in the affected crate(s).
- If outcome 2 (out of scope): none — the ticket closes on the recorded decision.
## Verification evidence
Filed from the phase E (`/settings/accounts`) build: implementing the owner-specified "Members" block on `/settings/accounts/` required reading `Account`'s full model definition and every procedure in `authz.cstack` for anything membership-shaped, which turned up `ProjectMember`/`listProjectRoster` (project-scoped) and the explicit "no membership/role concept" note on `Account` itself (ADR-0006) — confirmed by grep across the whole schema (`^model .*Member`, `^procedure `) rather than inferred. No code change proposed in this ticket; it documents the gap and asks for a product decision.
## Human accountable owner
@stephane-segning
## AI Usage Declaration
- Understanding code
- Drafting the ticket
## Human verification completed
Filed by an AI agent (Claude, converse-frontends IA v3 phase E task) on the accountable owner's
standing instruction to file this gap as discovered; the owner has not yet personally reviewed
this specific ticket text, so no human-verification box below is pre-checked on their behalf —
that is theirs to tick.
- [ ] I understood the intent
- [ ] I checked the source of truth
- [ ] I reviewed all AI-generated text/code
- [ ] I verified the implementation manually
- [ ] I verified the tests
- [ ] I checked for hallucinated assumptions
- [ ] I documented remaining risks
- [ ] I am the accountable owner and accept responsibility for this ticket.
Guida per i contributori
Apri la guida per i contributori
Valutazione
Questa issue non è ancora stata valutata.