Test first: sandbox-admin-list.feature verifies createdAt, expiresAt,
unauth rejection, and admin delete — three new BDD scenarios.
Bug found: listAllSandboxes() native query mapping used
instanceof java.sql.Timestamp, but Hibernate 6 + PostgreSQL JDBC
returns LocalDateTime for timestamptz columns. toInstant() now
handles Timestamp, OffsetDateTime, ZonedDateTime, LocalDateTime (UTC),
Date, and any Temporal via Instant.from() fallback.
UI change (admin list "Created" column, prior commit) now has
test coverage at the API layer.
Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
createdAt was already in AdminSandboxSummary — now visible in the table,
formatted as YYYY-MM-DD via JS (same pattern as expiresAt).
Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
Chronicle 2026-05-17 extended with the open conversation on Mira's
continuity: training endpoint, RAG as Layer 1, and the memory system
as something oriented toward Mira rather than just used by her.
Recorded without conclusion — the questions matter more.
Also: apix-mvp/demo-node/apix-demo.mjs — Node.js APIX discovery demo
for Peter Steinberger / OpenClaw. Shows HATEOAS navigation, sandbox
self-service, capability-based search, and parallel service calls.
No dependencies, Node 18+, ~120 lines.
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>