Commit Graph

34 Commits

Author SHA1 Message Date
Carsten Rehfeld 9b70599d98 docs: graph-registry concept, RFC capability taxonomy item, session chronicle fixes
- Add concepts/graph-registry.md: SQL discovery limitations, Apache AGE two-stage
  plan, Mycorrhizal relationship, four open questions
- RFC checklist: add capability string hierarchy formalization item (dot-notation
  is_instance_of semantics, prefix match requirement)
- Fix Tippfehler: Pharmakagent -> Pharmaagent in 2026-05-18-de.md
- Update deploy-registry.ps1 (ASCII-only, blue-green rolling deploy)

Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
2026-05-19 02:02:47 +02:00
Carsten Rehfeld f047fa0e2d feat(registry): service stage semantics for sandbox registration
- Sandbox search without ?stage= now defaults to DEVELOPMENT (was PRODUCTION),
  so freshly registered services are discoverable by default
- DEPRECATED and DECOMMISSIONED stages are rejected at creation with 400;
  they are lifecycle-only transitions, not valid initial states
- PRODUCTION is accepted at creation for established services going live immediately

BDD coverage: sandbox-stage.feature (6 scenarios, all green)
Fixes 2 pre-existing failures in sandbox-service-isolation.feature.

Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
2026-05-18 22:23:41 +02:00
Carsten Rehfeld 0b519a003f test+fix: admin sandbox list returns non-null createdAt (TDD)
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>
2026-05-18 21:49:10 +02:00
Carsten Rehfeld bef9494622 feat(admin-ui): add Created column to sandbox list
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>
2026-05-18 19:06:37 +02:00
Carsten Rehfeld 02420fcbc4 feat(apix-demo): working Node.js discovery demo + sandbox cleanup
- apix-demo.mjs: all 6 steps run end-to-end — HATEOAS root, sandbox
  creation, BSM registration (3 carriers), capability search, parallel
  quote calls, best-price selection; all field names aligned to BsmPayload
- SandboxService: add deleteSandbox() — removes services then sandbox,
  DEMO tier protected
- AdminResource: add DELETE /admin/sandboxes/{uuid} backed by deleteSandbox
- cleanup-sandboxes.mjs: admin script to purge test sandboxes while
  preserving DEMO-tier and named exceptions (apix-demo-ecosystem)

Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
2026-05-17 18:04:06 +02:00
Carsten Rehfeld b85c24d9bf docs(starblink): continuity conversation + Node.js demo client
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>
2026-05-17 16:52:13 +02:00
Carsten Rehfeld f3566a3c1a chore(ops): gitignore runtime artifacts — .logs/, .pids/, Temp JSON files
Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
2026-05-17 13:37:29 +02:00
Carsten Rehfeld 1fd481a2d9 chore(admin): gitignore node_modules and Playwright output dirs
Deploy to Production / deploy (push) Successful in 3m17s
Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
2026-05-17 02:14:18 +02:00
Carsten Rehfeld 7ccc384ebc test(admin): Playwright chart tests — 11 scenarios, fully mocked
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>
2026-05-17 02:14:02 +02:00
Carsten Rehfeld 4d980d7fc3 fix(admin): single update path per poll — no competing D3 transitions
Deploy to Production / deploy (push) Successful in 3m15s
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>
2026-05-17 01:49:37 +02:00
Carsten Rehfeld f895bcf0cf feat(admin): 1 s chart buckets + smooth D3 transition
Deploy to Production / deploy (push) Successful in 3m14s
- Bucket size: 1 s (was 5 s) → chart scrolls every second, feels live
- Window: 72 s visible, x-axis labels -72s / -36s / now
- D3 transition(900ms, easeLinear) on path redraw → continuous motion
- Sliding-window rate calculation unchanged (still 5 s window for rpm)

Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
2026-05-17 01:45:08 +02:00
Carsten Rehfeld a4a5a8e3f2 fix(admin): sliding-window rate — no sawtooth drop at bucket boundaries
Deploy to Production / deploy (push) Successful in 3m20s
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>
2026-05-17 01:37:55 +02:00
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