mirror of
https://github.com/dtzp555-max/olp.git
synced 2026-07-21 21:15:10 +00:00
ee4d9459aae50d9f0f0dfb739fa22b0969415e79
2
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
251b578114 |
feat+test+docs: D51 — dashboard.html full multi-panel UI (Phase 3) (#28)
* feat+test+docs: D51 — dashboard.html full multi-panel UI (Phase 3)
Fourth Phase 3 D-day. Replaces the D50 dashboard.html placeholder with
the full 4-panel UI per ADR 0008 § 6. Vanilla HTML + JS + fetch — no
build step, no framework, no CDN (Lane 1 = A). 30s page poll with
document.visibilityState pause/resume (Lane 4 = A).
4 PANELS (all rendered from /v0/management/dashboard-data — single
backing endpoint per Lane 2 in-memory query model):
Panel 1 — Per-provider quota
Table: { Provider | Available | Status }. Null available → "n/a"
pill; provider.quotaStatus() error → red status pill (graceful
degradation per ADR § 9).
Panel 2 — Last 24h: request count + cache hit + fallback rate
Per-provider row: { Requests | Cache hit % | Fallback rate % }.
Cache hit from cache_hit_24h.by_provider[p].hit_rate; fallback
rate computed from window_24h.by_provider[p].fallback_count/count.
Panel 3 — Request count last 30 days (SVG sparkline)
Vanilla SVG bar chart with <title> tooltips showing per-day per-
provider breakdown. Y-axis: requests per day (max-scaled). X-axis:
30 daily UTC buckets.
Panel 4 — Top fallback chains (last 24h)
Numbered table: { # | Chain (monospace, arrow-joined) | Count |
First seen | Last seen }.
POLL + VISIBILITYCHANGE PAUSE (ADR 0008 § 6.5):
- setInterval(refresh, 30000) after initial fetch.
- document.addEventListener('visibilitychange') → stopPolling() on
hidden / refresh()+startPolling() on visible.
- Prevents 2880 background polls/day per owner when tab hidden.
ERROR HANDLING:
- 401 from dashboard-data → in-page error banner explains owner-tier
requirement + suggests SSH-tunnel + header-injection workaround.
- Other HTTP errors → generic "HTTP <code>" banner; console.warn
for operator debugging.
- Per-panel empty states ("Loading…", "No requests in window.",
"No fallback chains triggered.").
CRITICAL CORRECTNESS INVARIANTS (ADR 0008 § 6 + Lane 1 = A):
- No <script src> — entire JS inline (Suite 25d asserts).
- No <link rel="stylesheet" href> — all CSS in <style> (25d).
- Only one backing endpoint hit: /v0/management/dashboard-data
(Suite 25e asserts).
- 401 path keeps panels in last-good state rather than clearing —
operator sees the error banner + can debug.
TESTS — Suite 25, +6 (582 → 588):
25a: owner /dashboard response contains all 4 panel container IDs
25b: JS declares POLL_INTERVAL_MS = 30000 + setInterval/clearInterval
25c: visibilitychange listener + document.visibilityState check
25d: NO external script src / NO external stylesheet href (Lane 1 = A
pinning)
25e: dashboard JS fetches /v0/management/dashboard-data only
25f: 401 in-page error banner mentions owner-tier guidance
MANUAL SMOKE (ADR 0008 § 10 #12):
Dashboard renders without console errors in a real browser when
served by a running OLP instance + owner-tier Bearer via SSH-tunnel
+ header-injection extension. Not automated at Phase 3.
DOCUMENTATION:
- AGENTS.md: dashboard.html marker promoted 🟡 D50 placeholder → ✅
D51 full UI.
- CHANGELOG.md: D51 entry under Unreleased per release_kit overlay.
NOT IN D51:
- Daily audit rotation (D52)
- tried_providers schema fix (D53; D45 P2 deferral)
- Phase 3 close (D55; v0.3.0; maintainer-triggered)
Test count: 582 → 588 (+6). Verified locally via npm test.
AUTHORITY:
- ADR 0008 § 6 (panels + refresh + localhost) + § 6.5 (poll +
visibilityState pause) + Lane 1 = A (no build step) + Lane 4 = A
(30s poll) + Lane 5 = B (full 4-panel scope).
- ADR 0008 § 9 (graceful degradation surfaced in Panel 1).
- ADR 0008 § 10 #12 (HTML smoke criterion satisfied at server-side
level).
- CLAUDE.md release_kit overlay phase_rolling_mode — under
Unreleased.
- Standing autopilot grant.
ALIGNMENT.md scope check: this PR replaces an existing entry-surface
static file (dashboard.html). Per Rule 5: management surface, not
OpenAI-spec-compatible — outside /v1/* spec scope. No code change in
server.mjs / lib/ / providers / IR / models-registry.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix: D51 fold-in — dashboard.html cosmetic polish (opus P3)
Fresh-context opus reviewer (PR #28) flagged 4 P3 cosmetic findings;
addressing the 2 trivial ones inline. The other 2 (visibilitychange
race + defensive date null-check) are negligible at family-scale per
reviewer; deferred to Phase 4 if UX feedback warrants.
- Line ~193: deleted orphan empty <text> SVG element (no textContent,
rendered nothing — debris from initial pass).
- Line ~200 comment: was "Date labels (first / mid / last)" but only
first + last rendered. Tightened to clarify intent + note mid label
deferred to Phase 4 if needed.
No behavior change. 588/588 tests pass.
Authority: PR #28 fresh-context opus reviewer P3 findings (2 of 4
addressed; remaining 2 documented as negligible).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
f9f2eaa059 |
feat+test+docs: D50 — server.mjs management endpoints (Phase 3 dashboard wire-up) (#27)
* feat+test+docs: D50 — server.mjs management endpoints (Phase 3 dashboard wire-up)
Third Phase 3 D-day. Wires the D49 lib/audit-query.mjs aggregate query
layer into 4 owner_only_block HTTP endpoints per ADR 0008 §§ 7-8.
Ships a placeholder dashboard.html at repo root (D51 lands the full
multi-panel UI). All endpoints follow the Phase 2 / D45 auth + audit
+ touchLastUsed pattern.
4 NEW ENDPOINTS — all owner_only_block per ADR 0008 § 8:
GET /dashboard
Serves dashboard.html (text/html). D50 stub explains state +
lists backing endpoints. D51 replaces with full UI.
GET /v0/management/dashboard-data
Full aggregate per § 7.2:
{ generated_at, window_24h, cache_hit_24h, quota,
spend_trend_30d, top_fallback_chains_24h, cache_stats }
GET /v0/management/quota
Quota subset only (per-provider provider.quotaStatus + error
capture per § 9 graceful degradation).
GET /cache/stats
Live in-memory cacheStore.stats() with generated_at wrapper.
HELPER:
_runOwnerOnlyManagementEndpoint(req, res, method, path, inner)
Factors common auth + audit ctx + owner-block + res.on('finish')
wire. inner is async (req, res, olpIdentity, auditCtx) → void.
Eliminates 4× boilerplate.
OWNER_ONLY_BLOCK MODE (ADR 0008 § 8 D48-fold-in):
authenticate → if owner_tier !== 'owner' → 401 owner_required.
Distinct from owner_only_trim (Phase 2 /health pattern). Anonymous
identity (when allow_anonymous: true) REACHES the handler and is
401'd by the owner check (Suite 24c). Allow_anonymous: false + no
header → 401 auth_required at middleware (Suite 24d).
PROVIDER QUOTASTATUS ERROR CAPTURE:
Dashboard-data + quota endpoints catch per-provider throws and
surface { provider, error, available: null } so one bad provider
doesn't fail the whole panel (ADR 0008 § 9 graceful degradation).
DASHBOARD.HTML PLACEHOLDER (~50 lines at repo root):
Explains D50 state, lists backing endpoints with curl example.
Cached in memory at first /dashboard request via _loadDashboardHtml
with module-scope _dashboardHtmlCache; falls back to in-memory stub
if file missing (defensive for test imports from non-repo cwd).
AUDIT ON MANAGEMENT ENDPOINTS (ADR 0008 § 7.5):
Every management request appends audit row including 401 paths
(verified by Suite 24j). Touch wire skips anonymous + env-owner
identities (matches Phase 2 pattern).
TESTS — Suite 24, +11 (571 → 582):
24a-d: /dashboard owner_only_block matrix (owner 200 / guest 401
/ anonymous-with-allow_anonymous=true 401 / no-auth-with-
allow_anonymous=false 401)
24e: dashboard-data owner → 200 JSON with all § 7.2 fields
(asserts spend_trend_30d.length === 30)
24f: dashboard-data guest → 401 owner_required
24g: quota owner → 200 JSON with quota array
24h: cache/stats owner → 200 JSON shape
24h-401: cache/stats guest → 401
24i: successful dashboard-data appends audit row with status 200
+ key_id + correct path
24j: 401 (guest blocked) dashboard-data appends audit row with
error_code: 'owner_required' + owner_tier: 'guest'
DOCUMENTATION:
- AGENTS.md: dashboard.html new entry (D50 placeholder); lib/audit-
query.mjs marker note unchanged.
- CHANGELOG.md: D50 entry under Unreleased per release_kit overlay.
NOT IN D50 scope:
- Full dashboard UI (D51 — replaces dashboard.html with the real
4-panel layout + 30s poll JS)
- Daily audit rotation (D52)
- tried_providers schema fix (D53)
- Phase 3 close (D55; v0.3.0; maintainer-triggered)
Test count: 571 → 582 (+11). Verified locally via npm test.
AUTHORITY:
- ADR 0008 § 7 (endpoint definitions) + § 8 (owner_only_block
mode) + § 9 (graceful degradation) + § 7.5 (audit on management
endpoints).
- ADR 0007 § 7 (auth model reused).
- ADR 0002 § Provider contract (quotaStatus).
- ADR 0005 (cacheStore.stats source of truth).
- CLAUDE.md release_kit overlay phase_rolling_mode — under
Unreleased.
- Standing autopilot grant.
ALIGNMENT.md scope check: this PR adds 4 new entry-surface endpoints
under owner-only_block gating + a new lib/audit-query consumer surface.
Per Rule 5: management endpoints are owner-only operational surface,
not OpenAI-spec-compatible — they exist outside the /v1/chat/completions
+ /v1/models spec scope. No provider plugin / IR change.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* docs: D50 fold-in — AGENTS.md dashboard.html duplicate (opus P3)
Fresh-context opus reviewer (PR #27) flagged a duplicate dashboard.html
entry: my D50 addition was added directly above a stale
"Planned (Phase 6) — not yet authored" line that should have been
removed. The file contradicted itself.
Fix: merge into single entry — keep the original line phrasing and
attach the D50 status update.
No code change, no test change.
Authority: PR #27 fresh-context opus reviewer P3 finding.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
|