Release v2.3.0 — brand-first quota widget + Gate Bar 0.1#217
Merged
github-actions[bot] merged 12 commits intomainfrom Apr 19, 2026
Merged
Release v2.3.0 — brand-first quota widget + Gate Bar 0.1#217github-actions[bot] merged 12 commits intomainfrom
github-actions[bot] merged 12 commits intomainfrom
Conversation
Reworks the /api/quotas surface so the dashboard widget matches how CodexBar presents provider state: - QuotaStatus gains a provider_group field; the widget groups packages under one provider card and subdivides by package inside it - daily / rolling_window statuses accept extra_provider_ids so a shared quota (e.g. Google AI Studio free tier covers both gemini-flash and gemini-flash-lite) is counted across all router IDs that draw from it - /api/quotas applies a credential gate: packages tagged with _requires_credential are hidden when the env var is missing / placeholder, or when the OAuth subject is not in the local token store. Skipped entries are reported back under skipped_packages so operators can see what's dormant and why. - dashboard widget renders one card per provider_group with all its packages stacked (CodexBar-style), plus a small "hidden" callout listing credential-gated packages that aren't active yet
Phase A of the Gate Bar & Quota Widget redesign (docs/GATE-BAR-DESIGN.md):
operators now see Claude / Codex / Gemini / Kilo Code — the products they
actually use — instead of company keys like anthropic/openai/kilocode. The
router keeps reading provider_group/_id so scoring and lane logic are
untouched.
QuotaStatus gains four fields:
- brand / brand_slug — operator-facing product name + URL-safe slug used
by /dashboard/quotas/<brand_slug> and Cockpit deep links
- pace_delta / elapsed_ratio — linear-pace indicator for rolling_window
and daily packages; credits packages leave it None and lean on
projected_days_left
- identity — credential shape (API key vs OAuth) so the widget can render
"Pro · OAuth" / "API · env ANTHROPIC_API_KEY" lines per brand
A fallback brand table covers pre-v1.3 catalogs, so nothing breaks if the
shared catalog lags. /api/quotas now also emits catalog_suggestions: brands
in the catalog that aren't active locally, ready to feed the widget's
upcoming "Available to add" mini-block (each carrying the authored
catalog_tagline in tier · price · quota shape grammar).
Coverage: 11 new tests in test_quota_tracker_brand.py pin the contract for
brand fallback, slug kebab-casing, identity derivation, pace math on
rolling/daily windows, and the to_dict() shape the API relies on. Full
suite: 395 passed.
Deferred to Phase B: the widget refresh that consumes these fields.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Phase B.1-B.3 of the v2.3 Gate Bar companion rollout. The quotas widget now groups by ``brand_slug`` (Claude, Codex, DeepSeek, ...) instead of ``provider_group``, surfaces the identity line (API key vs OAuth) per brand card, and renders a pace tick on rolling-window + daily bars so operators can see at a glance whether they're burning faster than the window elapses. - Responsive 1/2/3-column grid sorts brands by worst alert first. - Each brand card links to ``/dashboard/quotas/<slug>`` (Details, Phase B.4) and out to the Operator Cockpit via ``FAIGATE_COCKPIT_URL`` (default ``https://cockpit.fusionaize.ai``, strip trailing slash). - New ``_cockpit_base_url()`` helper + ``__COCKPIT_URL__`` placeholder replaced at render time so deep links compose cleanly. - Catalog mini-block renders ``catalog_suggestions`` (max 6, +N more) with an "Add in Cockpit ↗" CTA — discovery without write paths in the widget itself. - Skipped block labels by brand name, keeping the missing-credential story readable. See docs/GATE-BAR-DESIGN.md §3 for the Design-Thinking rationale. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Phase B.4 of the v2.3 Gate Bar companion rollout. Drilling into a brand card now lands on ``/dashboard/quotas/<brand_slug>`` — a quick-view subset of the Operator Cockpit (quota card, clients, routes, sparkline) that's read-only; every write path links out to the Cockpit. New HTTP surfaces (all scoped to the active providers behind a brand): - ``GET /api/quotas/<slug>/clients`` — client_profile + client_tag breakdown + totals, filtered to the brand's providers. - ``GET /api/quotas/<slug>/routes`` — lane_family / routing / selection_path breakdown for the brand. - ``GET /api/quotas/<slug>/analytics`` — hourly (last 24h, max 1 week) + daily (last 14d, max 90d) series + totals + per-provider summary. ``hours`` / ``days`` are clamped so a URL typo can't hammer SQLite. - ``GET /dashboard/quotas/<slug>`` — self-contained HTML shell that polls all four APIs on load + every 60s; substitutes ``BRAND_SLUG`` + ``COCKPIT_URL`` at render time. All four endpoints 404 on unknown or inactive brands so the widget can distinguish "typo in URL" from "brand exists but no traffic yet". Metrics layer gets two small additions (backward compatible): - ``_build_where_clause`` now accepts ``providers=[...]`` (rendered as ``provider IN (...)`` with dedup) and ``since=<ts>`` — lets the new endpoints aggregate across multiple runtime providers in one query. - ``get_hourly_series`` / ``get_daily_totals`` accept ``**filters`` so the detail view's sparkline is brand-scoped, not global. Test coverage: 14 new tests in ``test_brand_detail_endpoints.py`` pinning the 404 contract, the providers-filter round-trip through metrics, query clamping, and the HTML placeholder substitution. Full suite: 409 passed. See docs/GATE-BAR-DESIGN.md §3.4. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Phase C of the v2.3.0 Gate Bar rollout. Ships a macOS 14+ Universal menubar app at ``apps/gate-bar/`` that reads the local faigate gateway's ``/api/quotas`` and surfaces every active brand at a glance — colour-coded by severity, sorted worst-alert first. Architecture keeps the boundary sharp (see docs/GATE-BAR-DESIGN.md §5): - Pure HTTP client on 127.0.0.1 — no shared state, no socket, no filesystem coupling with the Python daemon. - Brand roster discovered at runtime; the app ships with no hard-coded provider enum. - Read-only: every mutating action (add provider, edit lanes, etc.) deep-links to the Operator Cockpit. SwiftUI surface stays in the Sonoma subset per the design doc: ``ObservableObject`` + Combine, plain ``Color``, no ``@Observable``, no ``MeshGradient``. Package.swift pins ``.macOS(.v14)``. Module layout: - ``Models.swift`` — Codable mirror of ``/api/quotas`` (forward- compatible decode so the Python side can add fields without breaking Gate Bar), plus ``BrandGroup`` + ``AlertLevel`` aggregates. - ``QuotaClient.swift`` — URLSession actor, single network surface. - ``QuotaStore.swift`` — ``ObservableObject`` that groups packages by ``brand_slug``, sorts worst-alert first, and exposes a tightest- window menubar summary. - ``Preferences.swift`` — UserDefaults-backed ``@Published`` wrapper (gateway URL, cockpit URL, refresh cadence). - ``Theme.swift`` — colour palette mirroring the web widget's CSS variables so the menubar reads as the same product. - ``PopoverView.swift`` / ``BrandCardView.swift`` — popover shell + per-brand card with pace tick (same vocabulary as §3 of the design). - ``PreferencesView.swift`` — settings window, 4 controls, no wizards. - ``GateBarApp.swift`` — ``@main`` + ``MenuBarExtra`` / ``Settings`` scenes; menubar label is "fAI · NN%" with a coloured dot. Tests (13, Swift Testing framework): - JSON decode round-trip + forward-compatibility. - AlertLevel classification (server-label precedence, ratio fallback, unknown-string degradation). - Store grouping, worst-alert sort, tie-break rules, menubar summary. CLT-only machines need explicit rpaths for Swift Testing — wrapped in ``scripts/swift-test.sh`` so ``./scripts/swift-test.sh`` works whether the dev has Xcode.app installed or not. Out of scope for 0.1 (tracked in apps/gate-bar/README.md): Sparkle auto-update, notifications, launch-at-login, .app bundling + notarization, Homebrew cask. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Persisted dashboard.quotas.default_view setting honoured by
/dashboard/quotas via server-side 302. Three values: overview (grid,
default), brand:<slug> (detail page), cockpit (offsite). The escape
hatch ?view=overview always renders the grid so a pinned brand card
can link home without fighting the redirect.
Persistence uses ruamel.yaml round-trip so the 220+ operator comments
in config.yaml survive a pin toggle — yaml.safe_dump would flatten
them. Writes are atomic (tempfile.mkstemp + os.replace in the same
dir) so a crash mid-write can't leave a half-rewritten config.
HTTP surface:
- GET /api/dashboard/settings — {default_view, pinned_brand_slug}
- POST /api/dashboard/settings — 400 on bad input, 200 with canonical
settings on success
- GET /dashboard/quotas — honours default_view, falls back to the
overview if settings read fails (never 500s)
UI:
- Overview: 'Home view' chip in header + 'Reset to Overview' when
anything other than overview is pinned.
- Every brand card: 'Pin as Home' / '📌 Home' button next to
Details/Cockpit. Pinned card gets a subtle accent outline.
- Detail page header: same pin button.
- Gate Bar popover footer: 'Dashboard ↗' link to
<gateway>/dashboard/quotas. Server-side redirect handles default_view
so the menubar app doesn't branch.
Tests: 39 new cases in tests/test_dashboard_settings.py cover
round-trip comment preservation, validation rules, atomic rename,
endpoint contracts, redirect behaviour, and bad-config fallback.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Until Gate Bar 0.2 ships a notarized Homebrew cask, developers who
want to try the menubar app on their own machine can run:
cd apps/gate-bar
./scripts/install-local.sh
The script does a release build, wraps the binary in a minimal .app
bundle (LSUIElement so no Dock icon, macOS 14+ min version),
ad-hoc code-signs it, and installs to ~/Applications. Because the
binary is built and signed on the same machine, Gatekeeper trusts it
without a Developer ID round-trip — no quarantine xattr means no
first-launch prompt.
Flags: --no-open skips the auto-launch, --uninstall removes the
installed bundle.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Consolidates six commits on this branch into a single release: - Brand-card overview at /dashboard/quotas (worst-alert first, pace marker, identity line, per-brand credential gating). - Per-brand detail view at /dashboard/quotas/<slug> + three read-only endpoints backing it (clients, routes, analytics). - Default landing view (dashboard.quotas.default_view in config.yaml) with Pin-as-Home on every card; writes go through ruamel.yaml round-trip so operator comments survive a pin toggle. - Gate Bar 0.1 SwiftUI menubar companion at apps/gate-bar/ — reads /api/quotas, renders cards, links to Dashboard and Cockpit. Source-only for now; notarized cask ships with Gate Bar 0.2. New runtime dep: ruamel.yaml>=0.18.6 (required for the comment-preserving dashboard.quotas.default_view writes). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Resolved conflicts in faigate/main.py and faigate/quota_tracker.py by keeping this branch's superset (brand/pace/identity on QuotaStatus + brand-first dashboard). Main had the earlier provider-group-only version of the quota commit; our branch has that plus the follow-up brand naming work.
added 3 commits
April 19, 2026 19:08
Three classes of fixes:
1. Reflective XSS (high severity) on /dashboard/quotas/{brand_slug}:
The slug from the URL path was spliced into the HTML template via
str.replace without validation, so a crafted slug containing HTML
could escape the attribute context. Now whitelisted to
[a-z0-9][a-z0-9-]{0,63} — matches every real catalog brand and
rejects anything else with 404.
2. Stack-trace exposure (medium, x3) on POST /api/dashboard/settings:
Error responses included str(exc) for both ValueError and the
catch-all, which can leak internal paths or user-supplied fragments.
Replaced with static messages; the real exception is logged via
logger.exception.
3. Ruff F401: removed unused imports shutil (test_dashboard_settings)
and timedelta (test_quota_tracker_brand) flagged by the lint CI job.
Three follow-up fixes for PR #217 CI: 1. pyproject.toml: ruff target-version py312 → py310. The project's requires-python is >=3.10, but with py312 ruff's UP017 rule was rewriting datetime.timezone.utc to the 3.11-only datetime.UTC, breaking the 3.10 CI matrix job with ImportError. 2. test_quota_tracker_brand.py: switch UTC import to timezone.utc so the tests actually run on 3.10. 3. faigate/main.py: pass the already-whitelisted brand slug through html.escape before splicing into the HTML template. CodeQL's taint tracker doesn't recognise arbitrary regex sanitisers, so this satisfies py/reflective-xss without relaxing the regex guard above. 4. faigate/dashboard_settings.py: ruff format pass (cosmetic).
…ules faigate's requires-python is >=3.10 but three modules imported the 3.11+ datetime.UTC alias directly. Main's CI happened to stay green because no test module imported these three files at collection time — my new test_quota_tracker_brand.py exposed the latent bug on the 3.10 matrix job. Files fixed: faigate/quota_tracker.py — 4x datetime.now(UTC), 1x tzinfo=UTC, 1x tz=UTC faigate/quota_headers.py — 2x datetime.now(UTC), 2x .replace/.astimezone faigate/quota_poller.py — 2x datetime.now(UTC) Already-correct files left untouched: updates.py and tests/test_updates.py both use the canonical try/except ImportError shim.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
v2.3.0 release — eight commits consolidating the brand-first quota widget refresh and the Gate Bar 0.1 macOS menubar companion.
Dashboard
/dashboard/quotas(worst-alert first, pace marker, identity line, per-brand credential gating)./dashboard/quotas/<slug>+ three read-only endpoints (clients,routes,analytics).dashboard.quotas.default_viewsetting with one-click Pin-as-Home. Writes go throughruamel.yamlround-trip so the 220+ operator comments in a realconfig.yamlsurvive a pin toggle —yaml.safe_dumpwould flatten them.GET/POST /api/dashboard/settingsdrives the pin UI.Gate Bar 0.1 (
apps/gate-bar/)MenuBarExtraapp, macOS 14+, pure HTTP client against/api/quotas../apps/gate-bar/scripts/install-local.shfor a locally-built, ad-hoc-signed.appin~/Applications. Notarized Homebrew cask ships with Gate Bar 0.2.Dep change:
ruamel.yaml>=0.18.6added torequirements.txt+pyproject.toml.Test plan
python3.12 -m pytest tests/test_dashboard_settings.py tests/test_brand_detail_endpoints.py(53 passed)./apps/gate-bar/scripts/swift-test.sh(13 passed, 3 suites)./apps/gate-bar/scripts/install-local.sh --no-open→ bundle verified, ad-hoc signed, launches without Gatekeeper promptAfter merge
git checkout main && git pull --ff-onlygit tag -a v2.3.0 -m "fusionAIze Gate v2.3.0"git push origin v2.3.0→ triggersrelease-artifacts.ymlnotify-tap.yml🤖 Generated with Claude Code