feat: panel exposure policy — rich stats, opt-in per deployment #15

Merged
synapse merged 1 commit from feat/panels into develop 2026-08-21 23:01:49 +00:00
Owner

Implements the panels OpenSpec change — the exposure-policy layer, per operator decisions (opt-in default, policy-as-config, apex blocked on this).

What's public is now policy, not capability: 9 panels implemented (summary, pages + 7 ranked: referrers, browsers, systems, sizes, locations, languages, campaigns — uniform items:[{name,count}] shape, normalized names, empty-is-valid); GCSG_PANELS/module panels option selects what a deployment answers, defaulting to the minimal two. Disabled → 404 {"error":"panel_disabled"} (distinguishable three ways: enabled / policy-disabled / nonexistent). Served /api/v1/openapi.json is filtered to the enabled set — per-deployment contract truth — while the committed snapshot remains the full capability document, still build-gated. /api/v1/panels = cheap discovery.

The widget got its superpower: <goatcounter-stats> discovers the policy and renders every enabled panel — flip a panel on server-side, every embedding page follows with zero frontend deploys. Optional panels attribute restricts page-side; empty panels skip silently; partial failures never kill the widget. Old gateways without /panels fall back to the classic two.

Lane compliance: hurl scenarios written red-first (7/7 green live and in the VM e2e with the canonical expanded policy); 28 rust + 27 bun tests; contract coupling now asserts the panel enum from both sides; startup info-logs the policy; disabled-panel demand is counted in http_requests_total; existing metric families extend via labels (no new signal types). Live run bonus: the dev GoatCounter's own 4/s limit produced a real 429 → classified http_4xx, no retry, exactly as specced.

Ships as v0.3.0 (separate bump PR after merge, as usual).

🤖 Generated with Claude Code

https://claude.ai/code/session_01T5j3dAJVcUjZv9k76sRTt9

Implements the `panels` OpenSpec change — the exposure-policy layer, per operator decisions (opt-in default, policy-as-config, apex blocked on this). **What's public is now policy, not capability**: 9 panels implemented (summary, pages + 7 ranked: referrers, browsers, systems, sizes, locations, languages, campaigns — uniform `items:[{name,count}]` shape, normalized names, empty-is-valid); `GCSG_PANELS`/module `panels` option selects what a deployment answers, defaulting to the minimal two. Disabled → `404 {"error":"panel_disabled"}` (distinguishable three ways: enabled / policy-disabled / nonexistent). Served `/api/v1/openapi.json` is filtered to the enabled set — per-deployment contract truth — while the committed snapshot remains the full capability document, still build-gated. `/api/v1/panels` = cheap discovery. **The widget got its superpower**: `<goatcounter-stats>` discovers the policy and renders every enabled panel — flip a panel on server-side, every embedding page follows with zero frontend deploys. Optional `panels` attribute restricts page-side; empty panels skip silently; partial failures never kill the widget. Old gateways without `/panels` fall back to the classic two. **Lane compliance**: hurl scenarios written red-first (7/7 green live and in the VM e2e with the canonical expanded policy); 28 rust + 27 bun tests; contract coupling now asserts the panel enum from both sides; startup info-logs the policy; disabled-panel demand is counted in http_requests_total; existing metric families extend via labels (no new signal types). Live run bonus: the dev GoatCounter's own 4/s limit produced a real 429 → classified `http_4xx`, no retry, exactly as specced. Ships as v0.3.0 (separate bump PR after merge, as usual). 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01T5j3dAJVcUjZv9k76sRTt9
feat: panel exposure policy — rich stats, opt-in per deployment
All checks were successful
ci / gate (pull_request) Successful in 21s
7b5b375040
The Panel enum is the single spine: 9 panels (7 new ranked ones sharing
a uniform normalized shape), GCSG_PANELS policy config (opt-in, minimal
default, fail-fast on typos), panel_disabled answers distinguishable
from bare 404s, served openapi.json filtered to the enabled set while
the committed snapshot stays the full capability document, /api/v1/panels
discovery. TS client: fetchPanels/fetchPanel + panel_disabled code;
<goatcounter-stats> is now self-configuring (renders the enabled set,
page-side panels attribute restricts further, empty panels skipped,
partial failures never kill the widget). Module: typed panels option.

28 rust tests, 27 bun tests (contract coupling extended to the panel
enum), 7/7 hurl files live incl. the disabled-panel and filtered-contract
scenarios; VM e2e runs the expanded policy. Live run also demonstrated
the 429-classification path against the dev instance's own rate limit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T5j3dAJVcUjZv9k76sRTt9
synapse merged commit 384b9016dc into develop 2026-08-21 23:01:49 +00:00
synapse deleted branch feat/panels 2026-08-21 23:01:49 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
synapse/goatcounter-stats-gateway!15
No description provided.