Commit Graph

22 Commits

Author SHA1 Message Date
Carsten Rehfeld fa473d2b61 feat(admin): chart redraws every 1 s — live bucket at right edge
Deploy to Production / deploy (push) Successful in 3m12s
Separate chart refresh from bucket flush:
- 5 s flush commits completed bucket into buf, resets accumulator
- Every poll (1 s): redraw path as buf[1..] + current live bucket
  so the rightmost bar visually builds up in real time
- Label and chart now both update at 1 s cadence

Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
2026-05-17 01:26:08 +02:00
Carsten Rehfeld 435e1bda42 fix(admin): smooth live rate label — window-accumulated rate, not instantaneous
Deploy to Production / deploy (push) Successful in 3m16s
With 1 s polls and batch traffic, delta * 60 flickers between 0 and 300.
Fix: live label shows accDelta / elapsed_in_window * 60000 — the rate
builds up continuously within each 5 s bucket. First 200 ms after flush
carries the previous window rate to avoid a zero jump.

Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
2026-05-17 01:20:15 +02:00
Carsten Rehfeld de597f1d8b fix(admin): clip rate chart at 100% — SVG clipPath + data cap
Deploy to Production / deploy (push) Successful in 3m13s
Two-layer fix:
- SVG clipPath on plot area: path physically cannot escape chart bounds
- Chart flush caps rpm at rateLimit: timing drift (two batches in one
  5 s window) no longer causes visual overshoot
Live label still shows true req/min and % including values > 100%.

Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
2026-05-17 01:12:10 +02:00
Carsten Rehfeld 4908d5d6e0 feat(admin): adaptive poll rate — 1 s active, 5 s idle after 15 s no traffic
Deploy to Production / deploy (push) Successful in 3m16s
Admin sandbox drill-down polls at 1 s when traffic is flowing (live label
updates feel instant), drops back to 5 s after 15 s of inactivity.
Chart bucket remains fixed at 5 s so x-axis labels stay correct; deltas
accumulate between buckets independently of poll rate.

Tier-dependent rate reserved for future sandbox-owner dashboard.

Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
2026-05-17 00:52:42 +02:00
Carsten Rehfeld 0ee76ac535 fix(admin): serialize poll response as JSON string via ObjectMapper
Deploy to Production / deploy (push) Successful in 3m20s
Response.ok(Map.of(...)).build() with class-level @Produces(TEXT_HTML)
caused Quarkus to call Map.toString() instead of the Jackson writer,
producing {key=value} output that r.json() can't parse. Explicitly
serialize with ObjectMapper and set Content-Type to application/json.

Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
2026-05-17 00:35:06 +02:00
Carsten Rehfeld 6bc4059922 fix(admin): move rate chart out of Qute template into admin.js
Deploy to Production / deploy (push) Successful in 3m26s
Qute processes all {...} in .html files including <script> blocks.
JS function bodies like function(d) { return y(d); } were silently
eaten, causing a syntax error that killed the entire chart script.

Moving the rate chart IIFE into admin.js (static, Qute-never-touches)
fixes the root cause. Axes (Y: 0/50/100%, X: -6m/-3m/now) and
live % label now render correctly.

Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
2026-05-17 00:29:16 +02:00
Carsten Rehfeld 00e4731bde feat(admin): add time/% axes to live rate chart
Deploy to Production / deploy (push) Successful in 3m20s
Y-axis shows 0/50/100% of rate limit with grid lines.
X-axis shows -6m / -3m / now time labels.
Live label also shows current utilisation percentage.
Chart height increased to 96px to accommodate axes.
Cache-busted admin.js to v4.

Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
2026-05-17 00:21:22 +02:00
Carsten Rehfeld 0d4f7934f7 fix(admin): bust admin.js browser cache with ?v=3, disable immutable static cache
Deploy to Production / deploy (push) Successful in 3m16s
Quarkus defaults to Cache-Control: immutable, max-age=86400 for static files.
The immutable flag means browsers ignore hard-refresh and serve stale JS for 24h.
Fix: version query string on admin.js URLs, max-age=300 (5min), clear immutable-paths.

Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
2026-05-16 23:53:21 +02:00
Carsten Rehfeld c7bfa73d2f fix(admin): blinks + live rate chart on sandbox drill-down
Deploy to Production / deploy (push) Successful in 3m14s
Blink bug: agent visits were gated behind registrarLat != null — sandboxes
without a declared location showed no blinks at all. Now always enters the
sandbox branch when __D.sandboxId is present; registrar star still conditional.

