mirror of
https://github.com/dtzp555-max/olp.git
synced 2026-07-19 09:45:07 +00:00
feat: D81 — dashboard-data quota_v2 shape + models-registry schema_version (Phase 5) (#53)
Authority citations (required per CLAUDE.md § Hard requirements):
1. ADR 0012 D81 — the D-day being implemented (Phase 5 charter, audit-query
+ dashboard-data extension row in § D-day table)
2. ADR 0013 Rule 5 — schema_version in models-registry.json mandate. D80
used a local constant QUOTA_SCHEMA_VERSION = '2026-05-26' (reviewer nit
#4 at PR #52). D81 folds in Rule 5 compliance: adds quota_probe.schema_version
to models-registry.json and has anthropic.mjs read from there with the
constant as fallback via _resolveSchemaVersion().
3. ADR 0008 — the audit-query design being amended (Amendment 1 added at D81
to docs/adr/0008-dashboard-and-audit-query.md documenting: quota_probe in
registry, aggregateProviderQuota() API shape, quota_v2 key, deprecation
timeline for legacy quota key).
4. D80 PR #52 (commit 82d2e1c) — producer of the quotaStatus() shape that
D81 normalizes. The 13-field anthropic-ratelimit-unified-* shape from
_parseRateLimitHeaders() is consumed by _normalizeAnthropicQuota() here.
Changes (A–G per D81 spec):
A. models-registry.json — new top-level quota_probe key
- quota_probe.schema_version = '2026-05-26'
- quota_probe.anthropic.{ source, endpoint, fields_pinned[13] }
- fields_pinned is load-bearing for drift detection (ADR 0013 Rule 5)
B. lib/providers/anthropic.mjs — _resolveSchemaVersion() helper
- Reads quota_probe.schema_version from modelsRegistryRaw at call time
- Falls back to QUOTA_SCHEMA_VERSION constant if registry field absent
- quotaStatus() now uses _resolveSchemaVersion() instead of raw constant
C. lib/audit-query.mjs — aggregateProviderQuota() export
- Normalizes per-provider quotaStatus() returns to ProviderQuotaEntry shape
- Live → { status:'live', utilization, reset, representative_claim, … }
- Stale → { status:'stale', … }
- null return → { status:'unavailable', reason:'no public quota api or probe disabled' }
- throw → { status:'unavailable', reason:<error.message> }
- getQuotaStatus injection point for full test isolation (D81 §F)
D. server.mjs handleManagementDashboardData — quota_v2 added
- Calls auditAggregateProviderQuota({ providers: loadedProviders })
- Both legacy quota (backwards compat) and quota_v2 in response
- Graceful degradation: quota_v2 failure → warn log + empty array, rest of
payload unaffected
E. server.mjs handleManagementQuota — quota_v2 added (mirrors D)
F. test-features.mjs — Suite 37 (7 new tests)
- 37a: live shape normalization (status=live, utilization, reset, etc.)
- 37b: stale shape (status=stale)
- 37c: null returns → unavailable entries
- 37d: throw path → unavailable with reason
- 37e: mixed providers (live + unavailable)
- 37f: getQuotaStatus injection verified
- 37g: models-registry.json has quota_probe.schema_version + 13 fields_pinned
G. docs/adr/0008-dashboard-and-audit-query.md — Amendment 1 added
Test delta: 720 → 727 (+7), 0 failures.
Live quota_v2 JSON sample (illustrative — probe is opt-in per ADR 0013 Rule 4;
real values require quota_probe_enabled:true + valid OAuth credentials):
GET /v0/management/dashboard-data (owner-only)
{
"quota_v2": [
{
"provider": "anthropic",
"status": "live",
"schema_version": "2026-05-26",
"last_fresh_at": 1748300000000,
"utilization": { "5h": 0.49, "7d": 0.31 },
"reset": { "5h": 1748290000, "7d": 1748500000, "overall": 1748300000, "overage": null },
"representative_claim": "five_hour",
"fallback_percentage": 0.5,
"overage": { "status": "rejected", "disabled_reason": "org_level_disabled_until" },
"raw_available": true
},
{
"provider": "openai",
"status": "unavailable",
"reason": "no public quota api or probe disabled",
"schema_version": null,
"last_fresh_at": null,
"utilization": null,
"reset": null,
"representative_claim": null,
"fallback_percentage": null,
"overage": null,
"raw_available": false
}
]
}
When probe is disabled (default), anthropic entry also returns unavailable:
{ "provider": "anthropic", "status": "unavailable",
"reason": "no public quota api or probe disabled", ... }
NOT modified: dashboard.html (D82), codex.mjs, mistral.mjs, any quotaStatus()
return shape from anthropic.mjs (D80 contract unchanged — normalization is in
the new audit-query layer only).
Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,93 @@
|
||||
|
||||
- **Date:** 2026-05-25
|
||||
- **Status:** Accepted (D48, design-only — implementation D-days D49–D54 follow; Phase 3 close = v0.3.0)
|
||||
|
||||
## Amendments
|
||||
|
||||
### Amendment 1 — 2026-05-26: D81 Phase 5 quota_v2 shape + aggregateProviderQuota()
|
||||
|
||||
**Scope:** D81 (Phase 5 / ADR 0012 D81) extends the audit-query layer and dashboard-data endpoint to surface the new per-provider quota shape introduced by D80 (`lib/providers/anthropic.mjs:quotaStatus()`). This amendment documents the three new interfaces.
|
||||
|
||||
#### 1. `models-registry.json` — new `quota_probe` top-level key
|
||||
|
||||
D81 adds a `quota_probe` key at the root of `models-registry.json` per ADR 0013 Rule 5 (schema_version in registry so downstream consumers can detect schema drift):
|
||||
|
||||
```json
|
||||
{
|
||||
"quota_probe": {
|
||||
"schema_version": "2026-05-26",
|
||||
"anthropic": {
|
||||
"source": "anthropic-ratelimit-unified-headers",
|
||||
"endpoint": "https://api.anthropic.com/v1/messages",
|
||||
"fields_pinned": [ ...13 field names... ]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`fields_pinned` is load-bearing: if Anthropic adds/renames a header in a future CLI version, dashboard consumers comparing field-presence against this list can flag "schema drift detected" per the ADR 0013 Rule 5 drift-detection runbook. This field must be updated alongside the parser whenever a drift event occurs.
|
||||
|
||||
`lib/providers/anthropic.mjs` reads `quota_probe.schema_version` from the registry at call time (via `_resolveSchemaVersion()`) with the module-level `QUOTA_SCHEMA_VERSION` constant as fallback. No hard dependency on the registry — the constant is the safety net.
|
||||
|
||||
#### 2. `lib/audit-query.mjs` — new `aggregateProviderQuota()` export
|
||||
|
||||
```js
|
||||
export async function aggregateProviderQuota({
|
||||
providers, // Map<name, plugin> or plain object
|
||||
getQuotaStatus, // optional injectable getter (name) => Promise<shape|null>
|
||||
}): Promise<Array<ProviderQuotaEntry>>
|
||||
```
|
||||
|
||||
For each provider, calls `quotaStatus()` (already cached at the plugin layer per ADR 0013 Rule 3) and normalizes to the `ProviderQuotaEntry` shape:
|
||||
|
||||
```js
|
||||
{
|
||||
provider: string,
|
||||
status: 'live' | 'stale' | 'unavailable',
|
||||
reason?: string, // only when status === 'unavailable'
|
||||
schema_version: string|null,
|
||||
last_fresh_at: number|null, // epoch-ms of last successful probe
|
||||
utilization: { '5h': number|null, '7d': number|null } | null,
|
||||
reset: {
|
||||
'5h': number|null, '7d': number|null,
|
||||
overall: number|null, overage: number|null,
|
||||
} | null,
|
||||
representative_claim: string|null,
|
||||
fallback_percentage: number|null,
|
||||
overage: { status: string|null, disabled_reason: string|null } | null,
|
||||
raw_available: boolean,
|
||||
}
|
||||
```
|
||||
|
||||
Providers returning `null` from `quotaStatus()` (codex, mistral — no public quota API; or probe disabled) produce `{ status: 'unavailable', reason: 'no public quota api or probe disabled', ...null fields }`.
|
||||
|
||||
Providers whose `quotaStatus()` throws produce `{ status: 'unavailable', reason: <error.message>, ...null fields }`.
|
||||
|
||||
This function does NOT scan ndjson files; it calls live provider plugins. It is audit-query-adjacent (normalized query shape for the dashboard layer) but not audit-derived. Query model remains Lane 2 = A (in-memory, no SQLite).
|
||||
|
||||
#### 3. `/v0/management/dashboard-data` and `/v0/management/quota` — new `quota_v2` field
|
||||
|
||||
Both endpoints now return TWO quota keys:
|
||||
|
||||
- **`quota`** (legacy, unchanged): `Array<{ provider, ...rawQuotaStatus, available }>`. Kept for backwards compatibility with the existing `dashboard.html` (D82 will switch consumers to `quota_v2`).
|
||||
- **`quota_v2`** (D81 new): `Array<ProviderQuotaEntry>` — the normalized shape from `aggregateProviderQuota()` above. This is what D82's enriched dashboard UI will consume.
|
||||
|
||||
Both fields are computed from the same underlying `quotaStatus()` call. The legacy `quota` key calls `quotaStatus()` independently from `quota_v2`; since the probe is cached at the plugin layer (ADR 0013 Rule 3), the double call incurs no extra API requests.
|
||||
|
||||
**Deprecation timeline:** the legacy `quota` key is deprecated as of D81. Target removal: v1.0.0 or when D82 completes the dashboard migration (whichever comes first). Removal requires a separate PR with a CHANGELOG entry.
|
||||
|
||||
#### 4. Failure handling
|
||||
|
||||
`aggregateProviderQuota()` never throws to the dashboard endpoint. Per-provider failures are absorbed as `{ status: 'unavailable', reason: <error> }` entries. If `aggregateProviderQuota()` itself throws (implementation bug), `handleManagementDashboardData` and `handleManagementQuota` catch the error, log `dashboard_data_quota_v2_failed` / `management_quota_v2_failed`, and return `quota_v2: []` so the rest of the payload is unaffected.
|
||||
|
||||
#### 5. Authority citations for this amendment
|
||||
|
||||
- **ADR 0012 D81** — the D-day this amendment documents.
|
||||
- **ADR 0013 Rule 5** — mandate for `quota_probe.schema_version` in `models-registry.json`.
|
||||
- **D80 PR #52 commit 82d2e1c** — the producer of the `quotaStatus()` shape this amendment normalizes.
|
||||
- **ADR 0008 Lane 2 = A** — query model unchanged; `aggregateProviderQuota()` does not scan ndjson.
|
||||
|
||||
---
|
||||
- **Authors:** project maintainer (with AI drafting assistance)
|
||||
- **Related:**
|
||||
- OLP v0.1 spec § 4.6 (Dashboard requirements — port from OCP with multi-provider support) and § 4.7 (observability endpoints)
|
||||
|
||||
Reference in New Issue
Block a user