ADORSYS-GIS / ADORSYS-GIS/lightbridge-governance

[Story]: Total AI spend per engineer across all four sources

Open
#36 0 comments 0 reactions 1 assignee Claimed by @Motouom View on GitHub
governance user-story
Dominant language
Rust
Stars
1
Forks
2
Avg merge
13h 13m
Merged PRs (30d)
110

Description

### Story Statement

As an **engineering manager**,
I want **one view of total AI spend per engineer across the gateway, Copilot, Claude Code and Codex**,
so that **I can answer "what does this person's AI usage cost" without stitching four systems together by hand.**

### Real Intent

This is the payoff, and it is the reason every other story insists on integer micro-USD and a Keycloak `sub`. Four sources that each measure honestly but cannot be added together are four dashboards nobody opens.

It is also where partial coverage becomes dangerous. A per-engineer total that silently omits a source reads as authoritative and is wrong in the direction that matters — too low.

### Background and Context

The platform already reads Postgres directly from Grafana (ai-helm ADR-0063, `uid: keycloak`), which is what lets this join on columns instead of fighting metric cardinality. Gateway spend is in Mimir keyed on `user_id`; Copilot, Claude Code and Codex land in the governance store keyed on the Keycloak `sub`.

### Source of truth (links)

- Epic: #30
- Depends on: #32, #33, #35 (the data), ai-helm#879 (the Grafana Postgres datasource)
- Pattern precedent: ai-helm ADR-0063, `docs/patterns/per-user-observability.md`
- Related: the Copilot dashboards, ai-helm#880

### Acceptance Criteria

#### Functional
- [ ] Given a developer, when the view loads, then their spend is shown **broken out by source** and totalled.
- [ ] Given all four sources, when totalled, then they are in the same unit and the total is arithmetically the sum — no conversion at query time.
- [ ] Given a time range, when it changes, then every source respects it consistently. ⚠️ Gateway spend is a live counter; the connectors are daily/near-real-time. A range shorter than a connector's granularity must not silently show zero for it.

#### Negative / Edge Cases
- [ ] ⚠️ Given a source has **no data** for a developer, when the view loads, then it renders as **"no data"**, distinct from **"zero spend"**. These mean opposite things — one is "they didn't use it", the other is "we can't see it" — and a total that conflates them is worse than no total.
- [ ] Given a developer appears in one source and not others (no ingest token, no Copilot seat), when the view loads, then that is **visible as a coverage gap**, not rendered as thrift.
- [ ] Given spend that could not be attributed to any person, when the view loads, then it appears in an **unattributed** bucket rather than being dropped. Unattributed spend is a finding, not a rounding error.

#### Non-Functional
- [ ] Reads the governance Postgres datasource for connector data; Mimir only for gateway spend.
- [ ] Dashboard source lives in `tools/dashboards/` (ai-helm); ⚠️ `uv run dashboards build` must be run and the regenerated JSON committed or `dashboards-drift` CI fails.
- [ ] ⚠️ Every `GrafanaFolder` sets `resyncPeriod` — Grafana runs stateless here, so a pod roll wipes operator-pushed folders and `folderRef` dashboards then fail `[400] folder not found` until the operator restarts.

### Out of Scope

- Any new collection. This story only joins what #32/#33 and the Copilot connector already store.
- Budget enforcement or alerting on spend. Visibility first.
- Per-team or per-cost-centre rollups — a later story once per-person is trusted.

### Dependencies and Blockers

- Depends on #32, #33, #35 and ai-helm#879.
- ⚠️ Genuinely blocked on real data. Building this against seeded data risks a view that looks right and is wrong; sequence it after at least one source is live.

### Assumptions

1. **All four sources resolve to the same Keycloak `sub`.** That is what #35 exists to guarantee for the two new ones; the gateway and Copilot already do.
2. **Integer micro-USD throughout** (ADR-0008), so the sum needs no conversion.
3. Grafana can reach the governance database (ai-helm#879). ⚠️ Needs its Cilium egress — every Postgres datasource here has hit that.

### Implementation Notes

- Make coverage a first-class element, not a footnote. A small "sources reporting for this developer: 3 of 4" beats a confident wrong total.
- `joinByField` on the identity key, the way the ADR-0063 user-directory dashboard does.
- Prefer an outer join so a developer missing from one source still appears, with that source blank rather than the row vanishing.

### Test Expectations

- Manual against real data, per source and combined.
- A developer present in only one source renders correctly.
- Unattributed spend appears in its bucket.
- Roll the Grafana pod and confirm folders and dashboards survive.

### Verification evidence

*To be completed on delivery.* Required: screenshots with real data showing the four-source breakdown, a partial-coverage developer, and the unattributed bucket; `uv run dashboards check` clean; evidence the dashboard survives a Grafana pod roll.

### 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.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.