Rate chart: rolling D3 area chart (72 buckets x 5s = 6min window) polls
GET /sandbox/{uuid}/poll every 5s, computes delta→rpm, renders with
CatmullRom curve. Red dashed limit line. Live value shown in stat box and
chart header. Poll endpoint added to DashboardResource (JSON, no HTML).

Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
2026-05-16 22:29:37 +02:00
Carsten Rehfeld 51e210ab68 fix(admin): pre-compute pagination in Java, remove arithmetic from Qute template
Deploy to Production / deploy (push) Successful in 3m12s
Qute cannot evaluate + and * as arithmetic operators at runtime even with
requireTypeSafeExpressions=false — it tries to look them up as map keys.
Pre-compute hasPrev/hasNext/displayPage/prevPage/nextPage in the resource.

Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
2026-05-16 21:55:18 +02:00
Carsten Rehfeld 60dfcd58f6 fix(admin): use getRequestUri().getPath() in auth filter to avoid //login double-slash
Deploy to Production / deploy (push) Successful in 3m13s
Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
2026-05-16 21:42:18 +02:00
Carsten Rehfeld ad19c8f20f fix(admin): relax Qute type-safety for arithmetic in list.html, fix JS template literals
Deploy to Production / deploy (push) Successful in 3m14s
requireTypeSafeExpressions=false allows {page+1} and (page+1)*size
in pagination conditions. JS template literals with ${days} replaced
with string concatenation to avoid Qute consuming the {days} token.

Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
2026-05-16 21:30:29 +02:00
Carsten Rehfeld 82a379e536 feat(admin): apix-admin UI at admin.api-index.org
Deploy to Production / deploy (push) Failing after 47s
Internal maintainer dashboard: global stats, sandbox list with
tier-filter and pagination, sandbox drill-down with D3 world map,
tier-promotion form. API-key auth via HttpOnly cookie. Port 8084,
rolling a/b deploy alongside the existing service pairs.

- apix-common: AdminSandboxSummary, AdminSandboxListResponse, AdminStatsResponse DTOs
- apix-registry: AdminResource (4 endpoints), SandboxService.listAllSandboxes + getGlobalStats
- apix-registry: SandboxResource GET /{uuid}/dashboard endpoint (was missing — fixes portal 500)
- apix-portal: RegistryClient path corrected to /dashboard
- apix-admin: new Quarkus module — auth filter, registry REST client, 3 resources, 6 Qute templates, admin.js
- infra: Dockerfile.admin, docker-compose admin-a/b services, Caddyfile admin.api-index.org block
- scripts: deploy-bluegreen.sh extended for admin-a/b

Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
2026-05-16 21:18:48 +02:00
Carsten Rehfeld e385d05ce1 fix(sandbox): two sandbox bugs blocking Peter Steinberger beta test
Bug 1 — sandbox-scoped discovery returns empty:
GET /services?capability=nlp with a sandbox API key returns [] because
Bunny.net CDN caches the public (PRODUCTION-scoped) empty response and
serves it regardless of the X-Api-Key header. Fix: ServiceResource.search()
now returns Cache-Control: private, no-store when a sandbox key is active,
opting the response out of CDN caching.

Bug 2 — _links.sandbox href uses sandbox name instead of UUID:
IndexResource.index() used sandbox.name as the URL path segment instead of
sandbox.id. Name-based URLs return 404 since migration-020 made UUID the
sole resource identifier. Fix: sandbox.name → sandbox.id.

Also: SandboxResource GET /{uuid} simplified to SandboxIndexResponse
(metadata + links only); heavy usage stats + agent visits are on
/telemetry. SandboxService feedback INSERT uses CAST(:x AS jsonb) for
portability across PostgreSQL JDBC driver versions.

Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
2026-05-16 13:20:37 +02:00
Carsten Rehfeld 134c8b3c6b fix(registry): all 5 CucumberIT suites green — device nav + iotReady filter
Three root causes fixed:

1. DeviceCucumberIT — duplicate step class removed from bdd.device; moved
   to dedicated devicebdd package with its own TestSetup and glue scanning,
   preventing duplicate-step conflicts with IoT suites.

2. DeviceNavigationSteps — ?-in-Cucumber-expression treated as optional-char
   quantifier. Changed GET /devices?capability=, ?deviceClass=, ?protocol=
   patterns to anchored regex (^…$) with (.+) capture so literal ? matches.

