ADORSYS-GIS / ADORSYS-GIS/lightbridge-authz

[Epic]: Dynamic budget refill — self-service tiers, grant ledger, and policy engine

Offen
#188 3 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen
epic governance
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

Beitragsleitfaden öffnen

Bewertung

Dieses Issue wurde noch nicht bewertet.

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.