- Sandbox search without ?stage= defaults to DEVELOPMENT (was PRODUCTION),
so freshly registered services are discoverable by default
- DEPRECATED and DECOMMISSIONED rejected at creation with 400 (lifecycle-only)
- PRODUCTION accepted at creation for established services going live immediately
- Admin sandbox list now returns non-null createdAt and expiresAt (toInstant fix
for Hibernate 6 returning LocalDateTime from timestamptz native queries)
- Admin UI sandbox list shows Created column
- BDD: sandbox-admin-list.feature + sandbox-stage.feature (47 scenarios, all green)
Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
Covers axes, live label format, path update on delta, 1 s poll cadence,
rate decay after CHART_TICK, burst overflow clamp, and header elements.
All network I/O mocked via page.route(); no live Quarkus server required.
Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
Removed separate setInterval for buf flush. Flush + redraw now happen
together inside poll() — one atomic step, one D3 transition(950ms).
Two independent 1 s timers were firing out of phase and interrupting
each other's transitions, causing flicker and apparent 2 s cadence.
Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
Replace fixed-window accDelta (resets to 0 every 5 s) with a sliding
window: recentPolls[] holds {t, delta} entries; liveRpm() sums all
entries within the last 5 s and extrapolates to rpm. Old entries age
out naturally so the rate decays smoothly with no hard reset jump.
Chart and label now update continuously without the 0-drop artifact.
Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- .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>