3. IotProfileCucumberIT iotReady=true — CanonicalQueryFilter was silently
   redirecting ?iotReady=true to the canonical URL (which omitted iotReady).
   RestAssured followed the 302 and the server never saw the parameter.
   Fix: add iotReady to QueryNormalisationService.canonicalForReplacements
   with normaliseIotReady ("true" → "true", else absent).
   ServiceResource now reads iotReady via UriInfo (bypasses JAX-RS @QueryParam
   which is downstream of the filter rewrite). RegistryService uses primitive
   boolean + INNER JOIN iot_profiles when iotReady=true.

Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
2026-05-15 20:11:18 +02:00
Carsten Rehfeld 8bc0ce1fb8 fix(portal): extract inline JS to static file to resolve Qute {#raw} parse error
Deploy to Production / deploy (push) Successful in 3m5s
Quarkus 3.15 removed the {#raw} section helper. Moving the dashboard map/expires
JS to META-INF/resources/dashboard.js (served as static asset, bypasses Qute)
fixes the portal startup failure in CI.

Co-Authored-By: Mira <noreply@anthropic.com>
2026-05-14 16:01:07 +02:00
Carsten Rehfeld 68b98589cb fix(build): commit missing pom.xml changes needed for compilation
Deploy to Production / deploy (push) Failing after 3m2s
- apix-common/pom.xml: add microprofile-openapi-api (provided) so
  @Schema annotations on shared enums compile without Quarkus on classpath
- apix-registry/pom.xml: pending fixes from previous session
- pom.xml (root): pending parent pom updates

Co-Authored-By: Mira <noreply@anthropic.com>
2026-05-14 15:51:46 +02:00
Carsten Rehfeld 46f32c2df2 chore: add missing source modules to version control
Deploy to Production / deploy (push) Failing after 7s
apix-demo, apix-portal/src, apix-spider/src, apix-registry/src,
apix-common/src were never staged. Without them the CI build has no
source to compile and the Docker images cannot be produced.

Also adds docs/ (infrastructure notes) missed in prior commits.

Co-Authored-By: Mira <noreply@anthropic.com>
2026-05-14 15:49:03 +02:00
Carsten Rehfeld a9b3354bde fix(ci): replace checkout action with git clone (no node on host runner)
Deploy to Production / deploy (push) Failing after 1m44s
actions/checkout@v3 is a JavaScript action requiring node in PATH.
Host runners have no node. Replace with direct git clone/fetch.

Co-Authored-By: Mira <noreply@anthropic.com>
2026-05-14 15:40:08 +02:00
Carsten Rehfeld 82f0ac6007 ops: add CI/CD pipeline, a/b rolling deploy, Gitea Actions workflow
Deploy to Production / deploy (push) Failing after 10s
- .gitea/workflows/deploy.yml — push-to-main triggers rolling deploy
- scripts/deploy-bluegreen.sh — a-stack then b-stack restart; Maven runs
  in Docker (no JDK needed on runner host); Caddy reload at end
- scripts/deploy-all.ps1 — emergency manual deploy from dev machine
- infra/docker-compose.yml — a/b pairs per service; wget health checks;
  Gitea service; Prometheus/Grafana/DB ports restricted to localhost
- infra/Caddyfile — dual upstreams with health-based routing
- infra/Dockerfile.* — one per service
- infra/prometheus.yml + grafana provisioning

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 14:01:12 +02:00
Carsten Rehfeld 5156089152 feat(registry): add IoT device connection profile (iot_profiles table)
New optional 1:1 extension to services: providers can declare how IoT
devices connect to their hub (hub_url, protocols, provisioning endpoint,
device classes, firmware compatibility) via PATCH /services/{id}.

- New entity IotProfileEntity + Liquibase changeset 009 (GIN indexes for
  jsonb device_classes and protocols arrays)
- IotProtocol / CredentialFormat enums; IotProfileRequest / IotProfileResponse DTOs
- JsonStringListConverter for jsonb List<String> persistence
- GET /services/{id}/replacements extended with iotProfile per candidate
  and new filter params: iotReady=true, deviceClass=..., protocol=...
- 6 new BDD scenarios (IotProfileCucumberTest) covering profile creation,
  candidate enrichment, iotReady / deviceClass filtering, partial update,
  and missing-field 422 validation
- All 57 tests green (6 new + 27 existing transition + 24 unit)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 16:18:42 +02:00
Carsten Rehfeld b2a16a8be7 Implement apix-registry with IoT sunset/decommission lifecycle and full BDD suite
- REST API: register, patch, O-level, replacements, history, search endpoints
- IoT lifecycle validations: future sunset, lock-before-release, sunset-passed-before-decommission
- DB schema: Liquibase changesets 001–008 (services, versions, replacements, sunset-at column)
- @ColumnTransformer(write="?::jsonb") on bsm_payload fields to avoid JDBC varchar→jsonb rejection
- Jandex plugin on apix-common + quarkus.index-dependency so @NotBlank validators resolve at runtime
- quarkus-logging-json extension added; quarkus.log.console.json=false is now a recognised key
- Fix requireSunsetBeforeLockRelease: Boolean.TRUE.equals instead of !Boolean.FALSE.equals (null guard)
- BDD suite: 27 scenarios / 213 steps across 5 feature files (sunset-lock, decommission, replacement, discovery, anonymity)
- Test infrastructure: JDBC TRUNCATE in @Before for DB isolation, Arc.container() for clock control — no test endpoints in production code
- sunsetAt truncated to microseconds in BDD steps to match Postgres timestamptz precision
- Cucumber step fixes: singular/plural candidate(s), lastResponse propagation in replacementsReturnsNCandidates

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 09:13:26 +02:00