ADORSYS-GIS / ADORSYS-GIS/lightbridge-authz
[Epic]: Dynamic budget refill — self-service tiers, grant ledger, and policy engine
- Vorherrschende Sprache
- Rust
- Sterne
- 0
- Forks
- 1
- Ø Merge
- 6 Std. 42 Min.
- Gemergte PRs (30 T.)
- 246
Beschreibung
### Executive Summary
We want users to be able to **get more AI budget without a human doing it by hand**, through a self-service refill flow backed by an immutable grant ledger and a policy engine that decides who may have what.
Today a user who exhausts their monthly budget is simply stopped until the next period, and the only remedy is a maintainer editing values. This epic makes budget a governed, auditable, self-service resource.
### Strategic Intent
Turn "I ran out of budget" from a support request into a policy decision the system can make, record immutably, and enforce at the gateway.
### Problem Statement
- **Users:** hitting the monthly budget means being blocked with no path forward except asking a maintainer. For a `free`-plan user that is $50/month and then a wall.
- **Developers / operations:** every refill today is a manual values edit — unlogged, unauditable, and touching the single most dangerous configuration surface in the platform (see the ADR-0084 note below).
- **Business / compliance:** there is no ledger. We cannot say who was granted extra budget, when, why, or by whom.
### Desired Outcome
- A user can request more budget through the self-service UI, and get it within policy limits without human involvement.
- Every grant is an immutable ledger entry: who, how much, why, under which policy version.
- Admins get a review queue for requests that exceed the unaided allowance.
- Policy is data, not a deploy — changing "how many rungs may a user climb unaided" does not ship a release.
- Arbitrary Rego is available for admins who need it, on the same decision contract as the simple evaluator.
### Scope (In / Out)
#### In Scope
- **Phase 1** — grant/request domain: ledger, balances, idempotency, replay tests.
- **Phase 2** — rule-data evaluator, behind an explicit decision contract.
- **Phase 3** — policy lifecycle: versioning, simulation endpoint.
- **Phase 2b** — OPA-Wasm as a *second* engine behind the same contract: bundle build/sign/verify, atomic hot-swap, last-known-good, eval timeout, revision in `/health`.
- **Phase 4** — refill workflows: user request, admin grant, review queue, expiry.
- **Phase 5** — automatic augmentation: deterministic trigger keys, worker, scheduled reconciliation.
- **Phase 6a** — the re-key: `x-budget-tier` ladder replaces the per-plan budget rules *(ai-helm)*.
- **Phase 6b** — grants write the tier attribute *(here + Keycloak)*.
- **Phase 7** — hardening: metrics, alerts, load tests.
#### Out of Scope
- **Arbitrary refill amounts.** Decision: refills are **discrete tiers** (option A), ladder `b-15 … b-1000`, roughly doubling. This is what removes the need for any new component in the request path — a decision that took this epic out of the red-risk tier entirely.
- **Refills for internal / API-key clients.** OIDC users only; internal clients get a different access model and keep plan-level budgets.
- **A new frontend.** Self-service lives in `lightbridge-ss` (converse-frontends) — which is an **Expo** app, not Vite.
- A dynamic budget limiter in the request path (option C). Not needed under option A.
### Two consequences of option A that are product decisions, not bugs
1. **A tier change resets the window's counter.** A refill therefore grants the *full new tier*, not the difference. This is the ADR-0084 incident mechanism — invoked deliberately, for one account at a time, as the intended semantic.
2. **The tier arrives as a Keycloak claim**, so a refill takes effect at the next token refresh, not instantly.
Both are accepted. They are recorded here because they will look like defects to anyone who meets them without context.
### ⚠️ The dangerous part, and how the schedule changed
The enforcement phase edits the **append-only** rate-limit rule list. ADR-0084 exists because the Lyft ratelimit service keys each counter on the rule's **position** in the rendered list, and adding a plan to a Helm *map* sorted it to index 0 mid-window, orphaning every account's accumulated spend. That was a live incident, confirmed by `SCAN`.
So phase 6a wants to land on a period boundary, when counters reset anyway.
> **⚠️ AMENDED 2026-08-01 — the original schedule is void.** This epic was planned to ship 6a **today**, four days before a 2026-08-05 window boundary that would "absorb the reset." Two ADRs landed in the meantime:
> - **ai-helm ADR-0111** folded a calendar `YYYY-MM` `x-billing-period` marker into the key.
> - **ai-helm ADR-0112** then set `unit: Year`, making that marker the **only** rotation — because ADR-0111 alone left the 30-day epoch in the key too, so counters were rotating twice and every account was silently getting a spurious extra budget ~12×/year.
>
> **The 2026-08-05 boundary no longer exists.** Shipping 6a on the original rationale would have orphaned every account's August spend with no compensating reset until 2026-09-01.
>
> **Revised: 6a targets a calendar month boundary — 2026-09-01 or any later 1st.** It keeps its independence from phases 1–5 and may still be pulled forward; it is simply no longer date-forced. Nothing else in this epic has a schedule constraint.
### Source of truth (links)
- Roadmap: ADORSYS-GIS/ai-helm#872, §4 and the decision table
- Full plan: `plans/lightbridge-dynamic-budget.md` in that PR — §0.1 (option A consequences), §0.2 (the ladder), §0.3–§0.4 (the amended schedule)
- The incident that governs the enforcement phase: ai-helm ADR-0084
- The mechanism being reused: ai-helm ADR-0110 (project quota tiers, append-only)
- The ADRs that changed the schedule: ai-helm ADR-0111, ADR-0112
- Adjacent, not duplicate: ADORSYS-GIS/ai-helm#531 — that epic delegates *quota tiers* to project leads (`x-quota-tier`); this one is *budget refill* (`x-budget-tier`). Separate rule families, deliberately appended after the quota-tier rules so no existing index shifts.
### Stakeholders
- Product Owner / Technical Lead: @stephane-segning
### Key Assumptions
1. **The ladder is append-only forever.** `b-2000` may be added; no rung may ever be reordered or removed. Same rule as ADR-0084/0110.
2. **Two unaided rungs per period** is the starting policy — a free user reaches $60, then needs a human. Policy is rule data, so it changes without a deploy.
3. **Keycloak roles ride a claim**, and Lightbridge maps claim values → the internal `budget:*` permission list via `config.yaml`. Unknown values fail closed to `default`.
4. `x-budget-tier` is a **separate** header from ADR-0110's `x-quota-tier`. ⚠️ Worth a second opinion before the ADR — two rule families keyed on the same account is a real cost, and collapsing them is the alternative.
### Constraints
- Append-only rule lists. No reorder, no removal, ever.
- Phase 6a lands on a calendar month boundary.
- OIDC users only.
- Rust, cratestack for persistence, `-D warnings`.
### Risks
| Risk | Probability | Impact | Mitigation |
|---|---|---|---|
| **Rule reorder orphans fleet-wide spend (ADR-0084 repeat)** | Low | 🔴 Critical | Append-only, `fail`-guarded template, one PR / one reviewer / one ADR, land on a boundary |
| **The redis-exporter / dashboard co-change is forgotten → quota board goes blank** | **High** | Medium | Same PR, explicitly. ⚠️ Must be written against the **`billing_period`** label that #866 shipped — re-introducing a `window` label is now a regression |
| Users confused that a refill resets rather than adds | High | Low | Document; it is the intended semantic |
| Refill does not take effect until token refresh | **Certain** | Low | Document; consider surfacing "pending until re-login" in the UI |
| OPA-Wasm bundle supply chain | Medium | High | Sign and verify bundles, last-known-good, eval timeout |
### Success metrics
| Metric | Current | Target | Source |
|---|---|---|---|
| Manual values edits per refill | 1 | **0** | git log on ai-helm-values |
| Grants without an audit record | all | **0** | ledger |
| Time from user request to granted budget (within policy) | hours–days | < 1 min + token refresh | workflow metrics |
| Policy changes requiring a deploy | all | 0 | rule-data versioning |
| Accounts whose spend is orphaned by the 6a migration | n/a | **0** | `SCAN` before/after |
### Child User Stories
- Phase 1 — grant ledger, balances, idempotency, replay tests
- Phase 2 — rule-data evaluator behind the decision contract
- Phase 3 — policy lifecycle, versioning, simulation
- Phase 2b — OPA-Wasm engine on the same contract
- Phase 4 — refill workflows + admin review queue
- Phase 5 — automatic augmentation
- Phase 6a — the tier re-key *(ai-helm, boundary-scheduled)*
- Phase 6b — grants write the tier attribute
- Phase 7 — hardening
### Human accountable owner
@stephane-segning
### AI Usage Declaration
Drafting, Research, Ticket decomposition, Technical proposal
### Human verification completed
- [x] Intent checked against source of truth
- [x] Risks reviewed
- [x] I am the accountable owner and accept responsibility for this epic.
Beitragsleitfaden
Bewertung
Dieses Issue wurde noch nicht bewertet.