Commit Graph

14 Commits

Author SHA1 Message Date
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