ADORSYS-GIS / ADORSYS-GIS/lightbridge-authz
[Story]: Phase 4 — self-service refill requests and admin review queue
- Lingua principale
- Rust
- Stelle
- 0
- Fork
- 1
- Merge medio
- 6h 42m
- PR unite (30g)
- 246
Descrizione
### Story Statement
As a **user who has run out of AI budget**,
I want to **request more budget from the self-service UI and get it immediately when policy allows**,
so that **I am not blocked from working, and nobody has to hand-edit configuration on my behalf.**
### Real Intent
This is the story the whole epic exists for. Everything before it — ledger, policy engine, lifecycle — is plumbing; this is where a user notices anything changed.
It also removes the single worst operational pattern in the current system: a maintainer editing rate-limit values by hand to unblock someone. That is unlogged, unauditable, and it touches the configuration surface that ADR-0084 exists because of.
### Background and Context
Today a user who exhausts their monthly budget is stopped until the period rolls, and the only remedy is a support request that ends in a values edit.
⚠️ **The self-service UI already exists: `lightbridge-ss` / converse-frontends. It is an Expo app, not Vite.** No new frontend is needed, and proposing one would be a mistake worth catching in review.
### Source of truth (links)
- Epic: #188
- Depends on: #189 (ledger), #190 (policy engine)
- Roadmap: ADORSYS-GIS/ai-helm#872 decision 6 ("all clients get refills via the self-service (converse-ui)") and §4 Wave 3
- Plan detail: `plans/lightbridge-dynamic-budget.md` §4 Phase 4, and §0.1 for the two consequences users will meet
### ⚠️ Two behaviours that will look like bugs and are not
Both need to be *designed for in the UI*, not just documented:
1. **A refill resets the window's counter.** Moving up a rung grants the **full new tier**, not the difference. A user who has spent $40 of $50 and moves to a $100 rung has $100, not $110 — and their $40 of history no longer counts against them.
2. **The tier arrives as a Keycloak claim, so it takes effect at the next token refresh** — not instantly. The UI must say so, or every refill generates a "it didn't work" report.
### Acceptance Criteria
#### Functional
- [ ] Given a user with budget remaining under the unaided allowance, when they request the next rung, then it is granted immediately and recorded in the ledger.
- [ ] Given a user who has used their unaided allowance for the period, when they request again, then the request goes to a **review queue** rather than being granted or silently refused.
- [ ] Given a queued request, when an admin approves it, then the grant is issued and the user is informed.
- [ ] Given a queued request, when an admin rejects it, then the user is informed **with a reason**.
- [ ] Given a granted refill, when the user views their budget, then the UI states clearly that it applies **after the next token refresh**.
#### Negative / Edge Cases
- [ ] Given an **internal or API-key client**, when a refill is attempted, then it is refused. Refills are **OIDC users only** — internal clients have a different access model.
- [ ] Given a user who is already on the top rung, when they request more, then they get a clear message rather than a failed grant.
- [ ] Given a user submitting the same request twice (double-click, retry), when both arrive, then exactly one grant results.
- [ ] Given the policy engine is unavailable, when a request is made, then it is **refused and queued**, not granted.
#### Non-Functional
- [ ] Every path through this story writes a ledger entry — including refusals and rejections. An unrecorded decision is the thing this epic exists to eliminate.
- [ ] The reset-not-add semantic and the token-refresh delay are both surfaced **in the UI**, not only in docs.
- [ ] `cargo clippy --all-targets --all-features -- -D warnings` clean.
### Out of Scope
- **Automatic augmentation** — Phase 5, its own story.
- The enforcement re-key (Phase 6a) and writing the Keycloak attribute (Phase 6b). Until those land, this story records intent without changing what the gateway enforces — which is fine, and worth stating so nobody reports it as broken.
- Refills for internal clients.
- A new frontend.
### Dependencies and Blockers
- Depends on #189, #190.
- Needs UI work in `lightbridge-ss` (converse-frontends) — coordinate; that is a different repo.
- ⚠️ **The refill has no gateway effect until Phase 6a/6b.** Sequence the user-facing launch accordingly, or the first user to try it will correctly report that nothing happened.
### Assumptions
1. **Discrete rungs are acceptable UX.** "Request the next tier up" rather than "request $5". This was the decision that took the epic out of the red-risk tier.
2. Two unaided rungs per period to start; policy, so it changes without a deploy.
3. Admins will action the review queue promptly enough that it is not a bottleneck. If not, that is a policy problem, not an engineering one.
4. Users tolerate the token-refresh delay **if it is stated up front**.
### Implementation Notes
Guidance, not gospel:
- Say the awkward things in the UI. "You have $100 for the rest of this period" is honest; showing a running total that silently reset is not.
- Make the review queue's rejection reason mandatory. A rejection without a reason turns into a support conversation.
- Idempotency keys from the client; the ledger already enforces exactly-once.
- Refused-because-unavailable and refused-because-policy must be distinguishable to an admin, even if the user sees one message.
### Test Expectations
- Unit: allowance accounting, rung transitions.
- Integration: request → grant → ledger entry; request → queue → approve → grant; request → queue → reject → informed.
- Negative: internal client refused; top rung; double-submit; policy engine down.
- Manual: the UI copy for the reset semantic and the refresh delay actually appears, and is understandable to someone who has not read this ticket.
### Verification evidence
*To be completed on delivery.*
Required:
- `just all-checks` output.
- Integration output for the grant path and both review-queue paths.
- Screenshots of the UI copy explaining the reset semantic and the token-refresh delay.
- The negative tests, named.
### Human accountable owner
@stephane-segning
### AI Usage Declaration
Drafting this story, Refining acceptance criteria
### Human verification completed
- [x] I checked the story against the source of truth
- [x] I confirmed the acceptance criteria
- [x] I documented remaining risks
- [x] I am the accountable owner and accept responsibility for this story.
Guida per i contributori
Apri la guida per i contributori
Valutazione
Questa issue non è ancora stata valutata.