mirror of
https://github.com/dtzp555-max/olp.git
synced 2026-07-21 21:15:10 +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:
+29
-2
@@ -71,11 +71,13 @@ import {
|
||||
} from './lib/keys.mjs';
|
||||
import { appendAuditEvent } from './lib/audit.mjs';
|
||||
// Phase 3 / D50 — management endpoints consume the audit aggregate query layer.
|
||||
// D81 (Phase 5) — adds aggregateProviderQuota for quota_v2 shape.
|
||||
import {
|
||||
aggregateRequests as auditAggregateRequests,
|
||||
topFallbackChains as auditTopFallbackChains,
|
||||
spendTrendDaily as auditSpendTrendDaily,
|
||||
cacheHitRateWindow as auditCacheHitRateWindow,
|
||||
aggregateProviderQuota as auditAggregateProviderQuota,
|
||||
} from './lib/audit-query.mjs';
|
||||
|
||||
// ── Config ────────────────────────────────────────────────────────────────
|
||||
@@ -2059,8 +2061,10 @@ async function handleDashboard(req, res) {
|
||||
async function handleManagementDashboardData(req, res) {
|
||||
return _runOwnerOnlyManagementEndpoint(req, res, 'GET', '/v0/management/dashboard-data',
|
||||
async (_req, res2, _identity, _auditCtx) => {
|
||||
// Quota panel: collect quotaStatus from each loaded provider; null on
|
||||
// Quota panel (legacy): collect quotaStatus from each loaded provider; null on
|
||||
// throw or null return → "unavailable" indicator.
|
||||
// DEPRECATED: kept for backwards compat with current dashboard.html (D82 will
|
||||
// switch consumers to quota_v2; legacy 'quota' key removed at v1.0.0 or earlier).
|
||||
const quota = [];
|
||||
for (const [name, provider] of loadedProviders) {
|
||||
try {
|
||||
@@ -2071,12 +2075,27 @@ async function handleManagementDashboardData(req, res) {
|
||||
}
|
||||
}
|
||||
|
||||
// quota_v2 (D81 / Phase 5): normalized per-provider quota shape for enriched
|
||||
// dashboard rendering. Built from aggregateProviderQuota() in lib/audit-query.mjs.
|
||||
// Each entry contains: provider, status, schema_version, last_fresh_at,
|
||||
// utilization, reset, representative_claim, fallback_percentage, overage, raw_available.
|
||||
// Providers with null quotaStatus() return { status: 'unavailable', reason: ... }.
|
||||
// Authority: ADR 0008 Amendment (D81) + ADR 0012 D81 + ADR 0013 Rule 5.
|
||||
let quota_v2 = [];
|
||||
try {
|
||||
quota_v2 = await auditAggregateProviderQuota({ providers: loadedProviders });
|
||||
} catch (err) {
|
||||
// Graceful degradation: quota_v2 is optional enrichment; don't fail entire payload.
|
||||
logEvent('warn', 'dashboard_data_quota_v2_failed', { error: err?.message ?? String(err) });
|
||||
}
|
||||
|
||||
const WINDOW_24H = 24 * 60 * 60 * 1000;
|
||||
const payload = {
|
||||
generated_at: new Date().toISOString(),
|
||||
window_24h: auditAggregateRequests({ windowMs: WINDOW_24H, logEvent }),
|
||||
cache_hit_24h: auditCacheHitRateWindow({ windowMs: WINDOW_24H, logEvent }),
|
||||
quota,
|
||||
quota_v2,
|
||||
spend_trend_30d: auditSpendTrendDaily({ days: 30, logEvent }),
|
||||
top_fallback_chains_24h: auditTopFallbackChains({ windowMs: WINDOW_24H, limit: 10, logEvent }),
|
||||
cache_stats: cacheStore.stats(),
|
||||
@@ -2093,6 +2112,7 @@ async function handleManagementDashboardData(req, res) {
|
||||
async function handleManagementQuota(req, res) {
|
||||
return _runOwnerOnlyManagementEndpoint(req, res, 'GET', '/v0/management/quota',
|
||||
async (_req, res2, _identity, _auditCtx) => {
|
||||
// Legacy quota array (backwards compat).
|
||||
const quota = [];
|
||||
for (const [name, provider] of loadedProviders) {
|
||||
try {
|
||||
@@ -2102,7 +2122,14 @@ async function handleManagementQuota(req, res) {
|
||||
quota.push({ provider: name, error: err?.message ?? String(err), available: null });
|
||||
}
|
||||
}
|
||||
sendJSON(res2, 200, { generated_at: new Date().toISOString(), quota });
|
||||
// quota_v2 (D81): normalized per-provider quota shape per ADR 0008 Amendment (D81).
|
||||
let quota_v2 = [];
|
||||
try {
|
||||
quota_v2 = await auditAggregateProviderQuota({ providers: loadedProviders });
|
||||
} catch (err) {
|
||||
logEvent('warn', 'management_quota_v2_failed', { error: err?.message ?? String(err) });
|
||||
}
|
||||
sendJSON(res2, 200, { generated_at: new Date().toISOString(), quota, quota_v2 });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user