871 Commits

Author SHA1 Message Date
hsiegeln
4e45be59ef docs: update CLAUDE.md with persistent route catalog conventions
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m24s
CI / docker (push) Successful in 1m9s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 41s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 19:05:36 +02:00
hsiegeln
3a54b4d7e7 refactor: consolidate heartbeat findById into single lookup
Merge the route state update and route catalog upsert blocks to share
one registryService.findById() call instead of two, reducing overhead
on the high-frequency heartbeat path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 19:04:41 +02:00
hsiegeln
b77968bb2d docs: update rule files with RouteCatalogStore classes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 18:50:39 +02:00
hsiegeln
10b412c50c feat: merge persistent route catalog into legacy catalog endpoint
Add RouteCatalogStore as a third data source in RouteCatalogController so that
/api/v1/routes/catalog surfaces routes with zero executions and routes from
previous app versions that fall within the requested time window.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 18:50:13 +02:00
hsiegeln
24c858cca4 feat: merge persistent route catalog into unified catalog endpoint
Wires RouteCatalogStore into CatalogController as a third data source:
routes with zero executions and routes from previous app versions
(within the queried time window) now appear in the unified catalog.
Also clears route_catalog on app dismiss via deleteByApplication().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 18:49:00 +02:00
hsiegeln
462b9a4bf0 feat: persist route catalog on agent register and heartbeat
Wire RouteCatalogStore into AgentRegistrationController and call upsert
after registration and heartbeat so routes survive server restarts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 18:47:22 +02:00
hsiegeln
c4f4477472 feat: wire ClickHouseRouteCatalogStore bean 2026-04-16 18:45:48 +02:00
hsiegeln
961dadd1c8 feat: implement ClickHouseRouteCatalogStore with first_seen cache 2026-04-16 18:45:45 +02:00
hsiegeln
887a9b6faa feat: add RouteCatalogStore interface and RouteCatalogEntry record 2026-04-16 18:45:42 +02:00
hsiegeln
04da0af4bc feat: add route_catalog table to ClickHouse schema 2026-04-16 18:45:38 +02:00
hsiegeln
dd0f0e73b3 docs: add persistent route catalog implementation plan
9-task plan covering ClickHouse schema, core interface, cached
implementation, bean wiring, write path (register/heartbeat),
read path (both catalog controllers), dismiss cleanup, and
rule file updates.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 18:42:37 +02:00
hsiegeln
2542e430ac docs: add persistent route catalog design spec
Routes with zero executions (sub-routes) vanish from the sidebar after
server restart because the catalog is purely in-memory with a ClickHouse
stats fallback that only covers executed routes. This spec describes a
persistent route_catalog table in ClickHouse with lifecycle tracking
(first_seen/last_seen) to reconstruct the sidebar without agent
reconnection and support historical time-window queries.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 18:39:49 +02:00
hsiegeln
a1ea112876 feat: replace text labels with icons in runtime cards
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m20s
CI / docker (push) Successful in 1m38s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 53s
Use Activity, Cpu, and HeartPulse icons instead of "tps", "cpu", and
"ago" text in compact and expanded app cards. Bump design-system to
v0.1.55 for sidebar footer alignment fix.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 15:43:37 +02:00
11ad769f59 Merge pull request 'feat/runtime-compact-view' (#136) from feat/runtime-compact-view into main
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m20s
CI / docker (push) Successful in 30s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Reviewed-on: #136
2026-04-16 15:27:37 +02:00
hsiegeln
3a9f3f41de feat: match filter input to sidebar search styling
All checks were successful
CI / cleanup-branch (pull_request) Has been skipped
CI / build (pull_request) Successful in 2m11s
CI / cleanup-branch (push) Has been skipped
CI / docker (pull_request) Has been skipped
CI / build (push) Successful in 2m11s
CI / deploy (pull_request) Has been skipped
CI / deploy-feature (pull_request) Has been skipped
CI / docker (push) Successful in 1m11s
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Successful in 40s
Add search icon, translucent background, and same padding/sizing
as the sidebar's built-in filter input. Placeholder changed to
"Filter..." to match sidebar convention.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 15:22:23 +02:00
hsiegeln
e84822f211 feat: add sort buttons and fix filter placeholder
Some checks failed
CI / cleanup-branch (pull_request) Has been skipped
CI / build (pull_request) Successful in 2m12s
CI / cleanup-branch (push) Has been skipped
CI / docker (pull_request) Has been skipped
CI / build (push) Successful in 2m13s
CI / deploy (pull_request) Has been skipped
CI / deploy-feature (pull_request) Has been skipped
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / docker (push) Has been cancelled
Add sort buttons (Status, Name, TPS, CPU, Heartbeat) to the toolbar,
right-aligned. Clicking toggles asc/desc, second sort criterion is
always name. Status sorts error > warning > success. Fix trailing
unicode escape in filter placeholder.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 15:19:10 +02:00
hsiegeln
e346b9bb9d feat: add app name filter to runtime toolbar
Some checks failed
CI / cleanup-branch (pull_request) Has been skipped
CI / build (pull_request) Successful in 2m9s
CI / cleanup-branch (push) Has been skipped
CI / docker (pull_request) Has been skipped
CI / build (push) Successful in 2m10s
CI / deploy (pull_request) Has been skipped
CI / deploy-feature (pull_request) Has been skipped
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / docker (push) Has been cancelled
Text input next to view toggle filters apps by name (case-insensitive
substring match). KPI stat strip uses unfiltered counts so totals
stay accurate. Clear button on non-empty input.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 15:16:28 +02:00
hsiegeln
f4811359e1 fix: keep view toggle visible in both compact and expanded modes
Some checks failed
CI / cleanup-branch (pull_request) Has been skipped
CI / build (pull_request) Successful in 2m16s
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 2m12s
CI / docker (pull_request) Has been skipped
CI / deploy (pull_request) Has been skipped
CI / deploy-feature (pull_request) Has been skipped
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
Move toolbar above the grid conditional so it renders in both
view modes. Hidden only on app detail pages (isFullWidth).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 15:13:47 +02:00
hsiegeln
e5b1171833 feat: replace Errors column with CPU in expanded agent table
Some checks failed
CI / cleanup-branch (pull_request) Has been skipped
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / cleanup-branch (push) Has been cancelled
CI / build (push) Has been cancelled
CI / build (pull_request) Successful in 2m0s
CI / docker (pull_request) Has been skipped
CI / deploy (pull_request) Has been skipped
CI / deploy-feature (pull_request) Has been skipped
Show per-instance CPU usage percentage instead of error rate in the
DataTable. Highlights >80% CPU in error color.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 15:12:01 +02:00
hsiegeln
d27a288128 fix: resolve TS2367 — view toggle active class in compact-only branch
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m32s
CI / cleanup-branch (pull_request) Has been skipped
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / docker (push) Has been cancelled
CI / build (pull_request) Successful in 2m15s
CI / docker (pull_request) Has been skipped
CI / deploy (pull_request) Has been skipped
CI / deploy-feature (pull_request) Has been skipped
The toggle only renders inside the compact branch, so viewMode is
always 'compact' there. Use static class assignment instead of a
comparison TypeScript correctly flags as unreachable.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 14:20:45 +02:00
hsiegeln
7825aae274 feat: show CPU usage in expanded GroupCard meta headers
Add max CPU percentage to the meta row of both the full expanded
view and the overlay expanded card, consistent with compact cards.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 14:18:48 +02:00
hsiegeln
4b264b3308 feat: add CPU usage to agent response and compact cards
Backend:
- Add cpuUsage field to AgentInstanceResponse (-1 if unavailable)
- Add queryAgentCpuUsage() to AgentRegistrationController — queries
  avg CPU per instance from agent_metrics over last 2 minutes
- Wire CPU into agent list response via withCpuUsage()

Frontend:
- Add cpuUsage to schema.d.ts
- Compute maxCpu per AppGroup (max across all instances)
- Show "X% cpu" on compact cards when available (hidden when -1)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 14:12:23 +02:00
hsiegeln
b57fe875f3 feat: click-outside dismiss and clean overlay styling
- Add invisible backdrop (z-index 99) behind expanded overlay to
  dismiss on outside click
- Remove background/padding from overlay wrapper so GroupCard
  renders without visible extra border
- Use drop-shadow filter instead of box-shadow for natural card
  shadow

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 14:04:26 +02:00
hsiegeln
911ba591a9 feat: hide toggle on app detail, left-align toolbar, TPS unit fix
- Move view toggle into compact grid conditional so it only renders
  on the overview page (not app detail /runtime/{slug})
- Left-align the toolbar buttons
- Change TPS format from "x.y/s" to "x.y tps"

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 13:59:25 +02:00
hsiegeln
9d1cf7577a feat: overlay z-index fix, app name navigation, TPS on compact cards
- Bump overlay z-index to 100 so it renders above the sidebar
- App name in compact card navigates to /runtime/{slug} on click
- Add TPS (msg/s) as third metric on compact cards between live
  count and heartbeat

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 13:52:42 +02:00
hsiegeln
1fa897fbb5 feat: move toggle to toolbar, sort apps by name, overlay expand
- Move expand/collapse toggle from stat strip to dedicated toolbar
  below KPIs
- Sort app groups alphabetically by name
- Expanded card overlays from clicked card position instead of
  pushing other cards down
- Viewport constraint: overlay flips right-alignment and limits
  height when near edges

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 13:48:39 +02:00
hsiegeln
9f7951aa2b docs: add compact view to runtime section of ui rules
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 13:42:26 +02:00
hsiegeln
61df59853b feat: add expand/collapse animation for compact card toggle
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 13:39:48 +02:00
hsiegeln
5229e08b27 feat: add compact app cards with inline expand to runtime dashboard
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 13:37:58 +02:00
hsiegeln
d0c2fd1ac3 feat: add view mode state and toggle to runtime dashboard
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 13:37:16 +02:00
hsiegeln
5c94881608 style: add compact view CSS classes for runtime dashboard
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 13:35:33 +02:00
hsiegeln
23d24487d1 docs: add runtime compact view implementation plan
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 13:32:14 +02:00
hsiegeln
bf289aa1b1 docs: add runtime compact view design spec
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 13:27:53 +02:00
hsiegeln
78396a2796 fix: sidebar route selection and missing routes after server restart
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m20s
CI / docker (push) Successful in 1m10s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s
Two sidebar bugs fixed:

1. Route entries never highlighted on navigation because sidebar-utils
   generated /apps/ paths for route children while effectiveSelectedPath
   normalizes to /exchanges/. The design system does exact string matching.

2. Routes disappeared from sidebar when agents had no recent exchange
   data. Heartbeat carried routeStates (with route IDs as keys) but
   AgentRegistryService.heartbeat() never updated AgentInfo.routeIds.
   After server restart, auto-heal registered agents with empty routes,
   leaving ClickHouse (24h window) as the only discovery source.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 12:42:01 +02:00
hsiegeln
810f493639 chore: track .claude/rules/ and add self-maintenance instruction
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m23s
CI / docker (push) Successful in 5m22s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 44s
Un-ignore .claude/rules/ so path-scoped rule files are shared via git.
Add instruction in CLAUDE.md to update rule files when modifying classes,
controllers, endpoints, or metrics — keeps rules current as part of
normal workflow rather than requiring separate maintenance.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 09:26:53 +02:00
hsiegeln
95730b02ad refactor: decompose CLAUDE.md into path-scoped rules for reduced startup context
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m46s
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / docker (push) Has been cancelled
Fix all factual inaccuracies (Ed25519 methods, syncOidcRoles reference,
cpuShares->cpuRequest, deployment sub-tab order, ClickHouseLogStore package,
OidcConfig fields) and add 30+ missing classes/controllers.

Move reference material (class maps, Docker orchestration, metrics tables,
UI structure, CI/CD) into .claude/rules/ with path-scoped loading. Remove
duplicated GitNexus section (already in AGENTS.md, now in .claude/rules/).

Startup context reduced from ~13K to ~4K tokens (69% reduction).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 09:24:26 +02:00
hsiegeln
3666994b9e refactor: simplify Docker entrypoints — agent bundles log appender
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m54s
CI / docker (push) Successful in 1m16s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 57s
SonarQube / sonarqube (push) Successful in 4m0s
The agent shaded JAR now includes the log appender classes. Remove
PropertiesLauncher, -Dloader.path, and separate appender JAR references.

All JVM types now use: java -javaagent:/app/agent.jar -jar app.jar
Plain Java uses -cp with explicit main class. Native runs binary directly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 01:20:48 +02:00
hsiegeln
a4a5986f38 feat: use cameleer.processorId MDC key for precise log-to-processor correlation
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m52s
CI / docker (push) Successful in 1m54s
CI / deploy (push) Successful in 56s
CI / deploy-feature (push) Has been skipped
LogTab now checks mdc['cameleer.processorId'] first when filtering logs
for a selected processor node, falling back to fuzzy message/loggerName
matching for older agents without the new MDC key.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 00:19:02 +02:00
hsiegeln
859cf7c10d fix: support pre-3.2 Spring Boot JARs in runtime entrypoint
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m39s
CI / docker (push) Successful in 1m38s
CI / deploy (push) Successful in 47s
CI / deploy-feature (push) Has been skipped
RuntimeDetector now derives the correct PropertiesLauncher FQN from
the JAR manifest Main-Class package. Spring Boot 3.2+ uses
org.springframework.boot.loader.launch.PropertiesLauncher, pre-3.2
uses org.springframework.boot.loader.PropertiesLauncher.

DockerRuntimeOrchestrator uses the detected class instead of a
hardcoded 3.2+ reference, falling back to 3.2+ when not auto-detected.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 23:21:01 +02:00
hsiegeln
7961c6e18c fix: expose ClickHouse HTTP port via NodePort for remote access
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m17s
CI / docker (push) Successful in 1m3s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 42s
Add cameleer-clickhouse-external Service (NodePort 30123) matching the
pattern used by cameleer-postgres-external (NodePort 30432).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 23:01:46 +02:00
hsiegeln
251c88fa63 fix: prefer cameleer.exchangeId MDC key for log correlation
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m57s
CI / docker (push) Successful in 1m6s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
The agent now sets cameleer.exchangeId in MDC (persists across processor
executions, unlike Camel's camel.exchangeId which is scoped to MDCUnitOfWork).
For ON_COMPLETION exchange copies, the agent uses the parent's exchange ID.

Server changes:
- ClickHouseLogStore ingestion: extract exchange_id preferring
  cameleer.exchangeId, falling back to camel.exchangeId
- ClickHouseLogStore search: match exchangeId filter against exchange_id
  column OR cameleer.exchangeId OR camel.exchangeId in MDC
- Update CLAUDE.md with log exchange correlation documentation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 22:35:28 +02:00
hsiegeln
64af49e0b5 fix: improve sidebar layout with scrollable sections and bottom-pinned admin
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 2m1s
CI / docker (push) Successful in 2m25s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 43s
- Applications section: maxHeight 50vh with scroll overflow
- Starred section: maxHeight 30vh with scroll overflow
- Admin section: pinned to bottom of sidebar via position="bottom"
- Update design-system to 0.1.54 (sidebar section maxHeight, position props)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 22:09:10 +02:00
hsiegeln
e4b8f1bab4 fix: allow app creation without JAR when deploy is disabled
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m21s
CI / docker (push) Successful in 1m7s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
canSubmit no longer requires a JAR file when "Create only" is selected.
JAR upload and deploy steps are skipped when no file is provided.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 20:38:53 +02:00
hsiegeln
457650012b fix: resolve UI glitches and improve consistency
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m22s
CI / docker (push) Successful in 1m36s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s
- Sidebar: make +App button more subtle (lower opacity, brightens on hover)
- Sidebar: add filter chips to hide empty routes and offline/stale apps
- Sidebar: hide filter chips and +App button when sidebar is collapsed
- Exchange table: reorder columns to Status, Attributes, App, Route, Started, Duration; remove ExchangeId and Agent columns
- Exchange detail log tab: query by exchangeId only (no applicationId required), filter by processorId when processor selected
- KPI tooltips: styled tooltips with current/previous values, time period labels, percentage change, themed with DS variables
- KPI tooltips: fix overflow by left-aligning first two and right-aligning last two
- Exchange detail: show full datetime (YYYY-MM-DD HH:mm:ss.SSS) for start/end times
- Status labels: unify to title-case (Completed, Failed, Running) across all views
- Status filter buttons: match title-case labels (Completed, Warning, Failed, Running)
- Create app: show full external URL using routingDomain from env config or window.location.origin fallback
- Create app: add Runtime Type selector and Custom Arguments to Resources tab
- Create app: add Sensitive Keys tab with agent defaults, global keys, and app-specific keys (matching admin page design)
- Create app: add placeholder text to all Input fields for consistency
- Update design-system to 0.1.52 (sidebar collapse toggle fix)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 19:41:36 +02:00
hsiegeln
091dfb34d0 fix: rename Cameleer3ServerApplication.java to match class name
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 2m2s
CI / docker (push) Successful in 4m15s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 2m4s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 16:15:45 +02:00
hsiegeln
92c412b198 chore: update design-system to 0.1.51 (renamed assets)
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 1m12s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 15:57:49 +02:00
hsiegeln
cb3ebfea7c chore: rename cameleer3 to cameleer
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 18s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Rename Java packages from com.cameleer3 to com.cameleer, module
directories from cameleer3-* to cameleer-*, and all references
throughout workflows, Dockerfiles, docs, migrations, and pom.xml.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 15:28:42 +02:00
hsiegeln
1077293343 fix: pull base image on deploy and fix registry prefix default
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m16s
CI / docker (push) Successful in 1m8s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s
The PULL_IMAGE deploy stage was a no-op — Docker only pulls on create
if the image is missing entirely, not when a newer version exists.
DeploymentExecutor now calls orchestrator.pullImage() to fetch the
latest base image from the registry before creating containers.

Also fixes the default base image from 'cameleer-runtime-base:latest'
(local-only name) to the fully qualified registry path
'gitea.siegeln.net/cameleer/cameleer-runtime-base:latest'.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 11:29:58 +02:00
hsiegeln
fd806b9f2d fix: unify container/agent log identity and fix multi-replica log capture
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m21s
CI / docker (push) Successful in 1m18s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 42s
Four logging pipeline fixes:

1. Multi-replica startup logs: remove stopLogCaptureByApp from
   SseConnectionManager — container log capture now expires naturally
   after 60s instead of being killed when the first agent connects SSE.
   This ensures all replicas' bootstrap output is captured.

2. Unified instance_id: container logs and agent logs now share the same
   instance identity ({envSlug}-{appSlug}-{replicaIndex}). DeploymentExecutor
   sets CAMELEER_AGENT_INSTANCEID per replica so the agent uses the same
   ID as ContainerLogForwarder. Instance-level log views now show both
   container and agent logs.

3. Labels-first container identity: TraefikLabelBuilder emits cameleer.replica
   and cameleer.instance-id labels. Container names are tenant-prefixed
   ({tenantId}-{envSlug}-{appSlug}-{idx}) for global Docker daemon uniqueness.

4. Environment filter on log queries: useApplicationLogs now passes the
   selected environment to the API, preventing log leakage across environments.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 10:54:05 +02:00
hsiegeln
bf2d07f3ba fix: break circular dependency between runtimeOrchestrator and containerLogForwarder
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m33s
CI / docker (push) Successful in 1m11s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 40s
SonarQube / sonarqube (push) Successful in 3m34s
Extract DockerClient creation into a standalone bean so both
runtimeOrchestrator and containerLogForwarder depend on it directly
instead of on each other. DockerRuntimeOrchestrator now receives
DockerClient via constructor instead of creating it in @PostConstruct.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 00:06:40 +02:00
hsiegeln
9c912fe694 feat: distinguish agent re-registration from first registration
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m16s
CI / docker (push) Successful in 1m38s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Failing after 2m19s
Detect when an agent instance already exists in the registry and record
a RE_REGISTERED event with route count and capabilities instead of a
generic REGISTERED event. UI shows a refresh icon for re-registrations.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 23:57:20 +02:00
hsiegeln
33b0bc4d98 fix: cast LogEntryResponse to LogEntry for StartupLogPanel type safety
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / build (push) Has been cancelled
The DS LogViewer expects level as a string union, but the API response
type uses plain string. Cast at the call site to fix the TS build error.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 23:56:01 +02:00
hsiegeln
7a63135d26 fix: scope pg_stat_activity queries by ApplicationName for tenant isolation
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 36s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
DatabaseAdminController's active-queries and kill-query endpoints could
expose SQL text from other tenants sharing the same PostgreSQL instance.
Added ApplicationName=tenant_{id} to the JDBC URL and filter
pg_stat_activity by application_name so each tenant only sees its own
connections.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 23:51:13 +02:00
hsiegeln
c33b2a9048 docs: update CLAUDE.md with container startup log capture documentation
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 37s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Add ContainerLogForwarder, StartupLogPanel, useStartupLogs to key classes
and UI files. Document log capture lifecycle and source badge rendering.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 23:36:38 +02:00
hsiegeln
0dafad883e chore: bump @cameleer/design-system to 0.1.49
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 37s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
LogViewer now renders source badges (container/app/agent) on log entries.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 23:33:19 +02:00
hsiegeln
1287952387 feat: show startup logs panel below deployment progress
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 23:24:08 +02:00
hsiegeln
81dd81fc07 feat: add container source option to log source filters
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 23:21:34 +02:00
hsiegeln
e7732703a6 feat: add StartupLogPanel component for deployment startup logs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 23:21:26 +02:00
hsiegeln
119cf912b8 feat: add useStartupLogs hook for container startup log polling
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 23:21:23 +02:00
hsiegeln
81f42d5409 feat: stop container log capture on Docker die/oom events
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 23:19:26 +02:00
hsiegeln
49c7de7082 feat: stop container log capture when agent SSE connects
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 23:19:17 +02:00
hsiegeln
4940bf3376 feat: start log capture when deployment replicas are created
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 23:18:56 +02:00
hsiegeln
de85a861c7 feat: wire ContainerLogForwarder into DockerRuntimeOrchestrator
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 23:17:54 +02:00
hsiegeln
729944d3ac feat: add ContainerLogForwarder for Docker log streaming to ClickHouse
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 23:15:49 +02:00
hsiegeln
9c65a3c3b9 feat: add log capture methods to RuntimeOrchestrator interface
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 23:14:31 +02:00
hsiegeln
8fabc2b308 docs: add container startup log capture implementation plan
12 tasks covering RuntimeOrchestrator extension, ContainerLogForwarder,
deployment/SSE/event monitor integration, and UI startup log panel.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 23:12:01 +02:00
hsiegeln
14215bebec docs: add container startup log capture design spec
Covers streaming Docker logs to ClickHouse until agent SSE connect,
deployment log panel UI, and source badge in general log views.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 23:04:24 +02:00
hsiegeln
92d7f5809b improve: redesign SensitiveKeysPage with better layout and information hierarchy
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m29s
CI / docker (push) Successful in 1m11s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Show agent built-in defaults as reference Badge pills, separate editable keys
section with count badge, amber-highlighted push toggle, right-aligned save
button. Fix info text: keys add to defaults, not replace. Add ClaimMapping
controller to CLAUDE.md.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 19:03:45 +02:00
hsiegeln
9ac8e3604c fix: allow testing claim mapping rules before saving and keep rows editable after test
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m27s
CI / docker (push) Successful in 1m10s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 41s
The test endpoint now accepts inline rules from the client instead of reading
from the database, so unsaved rules can be tested. Matched rows show the
checkmark alongside action buttons instead of replacing them.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 18:52:18 +02:00
hsiegeln
891abbfcfd docs: add sensitive keys feature documentation
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m26s
CI / docker (push) Successful in 1m8s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 41s
- CLAUDE.md: add SensitiveKeysConfig, SensitiveKeysRepository, SensitiveKeysMerger
  to core admin classes; add SensitiveKeysAdminController endpoint; add
  PostgresSensitiveKeysRepository; add sensitive keys convention; add admin page
  to UI structure
- Design spec and implementation plan for the feature

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 18:29:15 +02:00
hsiegeln
7b73b5c9c5 feat: add per-app sensitive keys section to AppConfigDetailPage
Adds sensitiveKeys/globalSensitiveKeys/mergedSensitiveKeys fields to
ApplicationConfig, unwraps the new AppConfigResponse envelope in
useApplicationConfig, and renders an editable Sensitive Keys section
with read-only global pills and add/remove app-specific key tags.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 18:26:05 +02:00
hsiegeln
96780db9ad feat: wire SensitiveKeysPage into router and admin sidebar 2026-04-14 18:24:13 +02:00
hsiegeln
813ec6904e feat: add SensitiveKeysPage admin page
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 18:23:34 +02:00
hsiegeln
06c719f0dd feat: add sensitive keys API query hooks 2026-04-14 18:22:28 +02:00
hsiegeln
77aa3c3d6f test: add SensitiveKeysAdminController integration tests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 18:21:46 +02:00
hsiegeln
2fad8811c6 feat: merge global sensitive keys into app config GET and SSE push
- GET /config/{app} now returns AppConfigResponse with globalSensitiveKeys and mergedSensitiveKeys alongside the config
- PUT /config/{app} merges global + per-app sensitive keys before pushing CONFIG_UPDATE to agents via SSE
- extractSensitiveKeys() uses JsonNode reflection to avoid compile-time dependency on cameleer3-common getSensitiveKeys()
- SensitiveKeysRepository injected as new constructor parameter

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 18:19:59 +02:00
hsiegeln
28e38e4dee fix: add audit logging to GET /admin/sensitive-keys
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 18:17:42 +02:00
hsiegeln
c3892151a5 feat: add SensitiveKeysAdminController with fan-out support
GET/PUT /api/v1/admin/sensitive-keys (ADMIN only). PUT accepts optional
pushToAgents param — when true, fans out merged global+per-app sensitive
keys to all live agents via CONFIG_UPDATE SSE commands with 10-second
shared deadline. Per-app keys extracted via JsonNode to avoid depending
on ApplicationConfig.getSensitiveKeys() not yet in the published
cameleer3-common jar. Includes audit logging on every PUT.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 18:16:27 +02:00
hsiegeln
84641fe81a feat: add PostgresSensitiveKeysRepository 2026-04-14 18:08:45 +02:00
hsiegeln
d72a6511da feat: add SensitiveKeysMerger with case-insensitive union dedup
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 18:07:53 +02:00
hsiegeln
86b6c85aa7 feat: add SensitiveKeysConfig record and SensitiveKeysRepository interface 2026-04-14 18:06:12 +02:00
hsiegeln
dcd0b4ebcd fix: use managed assignments for OIDC fallback role paths
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m31s
CI / docker (push) Successful in 1m7s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s
The roles-claim and default-roles fallback paths in applyClaimMappings
were using assignRoleToUser (origin='direct'), causing OIDC-derived
roles to accumulate across logins and never be cleared. Changed both
to assignManagedRole (origin='managed') so all OIDC-assigned roles
are cleared and re-evaluated on every login, same as claim mapping
rules. Only roles assigned directly via the admin UI are preserved.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 17:19:20 +02:00
hsiegeln
58e802e2d4 feat: close modal on successful apply, update design spec
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m24s
CI / docker (push) Successful in 1m10s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 39s
Modal auto-closes after Apply succeeds. Design spec updated to reflect
implemented behavior: local-edit-then-apply pattern, target select
dropdowns, amber pill for add-to-group, close-on-success.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 17:12:39 +02:00
hsiegeln
9959e30e1e fix: use --amber DS variable for add-to-group pill color
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 17:09:31 +02:00
hsiegeln
5edefb2180 refactor: switch claim mapping editor to local-edit-then-apply pattern
All edits (add, edit, delete, reorder) now modify local state only.
Cancel discards changes, Apply diffs local vs server and issues the
necessary create/update/delete API calls. Target selects now include
a placeholder option. Footer shows Cancel and Apply buttons.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 17:07:36 +02:00
hsiegeln
0e87161426 feat: use select dropdowns for target role/group in claim mapping editor
Populate target field from existing roles (assign role) or groups
(add to group) instead of free-text input, preventing typos.
Switching action resets the target selection.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 17:02:09 +02:00
hsiegeln
c02fd77c30 fix: use correct DS CSS variables for modal background
Replace non-existent --surface-1/--surface-2 with --bg-raised (modal)
and --bg-hover (subtle backgrounds) from the design system.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 16:59:50 +02:00
hsiegeln
a3ec0aaef3 fix: address code review findings for claim mapping rules editor
- Bump all font sizes from 11px/10px to 12px (project minimum)
- Fix handleMove race condition: use mutateAsync + Promise.all
- Clear stale test results after rule create/edit/delete/reorder
- Replace inline styles with CSS module classes in OidcConfigPage
- Remove dead .editRow CSS class
- Replace inline chevron with Lucide icon

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 16:58:06 +02:00
hsiegeln
3985bb8a43 feat: wire claim mapping rules modal into OIDC config page
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 16:51:28 +02:00
hsiegeln
e8a697d185 feat: add claim mapping rules editor modal component
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 16:50:00 +02:00
hsiegeln
344700e29e feat: add React Query hooks for claim mapping rules API
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 16:46:40 +02:00
hsiegeln
f110169d54 feat: add POST /test endpoint for claim mapping rule evaluation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 16:42:54 +02:00
hsiegeln
90ae1d6a14 fix: include properties in hasTrace for ProcessorExecution path
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m39s
CI / docker (push) Failing after 47s
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Now that cameleer3-common has getInputProperties/getOutputProperties on
ProcessorExecution, add the check to the processors_json deserialization
path as well.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 14:34:29 +02:00
hsiegeln
05d91c16e7 fix: include properties in hasTrace check for ProcessorRecord paths
Some checks failed
CI / build (push) Successful in 1m57s
CI / cleanup-branch (push) Has been skipped
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / docker (push) Has been cancelled
The hasTrace flag on ProcessorNode now also checks inputProperties and
outputProperties on the flat-record code paths (buildTreeBySeq and
buildTreeByProcessorId). The ProcessorExecution path (processors_json)
will be updated once cameleer3-common publishes the new snapshot.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 14:32:18 +02:00
hsiegeln
0827fd21e3 feat: persist and display exchange properties from agent
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m59s
CI / docker (push) Successful in 2m13s
CI / deploy (push) Successful in 58s
CI / deploy-feature (push) Has been skipped
Add support for exchange properties sent by the agent alongside headers.
Properties flow through the same pipeline as headers: ClickHouse columns
(input_properties, output_properties) on both executions and
processor_executions tables, MergedExecution record, ChunkAccumulator
extraction, DetailService snapshot, and REST API response.

UI adds a Properties tab next to Headers in the process diagram detail
panel, with the same input/output split table layout.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 14:23:53 +02:00
hsiegeln
199d0259cd feat: add "+ App" shortcut button to sidebar Applications header
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m20s
CI / docker (push) Successful in 1m12s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Adds a subtle "+ App" button in the sidebar section header for quick
app creation without navigating to the Deployments tab first. Only
visible to OPERATOR and ADMIN roles.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 09:10:41 +02:00
hsiegeln
ac680b7f3f refactor: prefix all third-party service names with cameleer-
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 2m7s
CI / docker (push) Successful in 1m33s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 1m51s
SonarQube / sonarqube (push) Successful in 3m28s
Rename all Docker/K8s service names, DNS hostnames, secrets, volumes,
and manifest files to use the cameleer- prefix, making it clear which
software package each container belongs to.

Services renamed:
- postgres → cameleer-postgres
- clickhouse → cameleer-clickhouse
- logto → cameleer-logto
- logto-postgresql → cameleer-logto-postgresql
- traefik (service) → cameleer-traefik
- postgres-external → cameleer-postgres-external

Secrets renamed:
- postgres-credentials → cameleer-postgres-credentials
- clickhouse-credentials → cameleer-clickhouse-credentials
- logto-credentials → cameleer-logto-credentials

Volumes renamed:
- pgdata → cameleer-pgdata
- chdata → cameleer-chdata
- certs → cameleer-certs
- bootstrapdata → cameleer-bootstrapdata

K8s manifests renamed:
- deploy/postgres.yaml → deploy/cameleer-postgres.yaml
- deploy/clickhouse.yaml → deploy/cameleer-clickhouse.yaml
- deploy/logto.yaml → deploy/cameleer-logto.yaml

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 22:51:08 +02:00
hsiegeln
fe283674fb fix: use relative asset paths with always-injected <base> tag
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m22s
CI / docker (push) Successful in 1m11s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 43s
Switch Vite base back to './' (relative paths) and always inject
<base href="${BASE_PATH}"> in the entrypoint, even when BASE_PATH=/.

This fixes asset loading for both deployment modes:
- Single-instance: <base href="/"> resolves ./assets/x.js to /assets/x.js
- SaaS tenant: <base href="/t/slug/"> resolves to /t/slug/assets/x.js

Previously base:'/' produced absolute /assets/ paths that the <base>
tag couldn't redirect, breaking SaaS tenants. And base:'./' without
<base> broke deep URLs in single-instance mode. Always injecting the
tag makes relative paths work universally.

The patched server-ui-entrypoint.sh in cameleer-saas (which rewrote
absolute href/src attributes via sed) is no longer needed and can be
removed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 21:30:00 +02:00
hsiegeln
67e2c1a531 fix: revert relative base path and fix processor table overflow
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m22s
CI / docker (push) Successful in 1m10s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 42s
Revert base: './' back to '/' — relative asset paths break on deep
URLs like /dashboard/app/route where the browser resolves assets to
/dashboard/app/assets/ instead of /assets/.

Also fix processor metrics table clipping: remove flex:1/min-height:0
from .processorSection so the table takes its natural content height
and the page scrolls to show all rows (was clipping at ~12 of 18).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 21:16:59 +02:00
hsiegeln
025e1cfc34 docs: update CLAUDE.md GitNexus stats
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 2m3s
CI / docker (push) Successful in 32s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 40s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 21:04:59 +02:00
hsiegeln
d942425cb1 fix: use relative base path for Vite assets
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m16s
CI / docker (push) Successful in 1m8s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
When the server-ui is deployed under a subpath (/t/{slug}/), absolute
asset paths (/assets/...) resolve to the domain root instead of the
subpath, causing 404s. Using './' makes asset URLs relative to the
HTML page, so they resolve correctly regardless of mount path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 20:52:49 +02:00
hsiegeln
882198d59a fix: use lagInFrame instead of lag for ClickHouse compatibility
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m29s
CI / docker (push) Successful in 1m10s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 41s
ClickHouse does not have lag() as a window function. Use lagInFrame()
with explicit ROWS BETWEEN frame instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 12:06:25 +02:00
hsiegeln
5edb833d21 chore: remove stats table migration logic from ClickHouseSchemaInitializer
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m48s
CI / docker (push) Successful in 1m9s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 1m44s
Not needed yet -- all deployments are under our control and can be
reset manually if the old schema is encountered.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 11:51:34 +02:00
hsiegeln
3f2392b8f7 refactor: consolidate ClickHouse init.sql as clean idempotent schema
Some checks failed
CI / build (push) Has been cancelled
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / cleanup-branch (push) Has been cancelled
Rewrite init.sql as a pure CREATE IF NOT EXISTS file with no DROP or
INSERT statements. Safe for repeated runs on every startup without
corrupting aggregated stats data.

Old deployments with count()-based stats tables are migrated
automatically: ClickHouseSchemaInitializer checks system.columns for
the old AggregateFunction(count) type and drops those tables before
init.sql recreates them with the correct uniq() schema. This runs
once per table and is a no-op on fresh installs or already-migrated
deployments.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 11:49:53 +02:00
hsiegeln
6b558b649d fix: use absolute asset paths and prevent route-level runtime navigation
Some checks failed
CI / build (push) Has been cancelled
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / cleanup-branch (push) Has been cancelled
Change vite base from './' to '/' so asset paths are absolute. With
relative paths, direct navigation to multi-segment URLs like
/runtime/app/instance resolved assets to /runtime/assets/ which 404'd.

Also fix sidebar navigation: clicking a route while on the runtime tab
no longer navigates to /runtime/{appId}/{routeId} (which the runtime
page interprets as an instanceId). It stays at /runtime/{appId}.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 11:24:15 +02:00
hsiegeln
e57343e3df feat: add delta mode for counter metrics using ClickHouse lag()
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m17s
CI / docker (push) Successful in 1m12s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 42s
Counter metrics like chunks.exported.count are monotonically increasing.
Add mode=delta query parameter to the agent metrics API that computes
per-bucket deltas server-side using ClickHouse lag() window function:
max(value) per bucket, then greatest(0, current - previous) to get the
increase per period with counter-reset handling.

The chunks exported/dropped charts now show throughput per bucket
instead of the ever-increasing cumulative total.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 10:56:06 +02:00
hsiegeln
ae908fb382 fix: deduplicate all stats MVs and preserve loop iterations
All checks were successful
CI / build (push) Successful in 2m25s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 1m20s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 1m3s
SonarQube / sonarqube (push) Successful in 3m49s
Extend uniq-based dedup from processor tables to all stats tables
(stats_1m_all, stats_1m_app, stats_1m_route). Execution-level tables
use uniq(execution_id). Processor-level tables now use
uniq(concat(execution_id, toString(seq))) so loop iterations (same
exchange, different seq) are counted while chunk retry duplicates
(same exchange+seq) are collapsed.

All stats tables are dropped, recreated, and backfilled from raw
data on startup. All Java queries updated: countMerge -> uniqMerge,
countIfMerge -> uniqIfMerge.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 23:48:01 +02:00
hsiegeln
1872d46466 fix: remove semicolons from SQL comments that broke schema initializer
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m30s
CI / docker (push) Successful in 1m11s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 50s
The ClickHouseSchemaInitializer splits on semicolons before filtering
comments, so semicolons inside comment text created invalid statements.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 23:17:35 +02:00
hsiegeln
e2f784bf82 fix: deduplicate processor stats using uniq(execution_id)
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m17s
CI / docker (push) Successful in 1m10s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s
Processor execution counts were inflated by duplicate inserts into the
plain MergeTree processor_executions table (chunk retries, reconnects).
Replace count()/countIf() with uniq(execution_id)/uniqIf() in both
stats_1m_processor and stats_1m_processor_detail MVs so each exchange
is counted once per processor regardless of duplicates.

Tables are dropped and rebuilt from raw data on startup. MV created
after backfill to avoid double-counting.

Also adds stats_1m_processor_detail to the catalog purge list (was
missing).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 23:12:00 +02:00
hsiegeln
27f2503640 fix: clean up runtime UI and harden session expiry handling
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m27s
CI / docker (push) Successful in 1m13s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 42s
Remove redundant "X/X LIVE" badge from runtime page, breadcrumb trail
and routes section from agent detail page (pills moved into Process
Information card). Fix session expiry: guard against concurrent 401
refresh races and skip re-entrant triggers on auth endpoints.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 22:33:44 +02:00
hsiegeln
ffce3b714f chore: update design system to 0.1.48 (x-axis tick label overlap fix)
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 2m24s
CI / docker (push) Successful in 2m12s
CI / deploy (push) Successful in 54s
CI / deploy-feature (push) Has been skipped
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 22:12:12 +02:00
hsiegeln
d34abeb5cb fix: diagram zoom not working on initial render
All checks were successful
CI / build (push) Successful in 2m10s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 1m32s
CI / deploy (push) Successful in 52s
CI / deploy-feature (push) Has been skipped
The wheel event listener was attached in a useEffect with empty deps,
but the SVG element doesn't exist during the loading state. Switch
svgRef from a plain ref to a callback ref that triggers re-attachment
when the SVG element becomes available after data loads.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 22:05:35 +02:00
hsiegeln
aaf9a00d67 fix: replace native SVG tooltip with styled heatmap tooltip overlay
Some checks failed
CI / build (push) Successful in 2m12s
CI / cleanup-branch (push) Has been skipped
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / docker (push) Has been cancelled
Renders an HTML tooltip below hovered diagram nodes with processor
metrics (avg, p99, % time, invocations, error rate). Styled inline
with the existing NodeToolbar pattern — positioned via screen-space
coordinates, uses DS tokens for background/border/shadow/typography.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 22:03:05 +02:00
hsiegeln
00c9a0006e feat: rework runtime charts and fix time range propagation
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m28s
CI / docker (push) Successful in 1m10s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 41s
Runtime page (AgentInstance):
- Rearrange charts: CPU, Memory, GC (top); Threads, Chunks Exported,
  Chunks Dropped (bottom). Removes throughput/error charts (belong on
  Dashboard, not Runtime).
- Pass global time range (from/to) to useAgentMetrics — charts now
  respect the time filter instead of always showing last 60 minutes.
- Bottom row (logs + timeline) fills remaining vertical space.

Dashboard L3:
- Processor metrics section fills remaining vertical space.
- Chart x-axis uses timestamps instead of bucket indices.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 21:59:38 +02:00
hsiegeln
98ce7c2204 feat: combine process diagram and processor table into toggled card
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m25s
CI / docker (push) Successful in 1m9s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 40s
Dashboard L3 now shows a single Processor Metrics card with
Diagram/Table toggle buttons. The diagram shows native tooltips on
hover with full processor metrics (avg, p99, invocations, error rate,
% time).

Also fixes:
- Chart x-axis uses actual timestamps instead of bucket indices
- formatDurationShort uses locale formatting with max 3 decimals

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 21:40:43 +02:00
hsiegeln
66248f6b1c fix: accept logs from unregistered agents using JWT claims
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m24s
CI / docker (push) Successful in 1m7s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s
After server restart, agents send logs before re-registering. Instead
of dropping these logs, fall back to application and environment from
the JWT token claims. Only drops logs when neither registry nor JWT
provide an applicationId.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 21:29:05 +02:00
hsiegeln
ce8a2a1525 fix: use raw timestamp string for throughput/error chart data
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m22s
CI / docker (push) Successful in 1m10s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 56s
Avoids Date round-trip that crashes with toISOString() on invalid
timestamps from the timeseries API.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 21:25:00 +02:00
hsiegeln
65ed94f0e0 fix: commit DS v0.1.47 dependency update (missed in migration commit)
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m17s
CI / docker (push) Successful in 1m34s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 21:19:56 +02:00
hsiegeln
b1e1f789c5 ci: retrigger build (DS v0.1.47 publish race condition)
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 37s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 21:13:09 +02:00
hsiegeln
0dae1f1cc7 feat: migrate agent charts to ThemedChart + Recharts
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / build (push) Has been cancelled
Replace custom LineChart/AreaChart/BarChart usage with ThemedChart
wrapper. Data format changed from ChartSeries[] to Recharts-native
flat objects. Uses DS v0.1.47.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 19:44:55 +02:00
hsiegeln
a0af53f8f5 chore: update design system to 0.1.46 (responsive charts, timestamp tooltips)
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m17s
CI / docker (push) Successful in 1m31s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 42s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 18:52:53 +02:00
hsiegeln
aa91b867c5 fix: use Date x-values in agent charts for proper time axes
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / build (push) Has been cancelled
All chart series now use Date objects from the API response instead
of integer indices. This gives proper date/time on x-axes and in
tooltips (leveraging DS v0.1.46 responsive charts + timestamp
tooltips). GC chart switched from BarChart to AreaChart for
consistency with Date x-values.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 18:51:18 +02:00
hsiegeln
0b19fe8319 fix: update UI metric names from JMX to Micrometer convention
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 2m34s
CI / docker (push) Successful in 2m3s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 53s
Agent team migrated from JMX to Micrometer metrics. Update the 5
hardcoded metric names in AgentInstance.tsx JVM charts:
- jvm.cpu.process → process.cpu.usage.value
- jvm.memory.heap.used → jvm.memory.used.value
- jvm.memory.heap.max → jvm.memory.max.value
- jvm.threads.count → jvm.threads.live.value
- jvm.gc.time → jvm.gc.pause.total_time

Server backend is unaffected (generic MetricsSnapshot storage).
CLAUDE.md updated with full agent metric name reference.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 18:35:29 +02:00
hsiegeln
dc29afd4c8 docs: add Prometheus metrics reference to CLAUDE.md
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m15s
CI / docker (push) Successful in 3m42s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 43s
Lists all business metrics (gauges, counters, timers) with their
tags and source classes, plus agent container label mapping table.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 18:29:01 +02:00
hsiegeln
6bf7175a6c feat: add Micrometer Prometheus metrics to server
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 2m36s
CI / deploy (push) Has been cancelled
CI / docker (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
Adds micrometer-registry-prometheus and exposes /api/v1/prometheus
endpoint (unauthenticated for scraping). ServerMetrics component
provides business metrics beyond default JVM/HTTP:

Gauges: agents by state, SSE connections, buffer depths (execution,
processor, log, metrics), accumulator pending exchanges.

Counters: ingestion drops (buffer_full, no_agent, no_identity),
agent transitions (went_stale, went_dead, recovered), deployment
outcomes (running, failed, degraded), auth failures (invalid_token,
revoked, oidc_rejected).

Timers: ClickHouse flush duration by type, deployment duration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 18:23:27 +02:00
hsiegeln
caaa1ab0cc feat: add Prometheus docker_sd_configs labels to agent containers
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m26s
CI / docker (push) Successful in 1m12s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 43s
Labels prometheus.scrape, prometheus.path, and prometheus.port are now
set on every deployed container based on the resolved runtime type,
enabling automatic Prometheus service discovery via docker_sd_configs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 18:00:32 +02:00
hsiegeln
c1fbe1a63a chore: update design system to 0.1.45 (sidebar version styling)
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 2m0s
CI / docker (push) Successful in 1m59s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 42s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 17:28:29 +02:00
hsiegeln
b32d4adaa5 fix: show empty state for unmanaged apps on Deployments tab
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m19s
CI / docker (push) Successful in 1m7s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 39s
Previously showed an infinite spinner because unmanaged apps have no
PostgreSQL record. Now shows an "Unmanaged Application" message with
a link to create a managed app.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 17:16:18 +02:00
hsiegeln
dadab2b5f7 fix: align payloadCaptureMode default with agent (BOTH, not NONE)
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m26s
CI / docker (push) Successful in 1m13s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 49s
Server defaultConfig() and UI fallbacks returned "NONE" for payload
capture, but the agent defaults to "BOTH". This caused unwanted
reconfiguration when users saved other settings — payload capture
would silently change from the agent's default BOTH to NONE.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 17:12:21 +02:00
hsiegeln
51a4317440 fix: optimistically remove dismissed app from sidebar cache
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m26s
CI / docker (push) Successful in 1m14s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s
Sets query cache immediately on dismiss success so the sidebar updates
without waiting for the catalog refetch to complete.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 17:05:04 +02:00
hsiegeln
f84bdebd09 fix: confirm dialog asks user to type app name instead of generic text
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m16s
CI / docker (push) Successful in 1m13s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 16:59:26 +02:00
hsiegeln
c10d207d98 fix: add spacing below dismiss alert on Runtime page
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m20s
CI / deploy (push) Has been cancelled
CI / docker (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 16:58:01 +02:00
hsiegeln
be96336974 feat: add extra Docker networks to container config
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m23s
CI / docker (push) Successful in 1m7s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s
Apps can now join additional Docker networks (e.g., monitoring,
prometheus) configured via containerConfig.extraNetworks. Flows through
the 3-layer config merge. Networks are created if absent and containers
are connected during deployment. UI adds a pill-list field on the
Resources tab (both create and edit views).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 16:53:01 +02:00
hsiegeln
5b6543b167 fix: use ConfirmDialog for dismiss, move warning to top of page
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m20s
CI / docker (push) Successful in 1m8s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 41s
Replace window.confirm with design system ConfirmDialog for the dismiss
action. Move the "No agents connected" section to the top of the Runtime
page using Alert component with warning variant.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 16:43:05 +02:00
hsiegeln
5724b8459d docs: document catalog cleanup, log ingestion logging, and catalog config
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m21s
CI / docker (push) Successful in 29s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 41s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 16:37:46 +02:00
hsiegeln
223a60f374 docs: add orphaned app cleanup design spec
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m19s
CI / docker (push) Successful in 1m8s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 16:21:00 +02:00
hsiegeln
90c82238a0 feat: add orphaned app cleanup — auto-filter stale discovered apps, manual dismiss with data purge
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 16:19:59 +02:00
hsiegeln
d161ad38a8 fix: log deserialization failures on log ingestion endpoint
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m23s
CI / docker (push) Successful in 1m4s
CI / deploy (push) Successful in 41s
CI / deploy-feature (push) Has been skipped
Spring's default handler silently returns 400 for malformed payloads
with no server-side log. Added @ExceptionHandler to catch and WARN with
the agent instance ID and root cause message.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 15:33:57 +02:00
hsiegeln
2d3817b296 fix: downgrade successful log ingestion message to DEBUG
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m26s
CI / docker (push) Successful in 1m6s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 15:29:38 +02:00
hsiegeln
e55ee93dcf fix: add proper logging to log ingestion endpoint
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m47s
CI / docker (push) Successful in 1m38s
CI / deploy (push) Successful in 42s
CI / deploy-feature (push) Has been skipped
Previously the endpoint silently returned 202 for all failures: missing
agent identity, unregistered agents, empty payloads, and buffer-full
drops. Now logs WARN for each failure case with context (instanceId,
entry count, reason). Normal ingestion logged at INFO with accepted
count. Buffer-full drops tracked individually with accepted/dropped
counts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 15:20:07 +02:00
hsiegeln
d02a64709c docs: update documentation for runtime type detection feature
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m25s
CI / docker (push) Successful in 1m14s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 39s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 13:21:43 +02:00
hsiegeln
ee435985a9 feat: add Runtime Type and Custom Arguments fields to deployment Resources tab
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 13:11:59 +02:00
hsiegeln
d5b611cc32 feat: validate runtimeType and customArgs on container config save
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 13:08:52 +02:00
hsiegeln
e941256e6e feat: build Docker entrypoint per runtime type with custom args support
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 13:06:54 +02:00
hsiegeln
f66c8b6d18 feat: add runtimeType and customArgs to ResolvedContainerConfig and ConfigMerger
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 13:04:21 +02:00
hsiegeln
5e28d20e3b feat: run RuntimeDetector on JAR upload and store detected runtime
After versionRepo.create(), detect the runtime type from the saved JAR
via RuntimeDetector and persist the result via updateDetectedRuntime().
Log messages now include the detected runtime type (or 'unknown').

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 13:03:07 +02:00
hsiegeln
f4bbc1f65f feat: add detected_runtime_type and detected_main_class to app_versions
Flyway V10 migration adds the two nullable columns. AppVersion record,
AppVersionRepository interface, and PostgresAppVersionRepository are
updated to carry and persist detected runtime information.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 13:01:24 +02:00
hsiegeln
cbf29a5d87 feat: add RuntimeType enum and RuntimeDetector for JAR probing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 12:59:00 +02:00
hsiegeln
51cc2c1d3c ci: retrigger build (cameleer3-common SNAPSHOT updated with source field)
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m15s
CI / docker (push) Successful in 34s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 1m33s
2026-04-12 10:42:44 +02:00
hsiegeln
0603e62a69 fix: revert LogEntry to 7-arg constructor (source is not a ctor param)
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m11s
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / docker (push) Has been cancelled
LogEntry.getSource() exists but source is not a constructor parameter
in cameleer3-common — it uses a default value.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 10:40:48 +02:00
hsiegeln
00115a16ac fix: add source parameter to LogSearchRequest/LogEntry calls in ClickHouseLogStoreIT
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 58s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
All constructor calls updated to include the new source field added
in the log forwarding v2 changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 10:37:56 +02:00
hsiegeln
4d8df86786 docs: update for log forwarding v2 (source field, wire format)
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 58s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
HOWTO.md: log ingestion example updated from LogBatch wrapper to raw
JSON array with source field. CLAUDE.md: added LogIngestionController,
updated LogQueryController with new filters. SERVER-CAPABILITIES.md:
updated log ingestion and query descriptions, ClickHouse table note.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 10:33:03 +02:00
hsiegeln
6b00bf81e3 feat: add log source filter (app/agent) to runtime log viewers
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 1m7s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 10:27:59 +02:00
hsiegeln
b03dfee4f3 feat: log forwarding v2 — accept List<LogEntry>, add source field
Replace LogBatch wrapper with raw List<LogEntry> on the ingestion endpoint.
Add source column to ClickHouse logs table and propagate it through the
storage, search, and HTTP layers (LogSearchRequest, LogEntryResult,
LogEntryResponse, LogQueryController).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 10:25:46 +02:00
hsiegeln
4b18579b11 docs: document infrastructureendpoints flag
All checks were successful
CI / build (push) Successful in 1m49s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 1m37s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 39s
SonarQube / sonarqube (push) Successful in 3m33s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 23:19:58 +02:00
hsiegeln
c8cdd846c0 feat: fetch server capabilities and hide infra tabs when disabled
Adds a useServerCapabilities hook that fetches /api/v1/health once per
session (staleTime: Infinity) and extracts the infrastructureEndpoints
flag. buildAdminTreeNodes now accepts an opts parameter so ClickHouse
and Database tabs are hidden when the server reports infra endpoints as
disabled. LayoutShell wires the hook result into the admin tree memo.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 23:12:30 +02:00
hsiegeln
9de51014e7 feat: expose infrastructureEndpoints flag in health endpoint
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 23:10:15 +02:00
hsiegeln
293d11e52b feat: add infrastructureendpoints flag with conditional DB/CH controllers
Add cameleer.server.security.infrastructureendpoints property (default true) and
@ConditionalOnProperty to DatabaseAdminController and ClickHouseAdminController so
the SaaS provisioner can set CAMELEER_SERVER_SECURITY_INFRASTRUCTUREENDPOINTS=false
to suppress these endpoints (404) on tenant server containers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 23:09:28 +02:00
hsiegeln
ca89a79f8f docs: add infrastructure endpoint visibility implementation plan
14-task plan covering server-side @ConditionalOnProperty flag,
health endpoint capability exposure, UI sidebar filtering,
SaaS provisioner env var, and vendor infrastructure dashboard
with per-tenant PostgreSQL and ClickHouse visibility.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 23:06:41 +02:00
hsiegeln
01b268590d docs: add infrastructure endpoint visibility design spec
Covers restricting DB/ClickHouse admin endpoints in SaaS-managed
server instances via @ConditionalOnProperty flag, and building a
vendor-facing infrastructure dashboard in the SaaS platform with
per-tenant PostgreSQL and ClickHouse visibility.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 22:56:45 +02:00
hsiegeln
7a3256f3f6 docs: update env var and property references to new naming convention
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 2m2s
CI / docker (push) Successful in 32s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 1m35s
HOWTO.md configuration table rewritten with correct cameleer.server.*
property names, grouped by functional area. Removed stale CAMELEER_OIDC_*
env var references. SERVER-CAPABILITIES.md updated with correct env var
names for ingestion and agent registry tuning.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 21:56:19 +02:00
hsiegeln
350e769948 Group container settings under cameleer.server.runtime.container.*
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m21s
CI / docker (push) Successful in 1m2s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Move container resource defaults into their own sub-namespace for
future extensibility:

  cameleer.server.runtime.container.memorylimit → CAMELEER_SERVER_RUNTIME_CONTAINER_MEMORYLIMIT
  cameleer.server.runtime.container.cpushares   → CAMELEER_SERVER_RUNTIME_CONTAINER_CPUSHARES

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 21:33:07 +02:00
hsiegeln
534e936cd4 Group OIDC settings under cameleer.server.security.oidc.*
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m59s
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / docker (push) Has been cancelled
Move OIDC properties into a nested Oidc class within SecurityProperties
for clearer grouping. Env vars gain an extra separator:

  cameleer.server.security.oidc.issueruri     → CAMELEER_SERVER_SECURITY_OIDC_ISSUERURI
  cameleer.server.security.oidc.jwkseturi     → CAMELEER_SERVER_SECURITY_OIDC_JWKSETURI
  cameleer.server.security.oidc.audience      → CAMELEER_SERVER_SECURITY_OIDC_AUDIENCE
  cameleer.server.security.oidc.tlsskipverify → CAMELEER_SERVER_SECURITY_OIDC_TLSSKIPVERIFY

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 21:30:33 +02:00
hsiegeln
60fb5fe21a Remove vestigial clickhouse.enabled flag
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m19s
CI / docker (push) Successful in 1m4s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
ClickHouse is the only storage backend — there is no alternative.
The enabled flag created a false sense of optionality: setting it to
false would crash on startup because most beans unconditionally depend
on the ClickHouse JdbcTemplate.

Remove all @ConditionalOnProperty annotations gating ClickHouse beans,
the enabled property from application.yml, and the K8s manifest entry.
Also fix old property names in AbstractPostgresIT test config.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 21:27:10 +02:00
hsiegeln
8fe48bbf02 Migrate config to cameleer.server.* naming convention
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m52s
CI / docker (push) Successful in 1m30s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Move all configuration properties under the cameleer.server.* namespace
with all-lowercase dot-separated names and mechanical env var mapping
(dots→underscores, uppercase). This aligns with the agent's convention
(cameleer.agent.*) and establishes a predictable pattern across all
components.

Changes:
- Move 6 config prefixes under cameleer.server.*: agent-registry,
  ingestion, security, license, clickhouse, and cameleer.tenant/runtime/indexer
- Rename all kebab-case properties to concatenated lowercase
  (e.g., bootstrap-token → bootstraptoken, jar-storage-path → jarstoragepath)
- Update all env vars to CAMELEER_SERVER_* mechanical mapping
- Fix container-cpu-request/container-cpu-shares mismatch bug
- Remove displayName from AgentRegistrationRequest (redundant with instanceId)
- Update agent container env vars to CAMELEER_AGENT_* convention
- Update K8s manifests and CI workflow for new env var names
- Update CLAUDE.md, HOWTO.md, SERVER-CAPABILITIES.md documentation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 18:10:51 +02:00
hsiegeln
3b95dc777b docs: update CLAUDE.md with route control/replay config, CA import entrypoint
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 2m3s
CI / docker (push) Successful in 36s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 35s
- ResolvedContainerConfig: added routeControlEnabled, replayEnabled
- DeploymentExecutor: documents capability env vars and startup-only nature
- Dockerfile: documents docker-entrypoint.sh CA cert import

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 12:07:26 +02:00
hsiegeln
e37003442a feat: add route control and replay toggles to environment defaults
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m32s
CI / docker (push) Successful in 1m12s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
Admins can now disable route control and replay per environment via the
Default Resource Limits section. Both default to enabled. Apps in the
environment inherit these defaults unless overridden per-app.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 12:01:01 +02:00
hsiegeln
3501f32110 feat: make route control and replay configurable per environment/app
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m16s
CI / docker (push) Successful in 1m4s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 41s
Added routeControlEnabled and replayEnabled to ResolvedContainerConfig,
flowing through the three-layer config merge (global -> env -> app).
Both default to true. Admins can disable them per environment (e.g.
prod) via the defaultContainerConfig JSONB, or per app via the app's
containerConfig JSONB.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 11:56:13 +02:00
hsiegeln
4da81b21ba fix: enable route control and replay capabilities for deployed apps
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m24s
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / docker (push) Has been cancelled
buildEnvVars was missing CAMELEER_ROUTE_CONTROL_ENABLED and
CAMELEER_REPLAY_ENABLED, so deployed app containers defaulted to false
and agents didn't announce these capabilities.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 11:53:49 +02:00
hsiegeln
1b358f2e10 fix: config bar layout — override section's flex-direction to row
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m8s
CI / docker (push) Successful in 1m5s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
The .section base class sets flex-direction: column, which caused the
config bar items (App Log Level, Agent Log Level, etc.) to stack
vertically instead of displaying in a horizontal row.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 11:50:50 +02:00
hsiegeln
1539c7a67b fix: import /certs/ca.pem into JVM truststore at startup
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m13s
CI / docker (push) Successful in 1m3s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
The server container mounts the platform's certs volume at /certs but
the CA bundle was never imported into the JVM truststore. OIDC discovery
failed with PKIX path building errors when a self-signed or custom CA
was in use.

The new entrypoint script splits the PEM bundle and imports each cert
via keytool before starting the app. This makes the conditional
CAMELEER_OIDC_TLS_SKIP_VERIFY logic in the SaaS provisioner work
correctly: when ca.pem exists, the JVM now actually trusts it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 11:31:26 +02:00
hsiegeln
e9486bd05a feat: allow M2M password resets when OIDC is enabled
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m50s
CI / docker (push) Successful in 1m34s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 40s
The password reset endpoint was fully blocked under OIDC mode. Now
M2M callers (identified by oidc: principal prefix) can reset local
user passwords, enabling the SaaS platform to manage the server's
built-in admin credentials.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 09:46:26 +02:00
hsiegeln
cfc42eaf46 feat: add cameleer.tenant label to deployed app containers
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m48s
CI / docker (push) Successful in 1m32s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 43s
Allows the SaaS platform to identify and clean up all containers
belonging to a tenant on delete (cameleer/cameleer-saas#55).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 09:10:59 +02:00
hsiegeln
1a45235e30 feat: multi-format env var editor for deployment config
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m12s
CI / docker (push) Successful in 1m32s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s
Replace simple key-value rows with EnvEditor component that supports
editing variables as Table, Properties, YAML, or .env format.
Switching views converts data seamlessly. Includes file import
(drag-and-drop .properties/.yaml/.env) with auto-detect and merge.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 08:16:09 +02:00
hsiegeln
e9ce828e10 fix: update DS to v0.1.42 — fix double-border on environment selector
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m45s
CI / docker (push) Successful in 2m4s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s
SonarQube / sonarqube (push) Successful in 2m24s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 21:02:07 +02:00
hsiegeln
491bdfe1ff fix: type-safe ExchangeStatus cast in ButtonGroup onChange
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m9s
CI / docker (push) Successful in 1m30s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Cast the Set<string> from ButtonGroup.onChange to Set<ExchangeStatus>
before iterating, fixing TS2345 from DS TopBar decomposition.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 20:10:08 +02:00
hsiegeln
2863ceef12 refactor: compose TopBar center slot with server-specific controls
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 52s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Update to @cameleer/design-system@0.1.40 which decomposes TopBar into a
composable shell. Move status filters, time range, search trigger, and
auto-refresh toggle from the DS TopBar into LayoutShell as composed
children. Fixes cameleer/cameleer-saas#53.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 17:06:03 +02:00
hsiegeln
f0658cbd07 feat: hardcode Logto org scopes in auth flow, hide from admin UI
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m48s
CI / docker (push) Successful in 1m24s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 39s
Always include urn:logto:scope:organizations and
urn:logto:scope:organization_roles in OIDC auth requests. These are
required for role mapping in multi-tenant setups and harmless for
non-Logto providers (unknown scopes ignored per OIDC spec).

Filter them from the OIDC admin config page so they don't confuse
standalone server admins or SaaS tenants.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 15:37:40 +02:00
hsiegeln
0d610be3dc fix: use OIDC token roles when no claim mapping rules exist
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m29s
CI / docker (push) Successful in 1m15s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
The OIDC callback extracted roles from the token's Custom JWT claim
(e.g. roles: [server:admin]) but never used them. The
applyClaimMappings fallback only assigned defaultRoles (VIEWER).

Now the fallback priority is: claim mapping rules > OIDC token
roles > defaultRoles. This ensures users get their org-mapped
roles (owner → server:admin) without requiring manual claim
mapping rule configuration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 12:17:12 +02:00
hsiegeln
d238d2bd44 docs: update CLAUDE.md with tenant network isolation model
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m48s
CI / docker (push) Successful in 23s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 39s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 08:41:54 +02:00
hsiegeln
2ac52d3918 feat: tenant-scoped environment network names
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m27s
CI / docker (push) Successful in 1m9s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s
Environment networks now include the tenant ID to prevent cross-tenant
collisions: cameleer-env-{tenantId}-{envSlug} instead of cameleer-env-
{envSlug}. Without this, two tenants with a "dev" environment would
share the same Docker network.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 08:13:47 +02:00
hsiegeln
50e3f1ade6 feat: use configured DOCKER_NETWORK as primary for deployed apps
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m13s
CI / docker (push) Successful in 1m9s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
Instead of hardcoding cameleer-traefik as the primary network for
deployed app containers, use CAMELEER_DOCKER_NETWORK (env var). In
SaaS mode this is the tenant-isolated network (cameleer-tenant-{slug}).
Apps still connect to cameleer-traefik (for routing) and cameleer-env-
{slug} (for intra-environment discovery) as additional networks.

This enables per-tenant network isolation: apps deployed by tenant A
cannot reach apps deployed by tenant B since they share no network.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 08:08:48 +02:00
995d3ca00d Merge pull request 'fix: restore exchange table scroll by adding flex constraints to tableWrap' (#126) from fix/deployments-redirect-path into main
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m14s
CI / docker (push) Successful in 28s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 42s
SonarQube / sonarqube (push) Successful in 3m41s
Reviewed-on: cameleer/cameleer3-server#126
2026-04-09 19:25:00 +02:00
hsiegeln
ca18e58f5e fix: restore exchange table scroll by adding flex constraints to tableWrap
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m24s
CI / docker (push) Successful in 1m32s
CI / cleanup-branch (pull_request) Has been skipped
CI / deploy (push) Has been skipped
CI / build (pull_request) Successful in 1m45s
CI / docker (pull_request) Has been skipped
CI / deploy (pull_request) Has been skipped
CI / deploy-feature (pull_request) Has been skipped
CI / deploy-feature (push) Successful in 48s
The tableSection card wrapper broke the flex height chain — DataTable's
fillHeight couldn't constrain to viewport. Added .tableWrap with
flex: 1, min-height: 0, display: flex to re-establish the chain.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 19:20:50 +02:00
b345ac46a1 Merge pull request 'Fix /deployments redirect path (absolute, not relative)' (#125) from fix/deployments-redirect-path into main
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m24s
CI / docker (push) Successful in 25s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 39s
Reviewed-on: cameleer/cameleer3-server#125
2026-04-09 19:14:15 +02:00
hsiegeln
374131b7b5 fix: use absolute path for /deployments redirect
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 2m14s
CI / cleanup-branch (pull_request) Has been skipped
CI / build (pull_request) Successful in 2m4s
CI / docker (pull_request) Has been skipped
CI / docker (push) Successful in 34s
CI / deploy (pull_request) Has been skipped
CI / deploy-feature (pull_request) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Successful in 26s
The relative `to="apps"` didn't resolve correctly. All other legacy
redirects use absolute paths (`to="/apps"`, `to="/runtime"`).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 19:06:48 +02:00
0ac84a10e8 Merge pull request 'UX polish: bug fixes, design consistency, contrast, formatting' (#124) from feature/ux-polish into main
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m24s
CI / docker (push) Successful in 25s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 35s
Reviewed-on: cameleer/cameleer3-server#124
2026-04-09 19:03:53 +02:00
hsiegeln
191d4f39c1 fix: resolve 4 TypeScript compilation errors from CI
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m56s
CI / cleanup-branch (pull_request) Has been skipped
CI / build (pull_request) Successful in 1m58s
CI / docker (pull_request) Has been skipped
CI / deploy (pull_request) Has been skipped
CI / deploy-feature (pull_request) Has been skipped
CI / docker (push) Successful in 1m12s
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Successful in 37s
- AuditLogPage: e.details -> e.detail (correct property name)
- AgentInstance: BarChart x: number -> x: String(i) (BarSeries requires string)
- AppsTab: add missing CatalogRoute import
- Dashboard: wrap MonoText in span for title attribute (MonoText lacks title prop)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 18:57:42 +02:00
hsiegeln
4bc38453fe fix: nice-to-have polish — breadcrumbs, close button, status badges
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 40s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
CI / cleanup-branch (pull_request) Has been skipped
CI / build (pull_request) Failing after 35s
CI / docker (pull_request) Has been skipped
CI / deploy (pull_request) Has been skipped
CI / deploy-feature (pull_request) Has been skipped
- 7.1: Add deployment status badge (StatusDot + Badge) to AppsTab app
  list, sourced from catalog.deployment.status via slug lookup
- 7.3: Add X close button to top-right of exchange detail right panel
  in ExchangesPage (position:absolute, triggers handleClearSelection)
- 7.5: PunchcardHeatmap shows "Requires at least 2 days of data"
  when timeRangeMs < 2 days; DashboardL1 passes the range down
- 7.6: Command palette exchange results truncate IDs to ...{last8}
  matching the exchanges table display

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 18:51:49 +02:00
hsiegeln
9466551044 fix: add unsaved changes banners to edit mode forms
Adds amber edit-mode banners to AppConfigDetailPage and both
DefaultResourcesSection/JarRetentionSection in EnvironmentsPage,
matching the existing ConfigSubTab pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 18:47:55 +02:00
hsiegeln
39687bc8a9 fix: fix unicode in roles, add password confirmation field
- RolesTab: wrap \u00b7 in JS expression {'\u00b7'} so JSX renders the middle dot correctly instead of literal backslash-u sequence
- UsersTab: add confirm password field with mismatch validation, hint text for password policy, and reset on cancel/success
- UserManagement.module.css: add .hintText style for password policy hint

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 18:46:30 +02:00
hsiegeln
7ec56f3bd0 fix: add shared number formatting utilities (formatMetric, formatCount, formatPercent)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 18:43:52 +02:00
hsiegeln
605c8ad270 feat: add CSV export to audit log 2026-04-09 18:43:46 +02:00
hsiegeln
2ede06f32a fix: chart Y-axis auto-scaling, error rate unit, memory reference line, pointer events
- Throughput chart: divide totalCount by bucket duration (seconds) so Y-axis shows true msg/s instead of raw bucket counts; fixes flat-line appearance when TPS is low but totalCount is large
- Error Rate chart: convert failedCount/totalCount to percentage; change yLabel from "err/h" to "%" to match KPI stat card unit
- Memory chart: add threshold line at jvm.memory.heap.max so chart Y-axis extends to max heap and shows the reference line (spec 5.3)
- Agent state: suppress containerStatus badge when value is "UNKNOWN"; only render it with "Container: <state>" label when a non-UNKNOWN secondary state is present (spec 5.4)
- DashboardTab chartGrid: add pointer-events:none with pointer-events:auto on children so the chart grid overlay does not intercept clicks on the Application Health table rows below (spec 5.5)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 18:42:10 +02:00
hsiegeln
fb53dc6dfc fix: standardize button order, add confirmation dialogs for destructive actions
- Fix Cancel|Save order and add primary/loading props (AppConfigDetailPage)
- Add AlertDialog before stopping deployments (AppsTab)
- Add ConfirmDialog before deleting taps (TapConfigModal)
- Add AlertDialog before killing queries with toast feedback (DatabaseAdminPage)
- Add AlertDialog before removing roles from users (UsersTab)
- Standardize Cancel button to variant="ghost" (TapConfigModal, RouteDetail)
- Add loading prop to ConfirmDialogs (OidcConfigPage, RouteDetail)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 18:39:22 +02:00
hsiegeln
3d910af491 fix: hide empty attributes column, standardize status labels, truncate agent names
- Attributes column is now hidden when no exchanges in the current view
  have attributes; shown conditionally via hasAttributes check on rows
- Status labels already standardized via statusLabel() in ExchangeHeader
- Agent names truncated to last two hyphen-separated segments via
  shortAgentName(); full name preserved as tooltip title

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 18:36:06 +02:00
hsiegeln
eadcd160a3 fix: improve duration formatting (Xm Ys) and truncate exchange IDs
- formatDuration and formatDurationShort now show Xm Ys for durations >= 60s (e.g. "5m 21s" instead of "321s") and 1 decimal for 1-60s range ("6.7s" instead of "6.70s")
- Exchange ID column shows last 8 chars with ellipsis prefix; full ID on hover, copies to clipboard on click

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 18:34:04 +02:00
hsiegeln
ba0a1850a9 fix: WCAG AA contrast compliance for --text-muted/--text-faint, 12px font floor
Override design system tokens in app root CSS: --text-muted raised to 4.5:1
contrast in both light (#766A5E) and dark (#9A9088) modes; --text-faint dark
mode raised from catastrophic 1.4:1 to 3:1 (#6A6058). Migrate --text-faint
usages on readable text (empty states, italic notes, buttons) to --text-muted.
Raise all 10px and 11px font-size declarations to 12px floor.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 18:31:51 +02:00
hsiegeln
b6b93dc3cc fix: prevent admin page redirect during token refresh
adminFetch called logout() directly on 401/403 responses, which cleared
roles and caused RequireAdmin to redirect to /exchanges while users were
editing forms. Now adminFetch attempts a token refresh before failing,
and RequireAdmin tolerates a transient empty-roles state during refresh.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 18:28:45 +02:00
hsiegeln
3f9fd44ea5 fix: wrap app config in section cards, replace manual table with DataTable
- Add sectionStyles and tableStyles imports to AppsTab.tsx
- Wrap CreateAppView identity section and each config tab (Monitoring,
  Resources, Variables) in sectionStyles.section cards
- Wrap ConfigSubTab config tabs (Monitoring, Resources, Variables,
  Traces & Taps, Route Recording) in sectionStyles.section cards
- Replace manual <table> in OverviewSubTab with DataTable inside a
  tableStyles.tableSection card wrapper; pre-compute enriched row data
  via useMemo; handle muted non-selected-env rows via inline opacity
- Remove unused .table, .table th, .table td, .table tr:hover td, and
  .mutedRow CSS rules from AppsTab.module.css

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 18:28:11 +02:00
hsiegeln
ba53f91f4a fix: standardize table containment and container padding across pages
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 18:21:58 +02:00
hsiegeln
be585934b9 fix: show descriptive error when creating local user with OIDC enabled
Return a JSON error body from UserAdminController instead of an empty 400,
and extract API error messages in adminFetch so toasts display the reason.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 18:19:10 +02:00
hsiegeln
2771dffb78 fix: add /deployments redirect and fix GC Pauses chart X-axis
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 18:16:53 +02:00
hsiegeln
80bc092ec1 Add UX polish implementation plan (19 tasks across 8 batches)
Detailed step-by-step plan covering critical bug fixes, layout/interaction
consistency, WCAG contrast compliance, data formatting, chart fixes, and
admin polish. Each task includes exact file paths, code snippets, and
verification steps.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 18:13:41 +02:00
hsiegeln
4ea8bb368a Add UX polish design spec with comprehensive audit findings
Playwright-driven audit of the live UI (build 69dcce2, 60+ screenshots)
covering all pages, CRUD lifecycles, design consistency, and interaction
patterns. Spec defines 8 batches of work: critical bugs, layout
consistency, interaction consistency, contrast/readability, data
formatting, chart fixes, admin polish, and nice-to-have items.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 18:00:50 +02:00
hsiegeln
f24a5e5ff0 docs: update CLAUDE.md, audit, and spec for today's changes
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m20s
CI / docker (push) Successful in 27s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 40s
- CLAUDE.md: security (last-admin guard, password policy, brute-force,
  token revocation), environment filtering (queries + commands), Docker
  reconciliation, UI shared patterns, V8/V9 migrations
- UI-CONSISTENCY-AUDIT.md: marked RESOLVED
- UI consistency design spec: marked COMPLETED

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 16:54:54 +02:00
hsiegeln
1971c70638 fix: commands respect selected environment
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m19s
CI / docker (push) Successful in 1m4s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 40s
Backend: AgentRegistryService gains findByApplicationAndEnvironment()
and environment-aware addGroupCommandWithReplies() overload.
AgentCommandController and ApplicationConfigController accept optional
environment query parameter. When set, commands only target agents in
that environment. Backward compatible — null means all environments.

Frontend: All command mutations (config update, route control, traced
processors, tap config, route recording) now pass selectedEnv to the
backend via query parameter.

Prevents cross-environment command leakage — e.g., updating config for
prod no longer pushes to dev agents.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 16:28:09 +02:00
hsiegeln
69dcce2a8f fix: Runtime tab respects selected environment
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m21s
CI / docker (push) Successful in 1m5s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 41s
- Add environment parameter to AgentEventsController, AgentEventService,
  and ClickHouseAgentEventRepository (filters agent_events by environment)
- Wire selectedEnv to useAgents and useAgentEvents in both AgentHealth
  and AgentInstance pages
- Wire selectedEnv to useStatsTimeseries in AgentInstance

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 16:12:33 +02:00
hsiegeln
cb36d7936f fix: auto-compute environment slug + respect environment filter globally
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m26s
CI / docker (push) Successful in 1m6s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Part A: Environment creation slug is now auto-derived from display name
and shown read-only (matching app creation pattern). Removes manual slug
input.

Part B: All data queries now pass the selected environment to backend:
- Exchanges search, Dashboard L1/L2/L3 stats, Routes metrics, Route
  detail, correlation chains, and processor metrics all filter by
  selected environment.
- Backend RouteMetricsController now accepts environment parameter for
  both route and processor metrics endpoints.

Closes #XYZ

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 16:01:50 +02:00
hsiegeln
f95a78a380 fix: add periodic deployment status reconciliation
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m21s
CI / docker (push) Successful in 1m3s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s
The DockerEventMonitor only reacted to Docker events. If an event was
missed (e.g., during reconnect or startup race), a DEGRADED deployment
with all replicas healthy would never promote back to RUNNING.

Add a @Scheduled reconciliation (every 30s) that inspects actual
container state and corrects deployment status mismatches.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 15:40:18 +02:00
hsiegeln
3f94c98c5b refactor: replace native HTML with design system components (Phase 5)
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m26s
CI / docker (push) Successful in 1m12s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
- EnvironmentSelector: bare <select> -> DS Select
- LogTab: raw <table> + <input> + <button> -> DS LogViewer + Input + Button
- AppsTab: 3 homegrown sub-tab bars -> DS Tabs, remove unused CSS
- AppConfigDetailPage: 4x <select> -> DS Select, 2x <input checkbox> ->
  DS Toggle, 7x <label> -> DS Label, 4x <button> -> DS Button
- AgentHealth: 4x <select> -> DS Select, 7x <button> -> DS Button

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 15:22:14 +02:00
hsiegeln
ff62a34d89 refactor: UI consistency — shared CSS, design system colors, no inline styles
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m22s
CI / docker (push) Successful in 1m9s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
Phase 1: Extract 6 shared CSS modules (table-section, log-panel,
rate-colors, refresh-indicator, chart-card, section-card) eliminating
~135 duplicate class definitions across 11 files.

Phase 2: Replace all hardcoded hex colors in CSS modules with design
system variables. Strip ~55 hex fallbacks from var() patterns. Fix 4
undefined variable names (--accent, --bg-base, --surface, --bg-surface-raised).

Phase 3: Replace ~45 hardcoded hex values in ProcessDiagram SVG
components with var() CSS custom properties. Fix Dashboard.tsx color prop.

Phase 4: Create CSS modules for AdminLayout, DatabaseAdminPage,
OidcCallback (previously 100% inline). Extract shared PageLoader
component (replaces 3 copy-pasted spinner patterns). Move AppsTab
static inline styles to CSS classes. Extract LayoutShell StarredList styles.

58 files changed, net -219 lines.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 14:55:54 +02:00
hsiegeln
bfed8174ca docs: UI consistency audit and fix design spec
Full audit of design system adoption, color consistency, inline styles,
layout patterns, and CSS module duplication across the server UI.
Includes 6-phase fix plan.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 14:45:32 +02:00
hsiegeln
827ba3c798 feat: last-ADMIN guard and password hardening (#87, #89)
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m57s
CI / docker (push) Successful in 1m48s
CI / deploy (push) Successful in 51s
CI / deploy-feature (push) Has been skipped
- Prevent removal of last ADMIN role via role unassign, user delete,
  or group role removal (returns 409 Conflict)
- Add password policy: min 12 chars, 3/4 character classes, no username
- Add brute-force protection: 5 attempts then 15min lockout, IP rate limit
- Add token revocation on password change via token_revoked_before column
- V9 migration adds failed_login_attempts, locked_until, token_revoked_before

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 08:58:03 +02:00
hsiegeln
3bf470f83f fix: narrow DEPLOY_STATUS_DOT type to match StatusDotVariant
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m13s
CI / docker (push) Successful in 1m6s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
Fixes pre-existing TS2322 where Record<string, string> was not
assignable to the StatusDotVariant union type.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 08:33:38 +02:00
hsiegeln
de46cee440 chore: add GitNexus config to .gitignore and CLAUDE.md
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 50s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 08:30:53 +02:00
hsiegeln
04c90bde06 refactor: extract duplicated utility functions into shared modules
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 41s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Consolidate 20+ duplicate function definitions across UI components into
three shared util files (format-utils, agent-utils, config-draft-utils).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 08:28:31 +02:00
hsiegeln
2df5e0d7ba feat: active config snapshot, composite StatusDot with tooltip
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 43s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Part 1 — Config snapshot:
- V8 migration adds resolved_config JSONB to deployments table
- DeploymentExecutor saves the full resolved config at deploy time
- Deployment record includes resolvedConfig for auditability

Part 2 — Composite health StatusDot:
- CatalogController computes composite health from deployment status +
  agent health (green only when RUNNING AND agent live)
- CatalogApp includes healthTooltip (e.g. "Deployment: RUNNING,
  Agents: live (1 connected)")
- StatusDot added to app detail header with deployment status Badge
- StatusDot added to deployment table rows
- Sidebar passes composite health + tooltip through to tree nodes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 08:00:54 +02:00
hsiegeln
7b822a787a feat: show Redeploy button when config changed after deployment
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m20s
CI / docker (push) Successful in 1m5s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 41s
Compare app.updatedAt with deployment.deployedAt — if config was
modified after the deployment started, show a primary "Redeploy" button
in the Actions column. Also show a toast hint after saving config:
"Redeploy to apply changes to running deployments."

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 07:41:11 +02:00
hsiegeln
e88db56f79 refactor: CPU config to millicores, fix replica health, reorder tabs
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m18s
CI / docker (push) Successful in 1m5s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Has been cancelled
- Rename cpuShares to cpuRequest (millicores), cpuLimit from cores to
  millicores. ResolvedContainerConfig translates to Docker-native units
  via dockerCpuShares() and dockerCpuQuota() helpers. Future K8s
  orchestrator can pass millicores through directly.
- Fix waitForAnyHealthy to wait for ALL replicas instead of returning
  on first healthy one. Prevents false DEGRADED status with 2+ replicas.
- Default app detail to Configuration tab (was Overview)
- Reorder config sub-tabs: Monitoring, Resources, Variables, Traces &
  Taps, Route Recording

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 07:38:23 +02:00
hsiegeln
eb7cd9ba62 fix: keep sidebar selection when switching tabs
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m10s
CI / docker (push) Successful in 1m8s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Normalize the sidebar selectedPath so the app highlight persists across
tab switches (Dashboard, Runtime, Deployments). Also make sidebar clicks
tab-aware: clicking an app navigates to the current tab's path instead
of always going to /exchanges/.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 07:13:04 +02:00
hsiegeln
b86e95f08e feat: unified catalog endpoint and slug-based app navigation
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m19s
CI / docker (push) Successful in 1m7s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
SonarQube / sonarqube (push) Successful in 3m47s
Consolidate route catalog (agent-driven) and apps table (deployment-
driven) into a single GET /api/v1/catalog?environment={slug} endpoint.
Apps table is authoritative; agent data enriches with live health,
routes, and metrics. Unmanaged apps (agents without App record) appear
with managed=false.

- Add CatalogController merging App records + agent registry + ClickHouse
- Add CatalogApp DTO with deployment summary, managed flag, health
- Change AppController and DeploymentController to accept slugs (not UUIDs)
- Add AppRepository.findBySlug() and AppService.getBySlug()
- Replace useRouteCatalog() with useCatalog() across all UI components
- Navigate to /apps/{slug} instead of /apps/{UUID}
- Update sidebar, search, and all catalog lookups to use slug

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 23:43:14 +02:00
hsiegeln
0720053523 docs: add catalog consolidation design spec
Unify route catalog (agent-driven) and apps table (deployment-driven)
into a single catalog endpoint. Apps table becomes authoritative,
agent data enriches with live health/routes. Slug-based URLs replace
UUIDs for navigation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 23:32:18 +02:00
hsiegeln
a4a569a253 fix: improve deployment progress UI and prevent duplicate deployment rows
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m55s
CI / docker (push) Successful in 1m7s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 1m6s
- Redesign DeploymentProgress component: track-based layout with amber
  brand color, checkmarks for completed steps, user-friendly labels
  (Prepare, Image, Network, Launch, Verify, Activate, Live)
- Delete terminal (STOPPED/FAILED) deployments before creating new ones
  for the same app+environment, preventing duplicate rows in the UI
- Update CLAUDE.md with comprehensive key class locations, correct deploy
  stages, database migration reference, and REST endpoint summary

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 23:10:59 +02:00
hsiegeln
6288084daf docs: update documentation for Docker orchestration and env var rename
All checks were successful
CI / build (push) Successful in 2m9s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 1m41s
CI / deploy (push) Successful in 56s
CI / deploy-feature (push) Has been skipped
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 22:09:18 +02:00
hsiegeln
64ebf19ad3 refactor: use CAMELEER_SERVER_URL for agent export endpoint
Some checks failed
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / cleanup-branch (push) Has been cancelled
CI / build (push) Has been cancelled
The runtime-base image and all agent Dockerfiles now read
CAMELEER_SERVER_URL instead of CAMELEER_EXPORT_ENDPOINT.
Updated the volume-mode entrypoint override to match.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 22:07:07 +02:00
hsiegeln
20f3dfe59d feat: support Docker volume-based JAR mounting for Docker-in-Docker
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m27s
CI / docker (push) Successful in 1m8s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
When CAMELEER_JAR_DOCKER_VOLUME is set, the orchestrator mounts the
named volume at the jar storage path instead of using a host bind mount.
This solves the path translation issue in Docker-in-Docker setups where
the server runs inside a container and manages sibling containers.

The entrypoint is overridden to use the volume-mounted JAR path via
the CAMELEER_APP_JAR env var.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 21:38:34 +02:00
hsiegeln
c923d8233b fix: move network attachment from orchestrator to executor
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m29s
CI / docker (push) Successful in 1m10s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
Docker's connectToNetworkCmd needs the network ID (not name) and the
container's network sandbox must be ready. Moving network connection
to DeploymentExecutor where DockerNetworkManager handles ID resolution
and the container is already started.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 21:29:13 +02:00
hsiegeln
c72424543e fix: add client_max_body_size 200m to nginx API proxy
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m22s
CI / docker (push) Successful in 1m13s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Nginx defaults to 1MB body size, causing 413 on JAR uploads through
the UI proxy. Matches the Spring Boot multipart limit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 21:25:02 +02:00
hsiegeln
18ffbea9db fix: use visually-hidden clip pattern for file inputs
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m25s
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / docker (push) Has been cancelled
The opacity:0 approach caused the native "Choose File" button to
appear in the accessibility tree and compete for clicks. The clip
pattern properly hides the input while keeping it functional for
programmatic .click().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 21:23:05 +02:00
hsiegeln
19da9b9f9f fix: use opacity-based hidden input for file upload instead of display:none
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m25s
CI / docker (push) Successful in 1m14s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s
Some browsers block programmatic .click() on display:none inputs.
Using position:absolute + opacity:0 keeps the input in the render tree.
Also added type="button" to prevent any form-submission interference.
Applied to both create page and detail view file inputs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 21:17:50 +02:00
hsiegeln
8b3c4ba2fe feat: routing mode, domain, server URL, SSL offloading on Environments page
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m26s
CI / docker (push) Successful in 1m9s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 20:35:23 +02:00
hsiegeln
96fbca1b35 feat: replicas column, deploy progress, and new config fields in Deployments UI
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 20:33:41 +02:00
hsiegeln
977bfc1c6b feat: DeploymentProgress step indicator component
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 20:29:57 +02:00
hsiegeln
7e0536b5b3 feat: update Deployment interface with replicas, stages, new statuses
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 20:29:33 +02:00
hsiegeln
6e444a414d feat: add CAMELEER_SERVER_URL config property
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 20:28:44 +02:00
hsiegeln
f8d42026da feat: rewrite DeploymentExecutor with staged deploy, config merge, replicas
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 20:27:37 +02:00
hsiegeln
fef3ef6184 feat: DockerEventMonitor — persistent event stream for container lifecycle
Listens to Docker daemon events (die, oom, start, stop) for containers
labeled managed-by=cameleer3-server, updates replica states in Postgres,
and recomputes aggregate deployment status (RUNNING/DEGRADED/FAILED).
Bean is wired in RuntimeOrchestratorAutoConfig via instanceof guard so it
only activates when Docker is available.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 20:24:03 +02:00
hsiegeln
76eacb17e6 feat: DockerNetworkManager with lazy network creation and container attachment
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 20:21:39 +02:00
hsiegeln
3f2fec2815 feat: TraefikLabelBuilder with path-based and subdomain routing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 20:21:16 +02:00
hsiegeln
55bdab472b feat: expand ContainerRequest with cpuLimit, ports, restart policy, additional networks
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 20:20:13 +02:00
hsiegeln
b7d00548c5 feat: ResolvedContainerConfig record and three-layer ConfigMerger
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 20:18:25 +02:00
hsiegeln
fef0239b1d feat: update PostgresDeploymentRepository for orchestration columns
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 20:16:57 +02:00
hsiegeln
6eff271238 feat(core): add orchestration fields to Deployment record
Extends Deployment with targetState, deploymentStrategy, replicaStates
(List<Map<String,Object>>), and deployStage. Updates withStatus() to
carry the new fields through.
2026-04-08 20:15:11 +02:00
hsiegeln
01e0062767 feat(core): expand DeploymentStatus and add DeployStage enum
Adds DEGRADED and STOPPING to DeploymentStatus (reordered for lifecycle
clarity). Introduces DeployStage enum for tracking orchestration progress
through PRE_FLIGHT → COMPLETE.
2026-04-08 20:15:07 +02:00
hsiegeln
0fccdb636f feat(db): add V7 deployment orchestration migration
Adds target_state, deployment_strategy, replica_states (JSONB), and
deploy_stage columns to the deployments table with backfill logic.
2026-04-08 20:15:01 +02:00
hsiegeln
123e66e44d docs: Docker container orchestration implementation plan
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m25s
CI / docker (push) Successful in 25s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 35s
17 tasks covering: migration, domain models, config merger, Traefik
labels, network manager, Docker event monitor, DeploymentExecutor
rewrite, controller updates, and UI changes (progress indicator,
replicas, new config fields).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 20:11:12 +02:00
hsiegeln
b196918e70 docs: revert ICC-disabled, use shared traefik network with app-level auth
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m26s
CI / docker (push) Successful in 26s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 39s
ICC=false breaks Traefik routing and agent-server communication.
Switched to shared traefik network (ICC enabled) with app-level
security boundaries. Per-env Traefik networks noted as future option.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 20:00:12 +02:00
hsiegeln
dd4442329c docs: add ICC-disabled traefik network isolation to orchestration spec
The cameleer-traefik network disables inter-container communication
so app containers cannot reach each other directly — only through
Traefik. Environment networks keep ICC enabled for intra-env comms.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 19:53:51 +02:00
hsiegeln
da6bf694f8 docs: Docker container orchestration design spec
Covers: config merging (3-layer), Traefik label generation (path +
subdomain routing), network topology (infra/traefik/env isolation),
replica management, blue/green and rolling deployment strategies,
Docker event stream monitoring, deployment status state machine
(DEGRADED/STOPPING states), pre-flight checks, and UI changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 19:48:34 +02:00
hsiegeln
7e47f1628d feat: JAR retention policy with nightly cleanup job
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m23s
CI / docker (push) Successful in 1m9s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 40s
Per-environment "keep last N versions" setting (default 5, null for
unlimited). Nightly scheduled job at 03:00 deletes old versions from
both database and disk, skipping any version that is currently deployed.

Full stack:
- V6 migration: adds jar_retention_count column to environments
- Environment record, repository, service, admin controller endpoint
- JarRetentionJob: @Scheduled nightly, iterates environments and apps
- UI: retention policy editor on admin Environments page with
  toggle between limited/unlimited and version count input
- AppVersionRepository.delete() for version cleanup

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 19:06:28 +02:00
hsiegeln
863a992cc4 feat: add default container config editor to Environments admin page
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m20s
CI / docker (push) Successful in 1m8s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
New "Default Resource Limits" section in environment detail view with
memory limit/reserve, CPU shares/limit. These defaults apply to new
apps unless overridden per-app.

Added useUpdateDefaultContainerConfig hook for the PUT endpoint.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 18:52:39 +02:00
hsiegeln
0ccb8bc68d feat: extract Variables as first config tab in create and detail views
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m23s
CI / docker (push) Successful in 1m3s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
Environment Variables moved from Resources into a dedicated "Variables"
tab, placed first in the tab order since it's the most commonly needed
config when creating new apps.

Tab order:
- Create page: Variables | Monitoring | Resources
- Detail page: Variables | Monitoring | Traces & Taps | Route Recording | Resources

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 18:47:58 +02:00
hsiegeln
0a3733f9ba feat: show live external URL preview instead of slug on create page
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m19s
CI / docker (push) Successful in 1m7s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Has been cancelled
As the user types the app name, the URL builds in real-time:
  /{envSlug}/{appSlug}/

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 18:45:02 +02:00
hsiegeln
056b747c3f feat: replace create-app modal with full creation page at /apps/new
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m20s
CI / docker (push) Successful in 1m7s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 1m6s
Full-page creation flow with:
- Identity section: name, auto-slug, environment, JAR upload, deploy toggle
- Monitoring tab: engine level, payload capture, log levels, metrics,
  sampling, compress success, replay, route control
- Resources tab: memory, CPU, ports, environment variables

Environment variables are configurable before first deploy, addressing
the need to set app-specific config upfront.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 18:31:34 +02:00
hsiegeln
0b2d231b6b feat: split config into 4 tabs and fix JAR upload 413
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m19s
CI / docker (push) Successful in 1m5s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Config sub-tabs are now: Monitoring | Traces & Taps | Route Recording | Resources
(renamed from Agent/Infrastructure, with traces and recording as their own tabs).

Also increase Spring multipart max-file-size and max-request-size to 200MB
to fix HTTP 413 on JAR uploads.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 18:22:39 +02:00
hsiegeln
7503641afe chore: remove dead LogsTab and AppConfigPage files
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m13s
CI / docker (push) Successful in 1m5s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
Both replaced by consolidated Deployments tab. ~1300 lines removed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 18:11:05 +02:00
hsiegeln
967156d41b feat: migrate traces/taps and route recording into Deployments config
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m19s
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / docker (push) Has been cancelled
ConfigSubTab now uses inner tabs (Agent / Infrastructure):
- Agent: observability settings, compress success, traces & taps table,
  route recording toggles
- Infrastructure: container resources, exposed ports, environment variables

This completes the Config tab consolidation — all features from the
standalone Config page now live in the Deployments tab.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 18:09:12 +02:00
hsiegeln
0a0733def7 refactor: consolidate tabs — remove standalone Logs and Config tabs
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m18s
CI / docker (push) Successful in 1m5s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 42s
Logs functionality already exists in Runtime tab (AgentHealth/AgentInstance).
Config functionality moved to Deployments tab ConfigSubTab.
Old routes redirect to /runtime and /apps respectively.
Navigation links updated throughout.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 18:02:29 +02:00
hsiegeln
b7f215e90c feat: add delete confirmation dialog for apps
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m21s
CI / docker (push) Successful in 1m4s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 41s
Prevents accidental app deletion by requiring the user to type the app
slug before confirming.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 17:55:37 +02:00
hsiegeln
6a32b83326 feat: single-step app creation with auto-slug, JAR upload, and deploy
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m20s
CI / docker (push) Successful in 1m5s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Replace inline create form with a modal that handles the full flow:
- Name → auto-computed slug (editable if needed)
- Environment picker
- JAR file upload
- "Deploy immediately" toggle (on by default)
- Single "Create & Deploy" button runs all three API calls sequentially
  with step indicator

After creation, navigates directly to the new app's detail view.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 17:48:20 +02:00
hsiegeln
c4fe992179 feat: redesign Deployments tab with Overview + Configuration sub-tabs
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m20s
CI / docker (push) Successful in 1m4s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Overview sub-tab:
- Deployments table with env badge, version, status, URL, deployed time
- Actions (Start/Stop) scoped to selected environment; other envs show
  "switch env to manage" hint with muted rows
- Versions list with per-env deploy target picker

Configuration sub-tab:
- Read-only by default with Edit mode gate (Cancel/Save banner)
- Agent observability: engine level, payload capture with size unit
  selector, log levels, metrics toggle, sampling, replay and route
  control (default enabled)
- Container resources: memory/CPU limits, exposed ports as deletable
  pills with inline add input
- Environment variables: key-value editor with add/remove
- Reuses existing ApplicationConfig API for agent config push via SSE

Tab renamed from "Apps" to "Deployments" in the tab bar.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 17:36:09 +02:00
hsiegeln
01ac47eeb4 chore: update @cameleer/design-system to stable v0.1.39
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m26s
CI / docker (push) Successful in 1m41s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
Replaces snapshot dependency with tagged release.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:59:20 +02:00
hsiegeln
1c5ecb02e3 fix: make environment list accessible to all authenticated users
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m25s
CI / docker (push) Successful in 1m10s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s
The list endpoint on EnvironmentAdminController now overrides the
class-level ADMIN guard with isAuthenticated(), so VIEWERs can see
the environment selector. The LayoutShell merges environments from
both the table and agent heartbeats, so the selector always shows
configured environments even when no agents are connected.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:50:31 +02:00
hsiegeln
b1b7e142bb fix: remove duplicate updated_at column from V5 migration
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m24s
CI / docker (push) Successful in 1m5s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
apps.updated_at already exists from V3. The duplicate ALTER caused
Flyway to fail on startup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:31:06 +02:00
hsiegeln
de4ca10fa5 feat: move Apps from admin to main tab bar with container config
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m20s
CI / docker (push) Successful in 1m8s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Failing after 2m16s
- Apps tab visible to OPERATOR+ (hidden for VIEWER), scoped by
  sidebar app selection and environment filter
- List view: DataTable with name, environment, updated, created columns
- Detail view: deployments across all envs, version upload with
  per-env deploy target, container config form (resources, ports,
  custom env vars) with explicit Save
- Memory reserve field disabled for non-production environments
  with info hint
- Admin sidebar sorted alphabetically, Applications entry removed
- Old admin AppsPage deleted

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:23:30 +02:00
hsiegeln
875062e59a feat: add container config to apps and default config to environments
- V5 migration: container_config JSONB + updated_at on apps,
  default_container_config JSONB on environments
- App/Environment records updated with new fields
- PUT /apps/{id}/container-config endpoint for per-app config
- PUT /admin/environments/{id}/default-container-config for env defaults
- GET /apps now supports optional environmentId (lists all when omitted)
- AppRepository.findAll() for cross-environment app listing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:18:08 +02:00
hsiegeln
e04dca55aa feat: add Applications admin page with version upload and deployments
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m12s
CI / docker (push) Successful in 1m3s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 41s
- SplitPane layout with environment selector, app list, and detail pane
- Create/delete apps with slug uniqueness validation
- Upload JAR versions with file size display
- Deploy versions and stop running deployments with status badges
- Deployment list auto-refreshes every 5s for live status updates
- Registered at /admin/apps with sidebar entry

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 12:24:22 +02:00
hsiegeln
448a63adc9 feat: add About Me dialog showing user info, roles, and groups
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m48s
CI / docker (push) Successful in 1m45s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
- Add GET /api/v1/auth/me endpoint returning current user's UserDetail
- Add AboutMeDialog component with role badges and group memberships
- Add userMenuItems prop to TopBar via design-system update
- Wire "About Me" menu item into user dropdown above Logout

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 12:12:29 +02:00
hsiegeln
a8b977a2db fix: include managed role assignments in direct roles query
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m18s
CI / docker (push) Successful in 1m2s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s
getDirectRolesForUser filtered on origin='direct', which excluded
roles assigned via claim mapping (origin='managed'). This caused
OIDC users to appear roleless even when claim mappings matched.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 11:52:50 +02:00
hsiegeln
529e2c727c fix: apply defaultRoles fallback when no claim mapping rules match
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m19s
CI / docker (push) Successful in 1m3s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
When no claim mapping rules are configured or none match the JWT
claims, fall back to assigning the OidcConfig.defaultRoles (e.g.
VIEWER). This restores the behavior that was lost when syncOidcRoles
was replaced with claim mapping.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 11:33:24 +02:00
hsiegeln
9af0043915 feat: add Environment admin UI page
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m19s
CI / docker (push) Successful in 1m6s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 41s
SplitPane with create/edit/delete, production flag toggle,
enabled/disabled toggle. Follows existing admin page patterns.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 11:19:05 +02:00
hsiegeln
2e006051bc feat: add production/enabled flags to environments, drop status enum
Environments now have:
- production (bool): prod vs non-prod resource allocation
- enabled (bool): disabled blocks new deployments

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 11:16:09 +02:00
hsiegeln
d9160b7d0e fix: allow local login to coexist with OIDC
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m44s
CI / docker (push) Successful in 1m2s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s
Local login was blocked when OIDC env vars were present, causing
bootstrap to fail (chicken-and-egg: bootstrap needs local auth to
configure OIDC). The backend now accepts both auth paths; the
frontend/UI decides which login flow to present.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 09:09:24 +02:00
hsiegeln
36e8b2d8ff test: add integration tests for runtime management API
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m40s
CI / docker (push) Successful in 4m11s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
- EnvironmentAdminControllerIT: CRUD, access control, default env protection
- AppControllerIT: create, list, JAR upload, viewer access denied
- DeploymentControllerIT: deploy, list, not-found handling
- Fix bean name conflict: rename executor bean to deploymentTaskExecutor

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:52:07 +02:00
hsiegeln
3d20d7a0cb feat: add runtime management configuration properties
- JAR storage path, base image, Docker network
- Container memory/CPU limits, health check timeout
- Routing mode and domain for Traefik integration

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:47:43 +02:00
hsiegeln
8f2aafadc1 feat: add REST controllers for environment, app, and deployment management
- EnvironmentAdminController: CRUD under /api/v1/admin/environments (ADMIN)
- AppController: CRUD + JAR upload under /api/v1/apps (OPERATOR+)
- DeploymentController: deploy, stop, promote, logs under /api/v1/apps/{appId}/deployments
- Security rule for /api/v1/apps/** requiring OPERATOR or ADMIN role

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:47:05 +02:00
hsiegeln
248b716cb9 feat: implement async DeploymentExecutor pipeline
- Async container deployment with health check polling
- Stops previous deployment before starting new one
- Configurable memory, CPU, health timeout via application properties
- @EnableAsync on application class for Spring async proxy

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:45:38 +02:00
hsiegeln
b05b7e5597 feat: implement DockerRuntimeOrchestrator with volume-mount JAR deployment
- DockerRuntimeOrchestrator: docker-java based container lifecycle
- DisabledRuntimeOrchestrator: no-op for observability-only mode
- RuntimeOrchestratorAutoConfig: auto-detects Docker socket availability

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:44:32 +02:00
hsiegeln
585e078667 feat: implement PostgreSQL repositories for runtime management
- PostgresEnvironmentRepository, PostgresAppRepository
- PostgresAppVersionRepository, PostgresDeploymentRepository
- RuntimeBeanConfig wiring repositories, services, and async executor

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:43:35 +02:00
hsiegeln
55068ff625 feat: add EnvironmentService, AppService, DeploymentService
- EnvironmentService: CRUD with slug uniqueness, default env protection
- AppService: CRUD, JAR upload with SHA-256 checksumming
- DeploymentService: create, promote, status transitions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:41:48 +02:00
hsiegeln
17f45645ff feat: add runtime repository interfaces and RuntimeOrchestrator
- EnvironmentRepository, AppRepository, AppVersionRepository, DeploymentRepository
- RuntimeOrchestrator interface with ContainerRequest and ContainerStatus

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:41:05 +02:00
hsiegeln
fd2e52e155 feat: add runtime management domain records
- Environment, EnvironmentStatus, App, AppVersion
- Deployment, DeploymentStatus, RoutingMode

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:40:39 +02:00
hsiegeln
85530d5ea3 feat: add runtime management database schema (environments, apps, versions, deployments)
- environments, apps, app_versions, deployments tables
- Default environment seeded on migration
- Foreign keys with CASCADE delete

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:40:18 +02:00
hsiegeln
32ae642fab chore: add docker-java dependency for runtime orchestration
- docker-java-core 3.4.1
- docker-java-transport-zerodep 3.4.1

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:57 +02:00
hsiegeln
ec9856d8a2 fix: Ed25519SigningService falls back to ephemeral key when jwt-secret is absent
- SecurityBeanConfig uses Ed25519SigningServiceImpl.ephemeral() when no jwt-secret
- Fixes pre-existing application context failure in integration tests
- Reverts test jwt-secret from application-test.yml (no longer needed)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:34:55 +02:00
hsiegeln
847c1f792b test: add integration tests for claim mapping admin API
- ClaimMappingAdminControllerIT with create+list and delete tests
- Add adminHeaders() convenience method to TestSecurityHelper
- Add jwt-secret to test profile (fixes pre-existing Ed25519 init failure)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:20:58 +02:00
hsiegeln
ac9ce4f2e7 feat: add ClaimMappingAdminController for CRUD on mapping rules
- ADMIN-only REST endpoints at /api/v1/admin/claim-mappings
- Full CRUD: list, get by ID, create, update, delete
- OpenAPI annotations for Swagger documentation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:16:23 +02:00
hsiegeln
7657081b78 feat: disable local auth when OIDC is configured (resource server mode)
- UiAuthController.login returns 404 when OIDC issuer is configured
- JwtAuthenticationFilter skips internal user tokens in OIDC mode (agents still work)
- UserAdminController.createUser and resetPassword return 400 in OIDC mode

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:15:47 +02:00
hsiegeln
b5e85162f8 feat: replace syncOidcRoles with claim mapping evaluation on OIDC login
- OidcUserInfo now includes allClaims map from id_token + access_token
- OidcAuthController.callback() calls applyClaimMappings instead of syncOidcRoles
- applyClaimMappings evaluates rules, clears managed assignments, applies new ones
- Supports both assignRole and addToGroup actions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:13:52 +02:00
hsiegeln
7904a18f67 feat: add origin-aware managed/direct assignment methods to RbacService
- Add clearManagedAssignments, assignManagedRole, addUserToManagedGroup to interface
- Update assignRoleToUser and addUserToGroup to explicitly set origin='direct'
- Update getDirectRolesForUser to filter by origin='direct'
- Implement managed assignment methods with ON CONFLICT upsert

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:12:07 +02:00
hsiegeln
67ca1e726f feat: add license admin API for runtime license updates
- GET /api/v1/admin/license returns current license info
- POST /api/v1/admin/license validates and loads new license token
- Requires ADMIN role, validates Ed25519 signature before applying
- OpenAPI annotations for Swagger documentation
2026-04-07 23:12:03 +02:00
hsiegeln
b969075007 feat: add license loading at startup from env var or file
- LicenseBeanConfig wires LicenseGate bean with startup validation
- Supports token from CAMELEER_LICENSE_TOKEN env var or CAMELEER_LICENSE_FILE path
- Falls back to open mode when no license or no public key configured
- Add license config properties to application.yml
2026-04-07 23:11:02 +02:00
hsiegeln
d734597ec3 feat: implement PostgresClaimMappingRepository and wire beans
- JdbcTemplate-based CRUD for claim_mapping_rules table
- RbacBeanConfig wires ClaimMappingRepository and ClaimMappingService beans

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:10:38 +02:00
hsiegeln
dd5cf1b38c feat: implement LicenseGate for feature checking
- Thread-safe AtomicReference-based license holder
- Defaults to open mode (all features enabled) when no license loaded
- Runtime license loading with feature/limit queries
- Unit tests for open mode and licensed mode
2026-04-07 23:10:14 +02:00
hsiegeln
e1cb17707b feat: implement ClaimMappingService with equals/contains/regex matching
- Evaluates JWT claims against mapping rules
- Supports equals, contains (list + space-separated), regex match types
- Results sorted by priority
- 7 unit tests covering all match types and edge cases

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:09:50 +02:00
hsiegeln
b5cf35ef9a feat: implement LicenseValidator with Ed25519 signature verification
- Validates payload.signature license tokens using Ed25519 public key
- Parses tier, features, limits, timestamps from JSON payload
- Rejects expired and tampered tokens
- Unit tests for valid, expired, and tampered license scenarios
2026-04-07 23:08:04 +02:00
hsiegeln
2f8fcb866e feat: add ClaimMappingRule domain model and repository interface
- AssignmentOrigin enum (direct/managed)
- ClaimMappingRule record with match type and action enums
- ClaimMappingRepository interface for CRUD operations

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:07:57 +02:00
hsiegeln
bd78207060 feat: add claim mapping rules table and origin tracking to RBAC assignments
- Add origin and mapping_id columns to user_roles and user_groups
- Create claim_mapping_rules table with match_type and action constraints
- Update primary keys to include origin column
- Add indexes for fast managed assignment cleanup

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:07:30 +02:00
hsiegeln
96ba7cd711 feat: add LicenseInfo and Feature domain model
- Feature enum with topology, lineage, correlation, debugger, replay
- LicenseInfo record with tier, features, limits, issuedAt, expiresAt
- Open mode factory method for standalone/dev usage
2026-04-07 23:06:17 +02:00
hsiegeln
c6682c4c9c fix: update package-lock.json for DS v0.1.38
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m10s
CI / docker (push) Successful in 1m33s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s
SonarQube / sonarqube (push) Successful in 2m4s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 11:47:54 +02:00
hsiegeln
6a1d3bb129 refactor: move inline styles to CSS modules
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 13s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Extract inline fontSize/color styles from LogTab, LayoutShell,
UsersTab, GroupsTab, RolesTab, and LevelFilterBar into CSS modules.
Follows project convention of CSS modules over inline styles.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 11:45:02 +02:00
hsiegeln
9cbf647203 chore: update DS to v0.1.38, enforce 12px font size floor
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 22s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Update @cameleer/design-system to v0.1.38 (12px minimum font size).
Replace all 10px and 11px font sizes with 12px across 25 CSS modules
and 5 TSX inline styles to match the new DS floor.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 11:41:51 +02:00
hsiegeln
07f3c2584c fix: syncOidcRoles uses direct roles only, always overwrites
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m19s
CI / docker (push) Successful in 1m0s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 35s
- Expose getDirectRolesForUser on RbacService interface so syncOidcRoles
  compares against directly-assigned roles only, not group-inherited ones
- Remove early-return that preserved existing roles when OIDC returned
  none — now always applies defaultRoles as fallback
- Update CLAUDE.md and SERVER-CAPABILITIES.md to reflect changes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 10:56:40 +02:00
hsiegeln
ca1b549f10 docs: document OIDC access_token role extraction and audience config
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / build (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 10:55:01 +02:00
hsiegeln
7d5866bca8 chore: remove debug logging from OidcTokenExchanger
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m16s
CI / docker (push) Successful in 1m2s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 10:50:27 +02:00
hsiegeln
f601074e78 fix: include resource parameter in OIDC token exchange request
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m17s
CI / docker (push) Successful in 59s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Logto returns opaque access tokens unless the resource parameter is
included in both the authorization request AND the token exchange.
Append resource to the token endpoint POST body per RFC 8707 so Logto
returns a JWT access token with Custom JWT claims.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 10:45:44 +02:00
hsiegeln
725f826513 debug: log access_token format to diagnose opaque vs JWT
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m18s
CI / docker (push) Successful in 1m1s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 10:39:53 +02:00
hsiegeln
52f5a0414e debug: temporarily log access_token decode failures at WARN level
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m7s
CI / docker (push) Successful in 59s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 10:34:15 +02:00
hsiegeln
11fc85e2b9 fix: log access_token claims and audience mismatch during OIDC exchange
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m13s
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / docker (push) Has been cancelled
Helps diagnose whether rolesClaim path matches the actual token structure.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 10:32:34 +02:00
hsiegeln
d4b530ff8a refactor: remove PKCE from OIDC flow (confidential client)
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m16s
CI / docker (push) Successful in 1m2s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Backend holds client_secret and does the token exchange server-side,
making PKCE redundant. Removes code_verifier/code_challenge from all
frontend auth paths and backend exchange method. Eliminates the source
of "grant request is invalid" errors from verifier mismatches.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 10:22:13 +02:00
hsiegeln
03ff9a3813 feat: generic OIDC role extraction from access token
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m48s
CI / docker (push) Successful in 1m1s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s
The OIDC login flow now reads roles from the access_token (JWT) in
addition to the id_token. This fixes role extraction with providers
like Logto that put scopes/roles in access tokens rather than id_tokens.

- Add audience and additionalScopes to OidcConfig for RFC 8707 resource
  indicator support and configurable extra scopes
- OidcTokenExchanger decodes access_token with at+jwt-compatible processor,
  falls back to id_token if access_token is opaque or has no roles
- syncOidcRoles preserves existing local roles when OIDC returns none
- SPA includes resource and additionalScopes in authorization requests
- Admin UI exposes new config fields

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 10:16:52 +02:00
hsiegeln
95eb388283 fix: handle space-delimited scope string in OIDC role extraction
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m6s
CI / docker (push) Successful in 1m12s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 39s
extractRoles() only handled List claims (JSON arrays). When rolesClaim
is configured as "scope", the JWT value is a space-delimited string,
which was silently returning [] and falling back to defaultRoles.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:20:37 +02:00
hsiegeln
8852ec1483 feat: add diagnostic logging for OIDC scope and role extraction
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m7s
CI / docker (push) Has started running
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
Logs received scopes, rolesClaim path, extracted roles, and all claim
keys at each stage of the OIDC auth flow to help debug Logto integration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:16:42 +02:00
hsiegeln
23e90d6afb fix: postinstall creates public/ dir before copying favicon
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m8s
CI / docker (push) Successful in 1m20s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 40s
SonarQube / sonarqube (push) Successful in 3m31s
Docker build copies package.json before source, so public/ doesn't
exist when npm ci runs postinstall. Use mkdirSync(recursive:true).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 23:38:43 +02:00
hsiegeln
d19551f8aa chore: auto-sync favicon from DS via postinstall script
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m7s
CI / docker (push) Failing after 52s
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
favicon.svg is now copied from @cameleer/design-system/assets on
npm install via postinstall hook. Removed from git tracking
(.gitignore). Updates automatically when DS version changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 23:25:44 +02:00
hsiegeln
b2e4b91d94 chore: update design system to v0.1.37 (improved SVG logo)
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m8s
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / docker (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 23:24:12 +02:00
hsiegeln
95b35f6203 fix: make OIDC logout resilient to end-session endpoint failures
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m32s
CI / docker (push) Successful in 1m13s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Fire end-session via fetch(no-cors) instead of window.location redirect.
Always navigate to /login?local regardless of whether end-session
succeeds, preventing broken JSON responses from blocking logout.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 23:06:56 +02:00
hsiegeln
a443abe6ae refactor: unify all brand icons to single SVG from DS v0.1.36
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 2m0s
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / docker (push) Has been cancelled
Replace PNG favicons and brand logos with cameleer3-logo.svg from
@cameleer/design-system/assets. Favicon, login dialog, and sidebar
all use the same SVG. Remove PNG favicon files from public/.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 23:03:30 +02:00
hsiegeln
a5340059d7 refactor: import brand assets directly from DS v0.1.34
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m16s
CI / docker (push) Successful in 1m30s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
DS now exports ./assets/* — import PNGs directly via Vite instead of
copying to public/. Removes duplicated brand files from public/.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 22:47:31 +02:00
hsiegeln
45cccdbd8a fix: revert to public/ brand assets — DS exports field blocks imports
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m10s
CI / docker (push) Successful in 2m7s
CI / deploy (push) Successful in 51s
CI / deploy-feature (push) Has been skipped
The @cameleer/design-system package.json exports field doesn't include
assets/, causing production build failures. Copy PNGs to public/ and
reference via basePath until DS adds asset exports.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 22:41:20 +02:00
hsiegeln
281e168790 fix: pass commit short hash as version to UI sidebar
Some checks failed
CI / build (push) Failing after 38s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Add VITE_APP_VERSION build arg to UI Dockerfile, pass short SHA from
CI docker build step. vite.config.ts truncates to 7 chars so both
CI build and Docker build produce consistent short hashes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 22:37:46 +02:00
hsiegeln
1386e80670 refactor: import brand icons directly from design system
Some checks failed
CI / build (push) Failing after 36s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Import PNGs via Vite from @cameleer/design-system/assets instead of
copying to public/. Only favicons remain in public/ (needed by HTML).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 22:20:07 +02:00
hsiegeln
f372d0d63c chore: update design system to v0.1.33 (transparent brand icons)
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / build (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 22:18:26 +02:00
hsiegeln
6ef66a14ec fix: use full-color brand PNGs for login dialog and sidebar
All checks were successful
CI / build (push) Successful in 1m32s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 1m44s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 40s
The SVG uses fill=currentColor (inherits text color). Switch to the
full-color PNG brand icons: 192px for login dialog, 48px for sidebar.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 22:10:48 +02:00
hsiegeln
0761d0dbee feat: use design system brand icons for favicon, login, sidebar
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / build (push) Has been cancelled
Replace hand-crafted favicon.svg with official brand assets from
@cameleer/design-system v0.1.32: PNG favicons (16/32px) and
camel-logo.svg for login dialog and sidebar. Update SecurityConfig
public endpoints accordingly. Update documentation for architecture
cleanup (PKCE, OidcProviderHelper, role normalization, K8s hardening,
Dockerfile credential removal, CI deduplication, sidebar path fix).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 22:08:58 +02:00
hsiegeln
0de392ff6e fix: remove securityContext from UI pod — nginx needs root for setup
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m6s
CI / docker (push) Successful in 39s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 41s
The standard nginx image requires root to modify /etc/nginx/conf.d
and create /var/cache/nginx directories during startup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 22:06:07 +02:00
hsiegeln
c502a42f17 refactor: architecture cleanup — OIDC dedup, PKCE, K8s hardening
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m6s
CI / docker (push) Successful in 59s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Failing after 2m59s
- Extract OidcProviderHelper for shared discovery + JWK source construction
- Add SystemRole.normalizeScope() to centralize role normalization
- Merge duplicate claim extraction in OidcTokenExchanger
- Add PKCE (S256) to OIDC authorization flow (frontend + backend)
- Add SecurityContext (runAsNonRoot) to all K8s deployments
- Fix postgres probe to use $POSTGRES_USER instead of hardcoded username
- Remove default credentials from Dockerfile
- Extract sanitize_branch() to shared .gitea/sanitize-branch.sh
- Fix sidebar to use /exchanges/ paths directly, remove legacy redirects
- Centralize basePath computation in router.tsx via config module

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 21:57:29 +02:00
hsiegeln
07ff576eb6 fix: prevent SSO re-login loop on OIDC logout
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m12s
CI / docker (push) Successful in 1m1s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
Logout now always redirects to /login?local, either via OIDC
end_session or as a direct fallback, preventing prompt=none
auto-redirect from logging the user back in immediately.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 17:37:35 +02:00
hsiegeln
c249c6f3e0 docs: update Config tab navigation behavior and role gating
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m31s
CI / docker (push) Successful in 13s
CI / deploy (push) Successful in 46s
CI / deploy-feature (push) Has been skipped
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 16:29:20 +02:00
hsiegeln
bb6a9c9269 fix: Config tab sidebar navigation stays on config for app and route clicks
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m11s
CI / docker (push) Successful in 58s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
When on Config tab: clicking an app navigates to /config/:appId (shows
that app's config with detail panel). Clicking a route navigates to
/config/:appId (same app config, since config is per-app not per-route).
Clicking Applications header navigates to /config (all apps table).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 16:13:39 +02:00
hsiegeln
c6a8a4471f fix: always show Config tab and fix 404 on sidebar navigation
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m4s
CI / docker (push) Successful in 57s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s
Config tab now always visible (not just when app selected). Shows all-
app config table at /config, single-app detail at /config/:appId.

Fixed 404 when clicking sidebar nodes while on Config tab — the sidebar
navigation built /config/appId/routeId which had no route. Now falls
back to exchanges tab for route-level navigation from config.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 16:10:02 +02:00
hsiegeln
640a48114d docs: document UI role gating for VIEWER/OPERATOR/ADMIN
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m37s
CI / docker (push) Successful in 1m0s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 15:52:25 +02:00
hsiegeln
b1655b366e feat: role-based UI access control
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / build (push) Has been cancelled
- Hide Admin sidebar section for non-ADMIN users
- Add RequireAdmin route guard — /admin/* redirects to / for non-admin
- Move App Config from admin section to main Config tab (per-app,
  visible when app selected). VIEWER sees read-only, OPERATOR+ can edit
- Hide diagram node toolbar for VIEWER (onNodeAction conditional)
- Add useIsAdmin/useCanControl helpers to centralize role checks
- Remove App Config from admin sidebar tree

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 15:51:15 +02:00
hsiegeln
e54f308607 docs: add role-based UI access control design spec
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m5s
CI / docker (push) Successful in 10s
CI / deploy (push) Successful in 39s
CI / deploy-feature (push) Has been skipped
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 15:35:59 +02:00
hsiegeln
e69b44f566 docs: document configurable userIdClaim for OIDC
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m5s
CI / docker (push) Successful in 57s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 10:20:50 +02:00
hsiegeln
0c77f8d594 feat: add User ID Claim field to OIDC admin config UI
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m5s
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / docker (push) Has been cancelled
New input in the Claim Mapping section lets admins configure which
id_token claim is used as the unique user identifier (default: sub).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 10:19:38 +02:00
hsiegeln
a96cf2afed feat: add configurable userIdClaim for OIDC user identification
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m12s
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
The OIDC user login ID is now configurable via the admin OIDC setup
dialog (userIdClaim field). Supports dot-separated claim paths (e.g.
'email', 'preferred_username', 'custom.user_id'). Defaults to 'sub'
for backwards compatibility. Throws if the configured claim is missing
from the id_token.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 10:18:03 +02:00
hsiegeln
549dbaa322 docs: document OIDC role sync on every login
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m8s
CI / docker (push) Successful in 40s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 35s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 10:11:49 +02:00
hsiegeln
f4eafd9a0f feat: sync OIDC roles on every login, not just first
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / build (push) Has been cancelled
Roles from the id_token's rolesClaim are now diffed against stored
system roles on each OIDC login. Missing roles are added, revoked
roles are removed. Group memberships (manually assigned) are never
touched. This propagates scope revocations from the OIDC provider
on next user login.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 10:11:06 +02:00
hsiegeln
4e12fcbe7a docs: document server:-prefixed scopes and case-insensitive role mapping
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m7s
CI / docker (push) Successful in 40s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 10:06:11 +02:00
hsiegeln
9c2e6aacad feat: support server:-prefixed scopes and case-insensitive role mapping
Some checks failed
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / cleanup-branch (push) Has been cancelled
CI / build (push) Has been cancelled
M2M scope mapping now accepts both 'server:admin' and 'admin' (case-
insensitive). OIDC user login role assignment strips the 'server:'
prefix before looking up SystemRole, so 'server:viewer' from the
id_token maps to VIEWER correctly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 10:05:13 +02:00
hsiegeln
c757a0ea51 fix: replace last hardcoded paths with BASE_PATH-aware alternatives
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m13s
CI / docker (push) Successful in 57s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
- index.html: change /src/main.tsx to ./src/main.tsx (relative, respects
  <base> tag)
- AgentRegistrationController: derive SSE endpoint URL from request
  context via ServletUriComponentsBuilder instead of hardcoding /api/v1

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 09:53:00 +02:00
hsiegeln
9a40626a27 fix: include BASE_PATH and ?local in OIDC post-logout redirect URI
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m4s
CI / docker (push) Successful in 57s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Without BASE_PATH the redirect fails behind a reverse proxy. Adding
?local prevents the SSO auto-redirect from immediately signing the
user back in after logout.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 09:45:46 +02:00
hsiegeln
4496be08bd docs: document SSO auto-redirect, consent handling, and auto-signup
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m11s
CI / docker (push) Successful in 10s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 34s
SonarQube / sonarqube (push) Successful in 3m36s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 01:45:45 +02:00
hsiegeln
e8bcc39ca9 fix: add ES384 to OidcTokenExchanger JWT algorithm list
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m13s
CI / docker (push) Successful in 43s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 40s
Logto signs id_tokens with ES384 by default. SecurityConfig already
included it but OidcTokenExchanger only had RS256 and ES256.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 01:37:22 +02:00
hsiegeln
94bfb8fc4a fix: Back to Login button navigates to /login?local to prevent auto-redirect
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m8s
CI / docker (push) Successful in 59s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 01:31:57 +02:00
hsiegeln
c628c25081 fix: handle consent_required by retrying OIDC without prompt=none
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m36s
CI / deploy (push) Has been cancelled
CI / docker (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
When prompt=none fails with consent_required (scopes not yet granted),
retry the OIDC flow without prompt=none so the user can grant consent
once. Uses sessionStorage flag to prevent infinite loops — falls back
to local login if the retry also fails.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 01:29:31 +02:00
hsiegeln
3cea306e17 feat: auto-redirect to OIDC provider for true SSO
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m51s
CI / docker (push) Successful in 2m37s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 54s
When OIDC is configured, the login page automatically redirects to the
provider with prompt=none. If the user has an active OIDC session, they
are signed in without seeing a login page. If the provider returns
login_required (no session), falls back to the login form via ?local.
Users can bypass auto-redirect with /login?local.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 01:20:55 +02:00
hsiegeln
4244dd82e9 fix: use BASE_PATH for favicon references in subpath deployments
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m6s
CI / docker (push) Successful in 57s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
Hardcoded /favicon.svg paths skip the <base> tag and fail when served
from a subpath like /server/. Now uses config.basePath in TSX and a
relative href in index.html so the <base> tag resolves correctly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 01:17:17 +02:00
hsiegeln
d7001804f7 fix: permit branding endpoints without authentication
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m7s
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / docker (push) Has been cancelled
The login page loads the branding logo before the user is signed in.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 01:15:21 +02:00
hsiegeln
5c4c7ad321 fix: include BASE_PATH in OIDC redirect_uri for subpath deployments
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Has started running
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
Behind a reverse proxy with strip-prefix (e.g., Traefik at /server/),
the OIDC redirect_uri must include the prefix so the callback routes
back through the proxy. Now uses config.basePath (from <base href>)
instead of hardcoding '/'.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 01:14:34 +02:00
hsiegeln
0fab20e67a fix: append .well-known/openid-configuration to issuerUri in token exchanger
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m12s
CI / docker (push) Successful in 40s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s
OidcTokenExchanger fetched the discovery document from the issuerUri
as-is, but the database stores the issuer URI (e.g. /oidc), not the
full discovery URL. Logto returns 404 for the bare issuer path.
SecurityConfig already appended the well-known suffix — now the token
exchanger does the same.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 01:04:57 +02:00
hsiegeln
d7563902a7 fix: read oidcTlsSkipVerify at call time instead of caching in constructor
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m13s
CI / docker (push) Successful in 42s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Has been cancelled
OidcTokenExchanger cached securityProperties.isOidcTlsSkipVerify() in
the constructor as a boolean field. If Spring constructed the bean
before property binding completed, the cached value was false even when
the env var was set. SecurityConfig worked because it read the property
at call time. Now OidcTokenExchanger stores the SecurityProperties
reference and reads the flag on each call, matching SecurityConfig's
pattern.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 01:02:36 +02:00
hsiegeln
99e2a8354f fix: handle HTTPS redirects in InsecureTlsHelper for OIDC discovery
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m13s
CI / docker (push) Successful in 42s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s
Java's automatic redirect following creates new connections that do NOT
inherit custom SSLSocketFactory/HostnameVerifier. This caused the OIDC
discovery fetch to fail on redirect even with TLS_SKIP_VERIFY=true.
Now disables auto-redirect and follows manually with SSL on each hop.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 00:51:49 +02:00
hsiegeln
083cb8b9ec feat: add CAMELEER_CORS_ALLOWED_ORIGINS for multi-origin CORS support
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m7s
CI / docker (push) Successful in 41s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Behind a reverse proxy the browser sends Origin matching the proxy's
public URL, which the single-origin CAMELEER_UI_ORIGIN rejects.
New env var accepts comma-separated origins and takes priority over
UI_ORIGIN, which remains as a backwards-compatible fallback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 00:41:00 +02:00
hsiegeln
0609220cdf docs: add CAMELEER_OIDC_TLS_SKIP_VERIFY to all documentation
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m14s
CI / docker (push) Successful in 11s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 35s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 00:30:18 +02:00
hsiegeln
ca92b3ce7d feat: add CAMELEER_OIDC_TLS_SKIP_VERIFY to bypass cert verification for OIDC
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m7s
CI / docker (push) Successful in 43s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
Self-signed CA certs on the OIDC provider (e.g. Logto behind a reverse
proxy) cause the login flow to fail because Java's truststore rejects
the connection. This adds an opt-in env var that creates a trust-all
SSLContext scoped to OIDC HTTP calls only (discovery, token exchange,
JWKS fetch) without affecting system-wide TLS.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 00:26:40 +02:00
hsiegeln
7ebbc18b31 fix: make API calls respect BASE_PATH for subpath deployments
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m7s
CI / docker (push) Successful in 57s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
config.apiBaseUrl now derives from <base> tag when no explicit config
is set (e.g., /server/api/v1 instead of /api/v1). commands.ts authFetch
prepends apiBaseUrl and uses relative paths.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 00:04:52 +02:00
hsiegeln
5b7c92848d fix: remove path-rewriting sed that doubled BASE_PATH in <base> tag
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m43s
CI / docker (push) Successful in 1m9s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
The second sed matched the just-injected <base href="/server/"> and
rewrote it to <base href="/server/server/">. Since Vite builds with
base: './' (relative paths), the <base> tag alone is sufficient.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 23:52:30 +02:00
hsiegeln
44f3821df4 docs: add CAMELEER_OIDC_JWK_SET_URI to all documentation
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m40s
CI / docker (push) Successful in 12s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 22:58:05 +02:00
hsiegeln
51abe45fba feat: add BASE_PATH env var for serving UI from a subpath
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m9s
CI / docker (push) Successful in 1m4s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
When BASE_PATH is set (e.g., /server/), the entrypoint script injects
a <base> tag and rewrites asset paths in index.html. React Router reads
the basename from the <base> tag. Vite builds with relative paths.
Default / for standalone mode (no changes).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 21:04:28 +02:00
hsiegeln
3c70313d78 feat: add CAMELEER_OIDC_JWK_SET_URI for direct JWKS fetching
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / build (push) Has been cancelled
When set, fetches JWKs from this URL directly instead of discovering
from the OIDC well-known endpoint. Needed when the public issuer URL
(e.g., https://domain.com/oidc) isn't reachable from inside containers
but the internal URL (http://logto:3001/oidc/jwks) is.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 21:02:51 +02:00
hsiegeln
12bb734c2d fix: use tcpSocket probe for logto-postgresql instead of pg_isready
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m4s
CI / docker (push) Successful in 39s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 35s
pg_isready without -U defaults to OS user "root" which doesn't exist
as a PostgreSQL role, causing noisy log entries.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 13:44:59 +02:00
hsiegeln
cbeaf30bc7 fix: move PG_USER/PG_PASSWORD before DB_URL in logto.yaml
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m3s
CI / docker (push) Successful in 40s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 1m9s
K8s $(VAR) substitution only resolves env vars defined earlier in the
list. PG_USER and PG_PASSWORD must come before DB_URL.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 13:39:50 +02:00
hsiegeln
c4d2fa90ab docs: clarify Logto proxy setup and ENDPOINT/ADMIN_ENDPOINT semantics
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m3s
CI / docker (push) Successful in 42s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Failing after 3m15s
LOGTO_ENDPOINT and LOGTO_ADMIN_ENDPOINT are public-facing URLs that
Logto uses for OIDC discovery, issuer URI, and redirects. When behind
a reverse proxy (e.g., Traefik), set these to the external URLs.
Logto requires its own subdomain (not a path prefix).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 13:31:17 +02:00
hsiegeln
e9ef97bc20 docs: add Logto OIDC resource server spec and implementation plan
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m33s
CI / docker (push) Successful in 3m13s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 13:25:24 +02:00
hsiegeln
eecb0adf93 docs: replace Authentik with Logto, document OIDC resource server
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 13:15:09 +02:00
hsiegeln
c47b8b9998 ci: replace Authentik with Logto in deployment pipeline 2026-04-05 13:12:38 +02:00
hsiegeln
22d812d832 feat: replace Authentik with Logto K8s deployment 2026-04-05 13:12:01 +02:00
hsiegeln
fec6717a85 feat: update default rolesClaim to 'roles' for Logto compatibility
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 13:10:53 +02:00
hsiegeln
3bd07c9b07 feat: add OIDC resource server support with JWKS discovery and scope-based roles
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 13:10:08 +02:00
hsiegeln
a5c4e0cead feat: add spring-boot-starter-oauth2-resource-server and OIDC properties
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 13:06:53 +02:00
hsiegeln
de85cdf5a2 fix: let SPRING_DATASOURCE_URL fully control datasource connection
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m5s
CI / docker (push) Successful in 41s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
SonarQube / sonarqube (push) Successful in 3m26s
Explicit spring.datasource.url in YAML takes precedence over the env var,
causing deployed containers to connect to localhost instead of the postgres
service. Now the YAML uses ${SPRING_DATASOURCE_URL:...} so the env var
wins when set. Flyway inherits from the datasource (no separate URL).
Removed CAMELEER_DB_SCHEMA — schema is part of the datasource URL.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 23:24:22 +02:00
hsiegeln
2277a0498f fix: set CAMELEER_DB_SCHEMA=public for existing main deployment
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m1s
CI / docker (push) Successful in 41s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Has been cancelled
Existing deployment has tables in public schema. The new tenant_default
default breaks startup because Flyway sees an empty schema. Override to
public for backward compat; new deployments use the tenant-derived default.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 23:21:17 +02:00
hsiegeln
ac87aa6eb2 fix: derive PG schema from tenant ID instead of defaulting to public
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m6s
CI / docker (push) Successful in 43s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Failing after 2m17s
Schema now defaults to tenant_${cameleer.tenant.id} (e.g. tenant_default,
tenant_acme) instead of public. Flyway create-schemas: true ensures the
schema is auto-created on first startup. CAMELEER_DB_SCHEMA env var still
available as override for feature branch isolation. Removed hardcoded
public schema from K8s base and main overlay.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 21:46:57 +02:00
hsiegeln
f16d331621 docs: add SERVER-CAPABILITIES.md for SaaS integration reference
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m8s
CI / docker (push) Successful in 10s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
Comprehensive standalone document covering API surface, agent protocol,
security, storage, multi-tenancy, deployment, and configuration — designed
for external systems (like the SaaS orchestration layer) that need to
understand and manage Cameleer3 Server instances.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 20:30:42 +02:00
hsiegeln
69055f7d74 fix: persist environment selection in Zustand store instead of URL params
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m5s
CI / docker (push) Successful in 57s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
Environment selector was losing its value on navigation because URL search
params were silently dropped by navigate() calls. Moved to a Zustand store
with localStorage persistence so the selection survives navigation, page
refresh, and new tabs. Switching environment now resets all filters, clears
URL params, invalidates queries, and remounts pages via Outlet key. Also
syncs openapi.json schema with running backend.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 17:12:16 +02:00
hsiegeln
37eb56332a fix: use environmentId from heartbeat body for auto-heal
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m5s
CI / docker (push) Successful in 42s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
HeartbeatRequest now carries environmentId (cameleer3-common update).
Auto-heal prefers the heartbeat value (most current) over the JWT
claim, ensuring agents recover their correct environment immediately
on the first heartbeat after server restart.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 16:21:55 +02:00
hsiegeln
72ec87a3ba fix: persist environment in JWT claims for auto-heal recovery
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m4s
CI / docker (push) Successful in 1m7s
CI / deploy (push) Successful in 45s
CI / deploy-feature (push) Has been skipped
Add 'env' claim to agent JWTs (set at registration, carried through
refresh). Auto-heal on heartbeat/SSE now reads environment from the
JWT instead of hardcoding 'default', so agents retain their correct
environment after server restart.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 16:12:25 +02:00
hsiegeln
346e38ee1d fix: update DS to v0.1.31, simplify env selector styles
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m4s
CI / docker (push) Successful in 1m23s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
DS v0.1.31 changes .env wrapper to neutral button style matching
other TopBar controls. Simplified selector CSS to inherit all
font/color properties from the wrapper.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 16:01:58 +02:00
hsiegeln
39d9ec9cd6 fix: restyle environment selector to match DS TopBar pill
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m4s
CI / docker (push) Successful in 1m9s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 44s
Make the select transparent (no border, no background) so it
inherits the DS .env pill styling (success-colored badge with
mono font). Negative margins compensate for the pill padding.
Dropdown chevron uses currentColor to match the pill text.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 15:53:09 +02:00
hsiegeln
08f2a01057 fix: always show environment selector in TopBar
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m4s
CI / docker (push) Successful in 1m12s
CI / deploy (push) Successful in 44s
CI / deploy-feature (push) Has been skipped
Use unfiltered agent query to discover environments (avoids circular
filter). Always show selector even with single environment so it's
visible as a label. Default to ['default'] when no agents connected.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 15:47:48 +02:00
hsiegeln
574f82b731 docs: add historical implementation plans
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m7s
CI / docker (push) Successful in 37s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 15:45:49 +02:00
hsiegeln
c2d4d38bfb feat: move environment selector into TopBar (DS v0.1.30)
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m6s
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / docker (push) Has been cancelled
Update @cameleer/design-system to v0.1.30 which accepts ReactNode
for the environment prop. Move EnvironmentSelector from standalone
div into TopBar, rendering between theme toggle and user menu.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 15:43:43 +02:00
hsiegeln
694d0eef59 feat: add environment filtering across all APIs and UI
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m8s
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / docker (push) Has been cancelled
Backend: Added optional `environment` query parameter to catalog,
search, stats, timeseries, punchcard, top-errors, logs, and agents
endpoints. ClickHouse queries filter by environment when specified
(literal SQL for AggregatingMergeTree, ? binds for raw tables).
StatsStore interface methods all accept environment parameter.

UI: Added EnvironmentSelector component (compact native select).
LayoutShell extracts distinct environments from agent data and
passes selected environment to catalog and agent queries via URL
search param (?env=). TopBar shows current environment label.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 15:42:26 +02:00
hsiegeln
babdc1d7a4 docs: update CLAUDE.md with multitenancy architecture
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m8s
CI / docker (push) Successful in 11s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 35s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 15:14:38 +02:00
hsiegeln
a188308ec5 feat: implement multitenancy with tenant isolation + environment support
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m8s
CI / docker (push) Successful in 42s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 1m25s
Adds configurable tenant ID (CAMELEER_TENANT_ID env var, default:
"default") and environment as a first-class concept. Each server
instance serves one tenant with multiple environments.

Changes across 36 files:
- TenantProperties config bean for tenant ID injection
- AgentInfo: added environmentId field
- AgentRegistrationRequest: added environmentId field
- All 9 ClickHouse stores: inject tenant ID, replace hardcoded
  "default" constant, add environment to writes/reads
- ChunkAccumulator: configurable tenant ID + environment resolver
- MergedExecution/ProcessorBatch/BufferedLogEntry: added environment
- ClickHouse init.sql: added environment column to all tables,
  updated ORDER BY (tenant→time→env→app), added tenant_id to
  usage_events, updated all MV GROUP BY clauses
- Controllers: pass environmentId through registration/auto-heal
- K8s deploy: added CAMELEER_TENANT_ID env var
- All tests updated for new signatures

Closes #123

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 15:00:18 +02:00
hsiegeln
ee7226cf1c docs: multitenancy architecture design spec
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m4s
CI / docker (push) Successful in 10s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
Covers tenant isolation (1 tenant = 1 server instance), environment
support (first-class agent property), ClickHouse partitioning
(tenant → time → environment → application), PostgreSQL schema-per-
tenant via JDBC currentSchema, and agent protocol changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 14:37:00 +02:00
hsiegeln
7429b85964 feat: show route control bar on topology diagram
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m8s
CI / docker (push) Successful in 59s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s
When no exchange is selected, the topology-only diagram now shows
the RouteControlBar above it (if the agent supports routeControl
or replay and the user has OPERATOR/ADMIN role). This fixes a gap
where suspended routes with no recent exchanges had no way to be
resumed from the UI.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 13:49:28 +02:00
hsiegeln
a5c07b8585 docs: update CLAUDE.md with heartbeat capabilities restoration
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m28s
CI / docker (push) Successful in 10s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 13:31:33 +02:00
hsiegeln
45a74075a1 feat: restore agent capabilities from heartbeat after server restart
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m8s
CI / docker (push) Successful in 40s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
The heartbeat now carries capabilities (per protocol v2 update).
On each heartbeat, capabilities are updated in the agent registry.
On auto-heal (server restart), capabilities from the heartbeat
are used instead of empty Map.of(), so the agent's feature flags
(replay, routeControl, logForwarding, etc.) are restored
immediately on the first heartbeat.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 13:19:15 +02:00
hsiegeln
abed4dc96f security: fix SQL injection in ClickHouse query escaping
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m48s
CI / docker (push) Successful in 1m6s
CI / deploy (push) Successful in 47s
CI / deploy-feature (push) Has been skipped
Convert ClickHouseUsageTracker and ClickHouseMetricsQueryStore to
use JDBC parameterized queries (? binds) — these query raw tables
without AggregateFunction columns.

Fix lit(String) in RouteMetricsController and ClickHouseStatsStore
to escape backslashes before single quotes. Without this, an input
like \' breaks out of the string literal in ClickHouse (where \
is an escaped backslash). These must remain as literal SQL because
the ClickHouse JDBC 0.9.x driver wraps PreparedStatement in
sub-queries that strip AggregateFunction types, breaking -Merge
combinators.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 12:17:12 +02:00
hsiegeln
170b2c4a02 fix: run sonar:sonar in same reactor as verify
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m5s
CI / docker (push) Successful in 10s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 35s
Running mvn sonar:sonar as a separate invocation skips child
modules. Combining verify and sonar:sonar in a single mvn
command ensures the reactor processes all modules.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 11:57:05 +02:00
hsiegeln
66e91ba18c fix: remove explicit sonar.sources/tests from mvn sonar:sonar
All checks were successful
CI / build (push) Successful in 2m0s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 14s
CI / deploy (push) Successful in 40s
CI / deploy-feature (push) Has been skipped
Maven sonar plugin auto-detects sources and tests from the POM
module structure. Passing sonar.sources as CLI args caused path
doubling (module-dir/module-dir/src) in multi-module projects.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 11:13:47 +02:00
hsiegeln
e30b561dfe fix: use mvn sonar:sonar instead of standalone sonar-scanner
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m31s
CI / docker (push) Successful in 14s
CI / deploy (push) Successful in 44s
CI / deploy-feature (push) Has been skipped
The standalone sonar-scanner CLI has Java discovery issues in the
build container. Switch to the Maven sonar plugin (same approach
as cameleer3 agent repo), which uses Maven's own JDK. This also
removes the sonar-scanner download/install step entirely.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 11:07:49 +02:00
hsiegeln
5ae94e1e2c fix: set SONAR_SCANNER_JAVA_HOME for sonar-scanner 6.x
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m42s
CI / docker (push) Successful in 15s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 48s
sonar-scanner 6.x checks SONAR_SCANNER_JAVA_HOME, not JAVA_HOME.
Despite JAVA_HOME being correct and java being on PATH, the scanner
uses its own env var for Java discovery.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 11:04:03 +02:00
hsiegeln
7dca8f2609 fix: derive JAVA_HOME from jar binary and add to PATH
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m16s
CI / docker (push) Successful in 13s
CI / deploy (push) Successful in 50s
CI / deploy-feature (push) Has been skipped
java binary may not be on PATH directly in the build container.
Derive JAVA_HOME from the jar binary location (which we know works)
and prepend JAVA_HOME/bin to PATH so sonar-scanner can find java.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 10:59:45 +02:00
hsiegeln
2589c681c5 fix: derive JAVA_HOME for sonar-scanner in CI workflow
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m53s
CI / docker (push) Successful in 14s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 35s
sonar-scanner 6.x requires JAVA_HOME or java on PATH. The build
container has Java installed but doesn't export JAVA_HOME, so
derive it from the java binary location.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 10:05:18 +02:00
hsiegeln
352fa43ef8 fix: add chmod +x for sonar-scanner binary after jar extraction
All checks were successful
CI / build (push) Successful in 2m5s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 10s
CI / deploy (push) Successful in 51s
CI / deploy-feature (push) Has been skipped
jar xf doesn't preserve Unix file permissions from zip entries,
so the sonar-scanner binary lacks the execute bit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 09:57:48 +02:00
hsiegeln
b04b12220b fix: resolve 25 SonarQube code smells across 21 files
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m2s
CI / docker (push) Successful in 45s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s
Remove unused fields (log, rbacService, roleRepository, jwt),
unused variables (agentTps, routeKeys, updated), unused imports
(HttpHeaders, JdbcTemplate). Rename restricted identifier 'record'
to 'auditRecord'/'event'. Return empty collections instead of null.
Replace .collect(Collectors.toList()) with .toList(). Simplify
conditional return in BootstrapTokenValidator.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 09:36:13 +02:00
hsiegeln
633a61d89d perf: batch processor and log inserts to reduce ClickHouse part creation
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m7s
CI / docker (push) Successful in 39s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 1m2s
SonarQube / sonarqube (push) Failing after 1m58s
Diagnostics showed ~3,200 tiny inserts per 5 minutes:
- processor_executions: 2,376 inserts (14 rows avg) — one per chunk
- logs: 803 inserts (5 rows avg) — synchronous in HTTP handler

Fix 1: Consolidate processor inserts — new insertProcessorBatches() method
flattens all ProcessorBatch records into a single INSERT per flush cycle.

Fix 2: Buffer log inserts — route through WriteBuffer<BufferedLogEntry>,
flushed on the same 5s interval as executions. LogIngestionController now
pushes to buffer instead of inserting directly.

Also reverts async_insert config (doesn't work with JDBC inline VALUES).

Expected: ~3,200 inserts/5min → ~160 (20x reduction in part creation,
MV triggers, and background merge work).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 22:48:04 +02:00
hsiegeln
e0aac4bf0a perf: enable ClickHouse async_insert to batch small inserts server-side
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m6s
CI / docker (push) Successful in 41s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 43s
Diagnostics showed 3,200 tiny inserts per 5 minutes (processor_executions:
2,376 at 14 rows avg, logs: 803 at 5 rows avg), each creating a new part
and triggering MV aggregations + background merges. This was the root cause
of ~400m CPU usage at 3 tx/s.

async_insert=1 with 5s busy timeout lets ClickHouse buffer incoming inserts
and consolidate them into fewer, larger parts before writing to disk.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 22:33:48 +02:00
hsiegeln
ac94a67a49 fix: reduce ClickHouse CPU by increasing flush interval, rename LIVE→AUTO labels
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m7s
CI / docker (push) Successful in 1m24s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 41s
- Increase ingestion flush interval from 500ms to 5000ms to reduce MV merge storms
- Reduce ClickHouse background_schedule_pool_size from 8 to 4
- Rename LIVE/PAUSED badge labels to AUTO/MANUAL across all pages
- Update design system to v0.1.29

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 22:05:29 +02:00
hsiegeln
e1cb9d7872 fix: extract snapshot data from chunks, reduce ClickHouse log noise
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m11s
CI / docker (push) Successful in 41s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
- ChunkAccumulator now extracts inputBody/outputBody/inputHeaders/outputHeaders
  from ExecutionChunk.inputSnapshot/outputSnapshot instead of storing empty strings
- Set ClickHouse server log level to warning (was trace by default)
- Update CLAUDE.md to document Ed25519 key derivation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 17:58:54 +02:00
hsiegeln
a9ec424d52 fix: derive Ed25519 signing key from JWT secret, no DB storage
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m11s
CI / docker (push) Successful in 42s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Replace DB-persisted keypair with deterministic derivation from
CAMELEER_JWT_SECRET via HMAC-SHA256 seed + seeded SHA1PRNG KeyPairGenerator.
Same secret = same key pair across restarts, no private key in the database.

Closes #121

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 17:18:43 +02:00
hsiegeln
81f13396a0 fix: persist Ed25519 signing key to survive server restarts
All checks were successful
CI / build (push) Successful in 2m8s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 50s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 54s
The keypair was generated ephemerally on each startup, causing agents
to reject all commands after a server restart (signature mismatch).
Now persisted to PostgreSQL server_config table and restored on startup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 17:13:40 +02:00
hsiegeln
670e458376 fix: update ITs to use consolidated init.sql, remove dead code
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m23s
CI / docker (push) Successful in 1m29s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 50s
- All 7 ClickHouse integration tests now load init.sql via shared
  ClickHouseTestHelper instead of deleted V1-V11 migration files
- Remove unused useScope exports (setApp, setRoute, setExchange, clearScope)
- Remove unused CSS classes (monoCell, punchcardStack)
- Update ui/README.md DS version to v0.1.28

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 17:03:54 +02:00
hsiegeln
d4327af6a4 refactor: consolidate ClickHouse schema into single init.sql, cache diagrams
All checks were successful
CI / build (push) Successful in 2m2s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 51s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
- Merge all V1-V11 migration scripts into one idempotent init.sql
- Simplify ClickHouseSchemaInitializer to load single file
- Replace route_diagrams projection with in-memory caches:
  hashCache (routeId+instanceId → contentHash) warm-loaded on startup,
  graphCache (contentHash → RouteGraph) lazy-populated on access
- Eliminates 9M+ row scans on diagram lookups

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 15:24:53 +02:00
hsiegeln
bb3e1e2bc3 fix: set deduplicate_merge_projection_mode for ReplacingMergeTree projection
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m8s
CI / docker (push) Successful in 42s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
ClickHouse 24.12 requires this setting before adding projections to
ReplacingMergeTree tables. Using 'drop' mode which discards the projection
during deduplication merges and rebuilds it afterward.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 15:14:56 +02:00
hsiegeln
984bb2d40f fix: sort ClickHouse migration scripts by numeric version prefix
All checks were successful
CI / build (push) Successful in 2m32s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 55s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 52s
Alphabetical sort put V10/V11 before V2-V9 ("V11" < "V1_" in ASCII),
causing the route_diagrams projection to run before the table existed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 15:06:56 +02:00
hsiegeln
6f00ff2e28 fix: reduce ClickHouse log noise, admin query spam, and diagram scan perf
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m7s
CI / docker (push) Successful in 1m25s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 40s
- Set com.clickhouse log level to INFO and org.apache.hc.client5 to WARN
- Admin hooks (useUsers/useGroups/useRoles) now only fetch on admin pages,
  eliminating AUDIT view_users entries on every UI click
- Add ClickHouse projection on route_diagrams for (tenant_id, route_id,
  instance_id, created_at) to avoid full table scans on diagram lookups
- Bump @cameleer/design-system to v0.1.28 (PAUSED mode time range fix,
  refreshTimeRange API)
- Call refreshTimeRange before invalidateQueries in PAUSED mode manual
  refresh so sidebar clicks use current time window

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 14:48:30 +02:00
hsiegeln
2708bcec17 fix: first exchange click doesn't highlight selected row
All checks were successful
CI / build (push) Successful in 1m47s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 1m45s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 53s
On first click, Dashboard was in non-split mode. The click set
selectedId locally then triggered split view, which remounted
Dashboard — losing the selectedId state.

Added activeExchangeId prop passed from ExchangesPage so the
selection survives the remount. Also syncs via useEffect when
parent changes selection (e.g. correlated exchange navigation).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 11:28:26 +02:00
hsiegeln
901dfd1eb8 fix: PAUSED mode disabled queries entirely instead of just polling
Some checks failed
CI / build (push) Successful in 1m49s
CI / cleanup-branch (push) Has been skipped
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / docker (push) Has been cancelled
useLiveQuery returned enabled:false when paused, which prevented
queries from running at all. Changed to enabled:true always —
PAUSED now means "fetch once, no polling" instead of "don't fetch".

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 11:25:04 +02:00
hsiegeln
726e77bb91 docs: update all documentation for session changes
Some checks failed
CI / build (push) Successful in 2m2s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CLAUDE.md:
- Agent registry auto-heal note (in-memory, JWT fallback)
- Usage analytics (ClickHouse usage_events table)

HOWTO.md:
- Architecture diagram: added deploy-demo (NodePort 30092) and cameleer-demo namespace
- Access URLs: added Deploy Demo
- Agent registry: server restart resilience documentation
- Route control: CommandGroupResponse note

ui/README.md:
- Fixed outdated generate-api command
- Added DS version (v0.1.26)
- Fixed VITE_API_TARGET (30081 not 30090)
- Added key features section (cmd-k, LIVE mode, route control, event icons)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 11:22:44 +02:00
hsiegeln
d30c267292 fix: route catalog missing routes after server restart
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m20s
CI / docker (push) Successful in 52s
CI / deploy (push) Successful in 54s
CI / deploy-feature (push) Has been skipped
After server restart, auto-healed agents register with empty
routeIds. The catalog only looked at agent registry for routes,
so routes and counts disappeared.

Now merges route IDs from ClickHouse stats_1m_route into the
catalog. Also includes apps that only exist in ClickHouse data
(no agent currently registered). Routes and exchange counts
survive server restarts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 11:14:27 +02:00
hsiegeln
37c10ae0a6 feat: manual refresh on sidebar navigation when LIVE mode is off
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m9s
CI / docker (push) Successful in 57s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
When autoRefresh is disabled, sidebar clicks now invalidate all
queries (queryClient.invalidateQueries()), triggering a re-fetch.
This gives users "click to refresh" behavior instead of stale data.

When LIVE mode is on, queries already poll at intervals, so no
invalidation is needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 11:01:29 +02:00
hsiegeln
c16f0e62ed fix: clicking Applications header navigates back to all apps
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m31s
CI / docker (push) Successful in 1m22s
CI / deploy (push) Failing after 2m26s
CI / deploy-feature (push) Has been skipped
When the Applications section is already expanded, clicking the
header now navigates to /{tab} (all applications) instead of
collapsing. When collapsed, clicking expands as before.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 10:49:54 +02:00
hsiegeln
2bc3efad7f fix: agent auth, heartbeat, and SSE all break after server restart
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m9s
CI / docker (push) Successful in 41s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Three related issues caused by in-memory agent registry being empty
after server restart:

1. JwtAuthenticationFilter rejected valid agent JWTs if agent wasn't
   in registry — now authenticates any valid JWT regardless

2. Heartbeat returned 404 for unknown agents — now auto-registers
   the agent from JWT claims (subject, application)

3. SSE endpoint returned 404 — same auto-registration fix

JWT validation result is stored as a request attribute so downstream
controllers can extract the application claim for auto-registration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 10:41:23 +02:00
hsiegeln
0632f1c6a8 fix: agent token refresh returns 404 after server restart
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m8s
CI / docker (push) Successful in 42s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 1m23s
The refresh endpoint required the agent to exist in the in-memory
registry. After server restart the registry is empty, so all refresh
attempts got 404. The refresh token itself is self-contained with
subject, application, and roles — the registry lookup is optional.

Now uses application from the JWT, falling back to registry only
if the agent happens to be registered.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 10:37:57 +02:00
hsiegeln
bdac363e40 fix: active queries list always showed itself
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m8s
CI / docker (push) Successful in 40s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
The system.processes query was returning its own row. Added
filter: query NOT LIKE '%system.processes%'

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 10:33:47 +02:00
hsiegeln
d9615204bf fix: admin pages not scrollable (content clipped by overflow:hidden)
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m8s
CI / docker (push) Successful in 1m0s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Has been cancelled
AdminLayout was a plain div with padding but no scroll. The parent
<main> has overflow:hidden, so admin page content beyond viewport
height was clipped. Added flex:1, overflow:auto, minHeight:0 to
make AdminLayout a proper scroll container.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 10:31:04 +02:00
hsiegeln
2896bb90a9 fix: usage events never flushed to ClickHouse
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m11s
CI / docker (push) Successful in 42s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 1m7s
UsageFlushScheduler was a @Component with @ConditionalOnBean, but
ClickHouseUsageTracker is created via @Bean — component scan runs
first, so the condition always evaluated false. Events accumulated
in the WriteBuffer but flush() was never called.

Moved scheduler to @Bean in StorageBeanConfig with the same
@ConditionalOnProperty guard as the tracker.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 10:07:13 +02:00
hsiegeln
a036d8a027 docs: spec for cameleer-deploy-demo prototype
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 00:13:04 +02:00
hsiegeln
44a37317d1 fix: cmd-k context key for tab reset and Enter-to-navigate on admin pages
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m10s
CI / docker (push) Successful in 1m27s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 51s
SonarQube / sonarqube (push) Failing after 2m22s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 23:53:09 +02:00
hsiegeln
146398b183 feat: RBAC page reads cmd-k navigation state for tab switch and highlight
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 23:42:18 +02:00
hsiegeln
69ca52b25e feat: handle admin cmd-k selection with tab navigation state 2026-04-02 23:38:06 +02:00
hsiegeln
111bcc302d feat: build admin search data for cmd-k on admin pages
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 23:34:52 +02:00
hsiegeln
cf36f81ef1 chore: bump @cameleer/design-system to v0.1.26 2026-04-02 23:33:00 +02:00
hsiegeln
28f38331cc docs: implementation plan for context-aware cmd-k search
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 23:27:07 +02:00
hsiegeln
394fde30c7 docs: spec for context-aware cmd-k search
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 23:21:53 +02:00
hsiegeln
62b5c56c56 feat: event-type icons for agent event feeds
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m7s
CI / docker (push) Successful in 1m0s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 53s
Icons now reflect event type (UserPlus for registration, Skull
for dead, HeartPulse for recovery, Route for state changes, etc.)
while severity still drives the color. Updated in both
AgentInstance and AgentHealth pages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 23:06:01 +02:00
hsiegeln
9b401558a5 fix: make disabled route control buttons visually distinct
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m6s
CI / docker (push) Successful in 59s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 41s
Disabled buttons now show reduced opacity (0.35) and muted icon
color instead of just changing the cursor.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 22:58:46 +02:00
hsiegeln
38b76513c7 feat: route control buttons reflect current route state
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m8s
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / docker (push) Has been cancelled
Buttons are disabled based on route state: Started disables
Start/Resume, Stopped disables Stop/Suspend/Resume, Suspended
disables Start/Suspend. State looked up from catalog API.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 22:56:49 +02:00
hsiegeln
2265ebf801 chore: bump @cameleer/design-system to v0.1.25
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m9s
CI / docker (push) Successful in 1m23s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 1m12s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 22:47:53 +02:00
hsiegeln
20af81a5dc feat: show server version in sidebar header
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m19s
CI / docker (push) Successful in 1m30s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 1m39s
Version injected at build time via VITE_APP_VERSION env var.
CI sets it to branch@sha. Falls back to 'dev' in local dev.
Displayed next to "Cameleer" in the sidebar header.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 22:42:06 +02:00
hsiegeln
d819f88ae4 fix: starred routes not showing — starKey prefix mismatch
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m11s
CI / docker (push) Successful in 1m1s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 53s
collectStarredItems used 'app:' prefix for route keys but
buildAppTreeNodes uses 'route:' prefix. Routes were starred
but never matched in the starred section.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 22:36:28 +02:00
hsiegeln
5880abdd93 fix: keep admin section in place, don't move to top
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m13s
CI / docker (push) Successful in 57s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 39s
Admin section stays in its fixed position (after Starred, before
Footer). Entering admin mode collapses Applications and Starred
but does not reorder sections.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 22:32:53 +02:00
hsiegeln
b676450995 fix: simplify sidebar to Applications + Starred + Admin footer
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m7s
CI / docker (push) Successful in 59s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 42s
Remove Agents and Routes sections from sidebar. Layout is now:
Header (camel logo + Cameleer) → Search → Applications section →
Starred section (when items exist) → Footer (Admin + API Docs).

Admin accordion: clicking Admin navigates to /admin/rbac and
expands Admin section at top while collapsing Applications and
Starred. Clicking Applications exits admin mode.

Removed buildAgentTreeNodes and buildRouteTreeNodes from
sidebar-utils (no longer needed).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 22:29:44 +02:00
hsiegeln
e495b80432 fix: increase ClickHouse pool size and reduce flush interval
All checks were successful
CI / build (push) Successful in 1m49s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 2m10s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 43s
Pool was hardcoded to 10 connections serving 7 concurrent write
streams + UI reads, causing "too many simultaneous queries" and
WriteBuffer overflow. Pool now defaults to 50 (configurable via
clickhouse.pool-size), flush interval reduced from 1000ms to 500ms.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 22:11:15 +02:00
hsiegeln
45eab761b7 chore: bump @cameleer/design-system to v0.1.24
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 2m3s
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / docker (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 22:06:13 +02:00
hsiegeln
8d899cc70c refactor: use HeartbeatRequest from cameleer3-common
Some checks failed
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / cleanup-branch (push) Has been cancelled
CI / build (push) Has been cancelled
Replace local HeartbeatRequest DTO with the shared model from
cameleer3-common. Message types exchanged between server and agent
belong in the common module.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 22:05:26 +02:00
hsiegeln
520b80444a feat(#119): accept route states in heartbeat and state-change events
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 34s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Replace ACK-based route state inference with agent-reported state.
Heartbeats now carry optional routeStates map, and ROUTE_STATE_CHANGED
events update the registry immediately.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 21:45:13 +02:00
hsiegeln
17aff5ef9d docs: route state protocol extension spec
Defines two backward-compatible mechanisms for accurate route state
tracking: heartbeat extension (routeStates map in heartbeat body)
and ROUTE_STATE_CHANGED events for real-time updates. Covers
agent-side detection via Camel EventNotifier, server-side handling,
multi-agent conflict resolution, and migration path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 19:26:38 +02:00
hsiegeln
b714d3363f feat(#119): expose route state in catalog API and sidebar/dashboard
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 29s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Add routeState field to RouteSummary DTO (null for started, 'stopped'
or 'suspended' for non-default states). Sidebar shows stop/pause icons
and state badge for affected routes in both Apps and Routes sections.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 19:15:46 +02:00
hsiegeln
0acceaf1a9 feat(#119): add RouteStateRegistry for tracking route operational state
In-memory registry that infers route state (started/stopped/suspended)
from successful route-control command ACKs. Updates state only when all
agents in a group confirm success.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 19:15:35 +02:00
hsiegeln
ca1d472b78 feat(#117): agent-count toasts and persistent error toast dismiss
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 30s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 19:08:00 +02:00
hsiegeln
c3b4f70913 feat(#116): update command hooks for synchronous group response
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 30s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Add CommandGroupResponse and ConfigUpdateResponse types. Switch
useSendGroupCommand and useSendRouteCommand from openapi-fetch to authFetch
returning CommandGroupResponse. Update useUpdateApplicationConfig to return
ConfigUpdateResponse and fix all consumer onSuccess callbacks to access
saved.config.version instead of saved.version.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 19:01:06 +02:00
hsiegeln
027e45aadf feat(#116): synchronous group command dispatch with multi-agent response collection
Add addGroupCommandWithReplies() to AgentRegistryService that sends commands
to all LIVE agents in a group and returns CompletableFuture per agent for
collecting replies. Update sendGroupCommand() and pushConfigToAgents() to
wait with a shared 10-second deadline, returning CommandGroupResponse with
per-agent status, timeouts, and overall success. Config update endpoint now
returns ConfigUpdateResponse wrapping both the saved config and push result.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 19:00:56 +02:00
hsiegeln
f39f07e7bf feat(#118): add confirmation dialog for stop and suspend commands
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 35s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Stop and suspend route commands now show a ConfirmDialog requiring
typed confirmation before dispatch. Start and resume execute
immediately without confirmation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 18:54:23 +02:00
hsiegeln
d21d8b2c48 fix(#112): initialize sidebar accordion state from initial route
Some checks failed
CI / build (push) Failing after 43s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Direct navigation to /admin/* now correctly opens Admin section
and collapses operational sections on first render. Previously
the accordion effect only triggered on route transitions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 18:36:43 +02:00
hsiegeln
d5f5601554 fix(#112): add missing Routes section, fix admin double padding
Review feedback: buildRouteTreeNodes was defined but never rendered.
Added Routes section between Agents and Admin. Removed duplicate
padding on admin pages (AdminLayout handles its own padding).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 18:32:26 +02:00
hsiegeln
00042b1d14 feat(#112): remove admin tabs, sidebar handles navigation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 18:29:29 +02:00
hsiegeln
fe49eb5aba feat(#112): migrate to composable sidebar with accordion and collapse
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 18:29:25 +02:00
hsiegeln
bc913eef6e feat(#112): extract sidebar tree builders and types from DS
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 18:29:22 +02:00
hsiegeln
d70ad91b33 docs: clarify search ownership and icon-rail click behavior
Search: DS renders dumb input, app owns filterQuery state and
passes it to each SidebarTree. Icon-rail click: fires both
onCollapseToggle and onToggle simultaneously, no navigation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 17:41:31 +02:00
hsiegeln
ba361af2d7 docs: composable sidebar design spec for #112
Replaces the previous "hide sidebar on admin" approach with a
composable compound component design. DS provides shell + building
blocks (Sidebar, Section, Footer, SidebarTree); consuming app
controls all content, section ordering, accordion behavior, and
icon-rail collapse.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 17:38:01 +02:00
hsiegeln
78777d2ba6 Revert "feat(#112): hide sidebar, topbar, cmd palette on admin pages"
This reverts commit d95e518622.
2026-04-02 17:22:06 +02:00
hsiegeln
3f8a9715a4 Revert "feat(#112): add admin header bar with back button and logout"
This reverts commit a484364029.
2026-04-02 17:22:06 +02:00
hsiegeln
f00a3e8b97 Revert "fix(#112): remove dead admin breadcrumb code, add logout aria-label"
This reverts commit d5028193c0.
2026-04-02 17:22:06 +02:00
hsiegeln
d5028193c0 fix(#112): remove dead admin breadcrumb code, add logout aria-label
Review feedback: breadcrumb memo had an unused isAdminPage branch
(TopBar no longer renders on admin pages). Added aria-label to
icon-only logout button for screen readers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 17:16:01 +02:00
hsiegeln
a484364029 feat(#112): add admin header bar with back button and logout
AdminLayout gains a self-contained header (Back / Admin / user+logout)
with CSS module styles, replacing the inline padding wrapper. Admin
pages now render fully without the main app chrome.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 17:12:50 +02:00
hsiegeln
d95e518622 feat(#112): hide sidebar, topbar, cmd palette on admin pages
Pass null as sidebar prop, guard TopBar and CommandPalette with
!isAdminPage, and remove conditional admin padding from main element.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 17:12:44 +02:00
hsiegeln
56297701e6 fix: use ILIKE for case-insensitive log search in ClickHouse
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m4s
CI / docker (push) Successful in 57s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 1m29s
LIKE is case-sensitive in ClickHouse. Switch to ILIKE for message,
stack_trace, and logger_name searches so queries match regardless
of casing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 16:35:34 +02:00
hsiegeln
8c7c9911c4 feat: highlight search matches in log results
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m7s
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
Recursive case-insensitive highlighting of the search query in
collapsed message, expanded full message, and stack trace. Uses the
project's amber accent color for the highlight mark.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 16:34:15 +02:00
hsiegeln
4d66d6ab23 fix: use deterministic badge color for app names in Logs tab
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m9s
CI / docker (push) Successful in 1m0s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 42s
Use attributeBadgeColor() (hash-based) instead of "auto" so the same
application name gets the same badge color across all pages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 16:31:04 +02:00
hsiegeln
b73f5e6dd4 feat: add Logs tab with cursor-paginated search, level filters, and live tail
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m3s
CI / docker (push) Successful in 1m11s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 49s
- Extend GET /api/v1/logs with cursor pagination, multi-level filtering,
  optional application scoping, and level count aggregation
- Add exchangeId, instanceId, application, mdc fields to log responses
- Refactor ClickHouseLogStore with keyset pagination (N+1 pattern)
- Add LogSearchRequest/LogSearchResponse core domain records
- Create LogSearchPageResponse wrapper DTO
- Add Logs as 4th content tab (Exchanges | Dashboard | Runtime | Logs)
- Implement LogSearch component with debounced search, level filter bar,
  expandable log entries, cursor pagination, and live tail mode
- Add cross-navigation: exchange header → logs, log tab → logs tab
- Update ClickHouseLogStoreIT with cursor, multi-level, cross-app tests

Closes: #104

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 08:47:16 +02:00
hsiegeln
a52751da1b fix: avoid alias shadowing in processor metrics -Merge query
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m11s
CI / docker (push) Successful in 43s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
SonarQube / sonarqube (push) Failing after 1m52s
ClickHouse 24.12 new query analyzer resolves countMerge(total_count)
in the CASE WHEN to the SELECT alias (UInt64) instead of the original
AggregateFunction column when the alias has the same name. Renamed
aliases to tc/fc to avoid the collision.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 22:24:50 +02:00
hsiegeln
51780031ea fix: use alias in ORDER BY for processor metrics query
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m8s
CI / docker (push) Successful in 44s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 39s
ClickHouse rejects countMerge() in ORDER BY after GROUP BY because the
column is already finalized to UInt64. Use the SELECT alias instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 22:11:54 +02:00
hsiegeln
eb2cafc7fa fix: use jar instead of unzip in sonarqube workflow
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m5s
CI / docker (push) Successful in 39s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 47s
The build container lacks unzip. The JDK jar command handles zip
extraction natively.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 22:02:09 +02:00
hsiegeln
805e6d51cb fix: add processor_type to stats_1m_processor_detail MV
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m14s
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / docker (push) Has been cancelled
The table and materialized view were missing the processor_type column,
causing the RouteMetricsController query to fail and the dashboard
processor metrics table to render empty.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 22:00:23 +02:00
hsiegeln
f3feaddbfe feat: show distinct attribute keys in cmd-k Attributes tab
All checks were successful
CI / build (push) Successful in 1m58s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 1m46s
CI / deploy (push) Successful in 47s
CI / deploy-feature (push) Has been skipped
Add GET /search/attributes/keys endpoint that queries distinct
attribute key names from ClickHouse using JSONExtractKeys. Attribute
keys appear in the cmd-k Attributes tab alongside attribute value
matches from exchange results.

- SearchIndex.distinctAttributeKeys() interface method
- ClickHouseSearchIndex implementation using arrayJoin(JSONExtractKeys)
- SearchController /attributes/keys endpoint
- useAttributeKeys() React Query hook
- buildSearchData includes attribute keys as 'attribute' category items

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 21:39:27 +02:00
hsiegeln
9057981cf7 fix: use composite ID for routes in command palette search data
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m7s
CI / docker (push) Successful in 1m1s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 55s
Routes with the same name across different applications (e.g., "route1"
in both QUARKUS-APP and BACKEND-APP) were deduplicated because they
shared the same id (routeId). Use appId/routeId as the id so all
routes appear in cmd-k results.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 21:33:23 +02:00
hsiegeln
b30a5b5760 fix: prevent cmd-k scroll reset on catalog poll refresh
All checks were successful
CI / build (push) Successful in 1m49s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 2m3s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 1m0s
The searchData useMemo recomputed on every catalog poll cycle because
catalogData got a new array reference even when content was unchanged.
This caused the CommandPalette list to re-render and reset scroll.

Use a ref with deep equality check to keep a stable catalog reference,
only updating when the actual data changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 21:22:50 +02:00
hsiegeln
910230cbf8 fix: add <mark> highlighting to search match context snippets
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m14s
CI / docker (push) Successful in 46s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s
The command palette renders matchContext via dangerouslySetInnerHTML
expecting HTML with <mark> tags, but extractSnippet() returned plain
text. Wrap the matched term in <mark> tags and escape surrounding
text to prevent XSS.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 21:18:04 +02:00
hsiegeln
1d791bb329 fix: use exact match for ID fields in full-text search
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m5s
CI / docker (push) Successful in 41s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
ID fields (execution_id, correlation_id, exchange_id) should use
exact equality, not LIKE with wildcards. LIKE is only needed for
the _search_text full-text columns.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 21:13:54 +02:00
hsiegeln
9781fe0d7c fix: include execution/correlation/exchange IDs in full-text search
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m12s
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / docker (push) Has been cancelled
The _search_text materialized column only contained error messages,
bodies, and headers — not execution_id, correlation_id, exchange_id,
or route_id. Searching by ID via cmd-k returned no results.

- Add ID fields to _search_text in ClickHouse DDL (covered by ngram
  bloom filter index)
- Add direct LIKE matches on execution_id, correlation_id, exchange_id
  in the text search WHERE clause for faster exact ID lookups

Requires ClickHouse table recreation (fresh install).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 21:12:15 +02:00
hsiegeln
92951f1dcf chore: update @cameleer/design-system to v0.1.22
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m12s
CI / docker (push) Successful in 1m27s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Has been cancelled
Sidebar selectedPath now uses sidebarReveal on all tabs, not just
exchanges. This fixes sidebar highlighting on dashboard and runtime.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 21:09:20 +02:00
hsiegeln
a7d256b38a fix: compute hasTraceData from processor records in chunk accumulator
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m8s
CI / docker (push) Successful in 43s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s
The chunked ingestion path hardcoded hasTraceData=false because the
execution envelope doesn't carry processor bodies. But the processor
records DO have inputBody/outputBody — we just need to check them.

Track hasTraceData across chunks in PendingExchange and pass it to
MergedExecution when the final chunk arrives or on stale sweep.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 21:04:34 +02:00
hsiegeln
e26266532a fix: regenerate OpenAPI types, fix search scoping by applicationId
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m10s
CI / docker (push) Successful in 59s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 40s
The identity rename (application→applicationId) broke search filtering
because the stale schema.d.ts still had 'application' as the field name.
The backend silently ignored the unknown field, returning unfiltered results.

- Regenerate openapi.json and schema.d.ts from live backend
- Fix Dashboard: application→applicationId in search request
- Fix RouteDetail: application→applicationId in search request (2 places)
- LayoutShell: scope command palette search by appId/routeId
- LayoutShell: pass sidebarReveal state on sidebar click navigation

Note for DS team: the Sidebar selectedPath logic (line 5451 in dist)
has a hardcoded pathname.startsWith("/exchanges/") guard. This should
be broadened to simply `S ? S : $.pathname` so sidebarReveal works on
all tabs (dashboard, runtime), not just exchanges.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 20:55:19 +02:00
hsiegeln
178bc40706 Revert "fix: sidebar selection highlight and scoped command palette search"
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m19s
CI / docker (push) Successful in 9s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
This reverts commit 4168a6d45b.
2026-04-01 20:43:27 +02:00
hsiegeln
4168a6d45b fix: sidebar selection highlight and scoped command palette search
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m9s
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / docker (push) Has been cancelled
Two fixes:
- Pass sidebarReveal state on sidebar navigation so the design system
  can highlight the selected entry (it compares internal /apps/... paths
  against this state value, not the browser URL)
- Command palette search now includes scope.appId and scope.routeId
  so results are filtered to the current sidebar selection

Note: sidebar highlighting works on the exchanges tab. The design
system's selectedPath logic only checks pathname.startsWith("/exchanges/")
for sidebarReveal — a DS update is needed to support /dashboard/ and
/runtime/ tabs too.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 20:41:42 +02:00
hsiegeln
a028905e41 fix: update agent field names in frontend to match backend DTO
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m6s
CI / docker (push) Successful in 57s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
The AgentInstanceResponse backend DTO uses instanceId, displayName,
applicationId, status — but the stale schema.d.ts still had id, name,
application, state. This caused the runtime table to show no data.

- Update schema.d.ts AgentInstanceResponse fields
- Fix AgentHealth: row.id→instanceId, row.name→displayName,
  row.application→applicationId, inst.id→instanceId
- Fix AgentInstance: agent.id→instanceId, agent.name→displayName
- Fix ExchangeHeader: agent.id→instanceId, agent.state→status
- Fix LayoutShell search: agent.state→status, agentTps→tps

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 20:36:31 +02:00
hsiegeln
f82aa26371 fix: improve ClickHouse admin page, fix AgentHealth type error
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m13s
CI / docker (push) Successful in 3m46s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 58s
Rewrite ClickHouse admin to show useful storage metrics instead of
often-empty system.events data. Add active queries section.

- Replace performance endpoint: query system.parts for disk size,
  uncompressed size, compression ratio, total rows, part count
- Add /queries endpoint querying system.processes for active queries
- Frontend: storage overview strip, tables with total size, active
  queries DataTable
- Fix AgentHealth.tsx type: agentId → instanceId in inline type cast

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 20:18:06 +02:00
hsiegeln
188810e54b feat: remove TimescaleDB, dead PG stores, and storage feature flags
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 32s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Complete the ClickHouse migration by removing all PostgreSQL analytics
code. PostgreSQL now serves only RBAC, config, and audit — all
observability data is exclusively in ClickHouse.

- Delete 6 dead PostgreSQL store classes (executions, stats, diagrams,
  events, metrics, metrics-query) and 2 integration tests
- Delete RetentionScheduler (ClickHouse TTL handles retention)
- Remove all 7 cameleer.storage.* feature flags from application.yml
- Remove all @ConditionalOnProperty from ClickHouse beans in StorageBeanConfig
- Consolidate 14 Flyway migrations (V1-V14) into single clean V1 with
  only RBAC/config/audit tables (no TimescaleDB, no analytics tables)
- Switch from timescale/timescaledb-ha:pg16 to postgres:16 everywhere
  (docker-compose, deploy/postgres.yaml, test containers)
- Remove TimescaleDB check and /metrics-pipeline from DatabaseAdminController
- Set clickhouse.enabled default to true

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 20:10:58 +02:00
hsiegeln
283e38a20d feat: remove OpenSearch, add ClickHouse admin page
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 33s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Remove all OpenSearch code, dependencies, configuration, deployment
manifests, and CI/CD references. Replace the OpenSearch admin page
with a ClickHouse admin page showing cluster status, table sizes,
performance metrics, and indexer pipeline stats.

- Delete 11 OpenSearch Java files (config, search impl, admin controller, DTOs, tests)
- Delete 3 OpenSearch frontend files (admin page, CSS, query hooks)
- Delete deploy/opensearch.yaml K8s manifest
- Remove opensearch Maven dependencies from pom.xml
- Remove opensearch config from application.yml, Dockerfile, docker-compose
- Remove opensearch from CI workflow (secrets, deploy, cleanup steps)
- Simplify ThresholdConfig (remove OpenSearch thresholds, database-only)
- Change default search backend from opensearch to clickhouse
- Add ClickHouseAdminController with /status, /tables, /performance, /pipeline
- Add ClickHouseAdminPage with StatCards, pipeline ProgressBar, tables DataTable
- Update CLAUDE.md, HOWTO.md, and source comments

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 18:56:06 +02:00
hsiegeln
5ed7d38bf7 fix: sort sidebar entries alphanumerically
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 29s
CI / docker (push) Has been skipped
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Has been skipped
Applications, routes within each app, and agents within each app
are now sorted by name using localeCompare.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 18:24:39 +02:00
hsiegeln
4cdbcdaeea fix: update frontend field names for identity rename (applicationId, instanceId)
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 32s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
The backend identity rename (applicationName → applicationId,
agentId → instanceId) was not reflected in the frontend. This caused
drilldown to fail (detail.applicationName was undefined, disabling
the diagram fetch) and various display issues.

Updated schema.d.ts, ExchangeHeader, ExecutionDiagram, Dashboard,
AgentHealth, AgentInstance, LayoutShell, LogTab, InfoTab, DetailPanel,
ExchangesPage, and tracing-store.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 18:22:16 +02:00
hsiegeln
aa2d203f4e feat: add UI usage analytics tracking
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m9s
CI / docker (push) Successful in 1m14s
CI / deploy (push) Successful in 46s
CI / deploy-feature (push) Has been skipped
Tracks authenticated UI user requests to understand usage patterns:
- New ClickHouse usage_events table with 90-day TTL
- UsageTrackingInterceptor captures method, path, duration, user
- Path normalization groups dynamic segments ({id}, {hash})
- Buffered writes via WriteBuffer + periodic flush
- Admin endpoint GET /api/v1/admin/usage with groupBy=endpoint|user|hour
- Skips agent requests, health checks, and data ingestion

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 17:53:32 +02:00
hsiegeln
ce4abaf862 fix: infer compound node color from descendants when no own overlay state
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m17s
CI / docker (push) Successful in 1m9s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 49s
Path containers (EIP_WHEN, EIP_OTHERWISE, etc.) don't have their own
processor records, so they never get an overlay entry. Now inferred
from descendants: green if any descendant executed, red if any failed.
Gated (amber) only when no descendants executed at all.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 17:37:47 +02:00
hsiegeln
40ce4a57b4 fix: only show amber on containers where gate blocked all children
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m12s
CI / docker (push) Successful in 1m6s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 49s
A container is only gated (amber) when filterMatched=false or
duplicateMessage=true AND no descendants were executed. Containers
with executed children (split, choice, idempotent that passed) now
correctly show green/red based on their execution status.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 17:32:39 +02:00
hsiegeln
b44ffd08be fix: color compound nodes by execution status in overlay
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m12s
CI / docker (push) Successful in 1m5s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 51s
CompoundNode now uses execution overlay status to color its header:
failed (red) > completed (green) > default. Previously only used
static type-based color regardless of execution state.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 17:20:59 +02:00
hsiegeln
cf439248b5 feat: expose iteration/iterationSize fields for diagram overlay
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m12s
CI / docker (push) Successful in 1m5s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 52s
Replace synthetic wrapper node approach with direct iteration fields:
- ProcessorNode gains iteration (child's index) and iterationSize
  (container's total) fields, populated from ClickHouse flat records
- Frontend hooks detect iteration containers from iterationSize != null
  instead of scanning for wrapper processorTypes
- useExecutionOverlay filters children by iteration field instead of
  wrapper nodes, eliminating ITERATION_WRAPPER_TYPES entirely
- Cleaner data contract: API returns exactly what the DB stores

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 17:14:36 +02:00
hsiegeln
e8f9ada1d1 fix: inject ClickHouse JdbcTemplate into stats-querying controllers
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m9s
CI / docker (push) Successful in 49s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 44s
RouteCatalogController, RouteMetricsController, and AgentRegistrationController
had unqualified JdbcTemplate injection, receiving the PostgreSQL template
instead of ClickHouse. The stats queries silently failed (caught exception)
returning 0 counts. Added @Qualifier("clickHouseJdbcTemplate") to all three.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 16:34:56 +02:00
hsiegeln
bc70797e31 fix: force UTC timezone in Docker runtime
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m8s
CI / docker (push) Successful in 47s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 42s
Sets TZ=UTC and -Duser.timezone=UTC to guarantee all JVM time operations
use UTC regardless of the container's base image or host configuration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 16:24:23 +02:00
hsiegeln
f6123b8a7c fix: use explicit UTC formatting in ClickHouse DateTime literals
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m9s
CI / docker (push) Successful in 50s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 57s
Timestamp.toString() uses JVM local timezone which can mismatch with
ClickHouse's UTC timezone, causing time-filtered queries to return empty
results. Replaced with DateTimeFormatter.withZone(UTC) in all lit() methods.

Also added warn logging to RouteCatalogController catch blocks to surface
query errors instead of silently swallowing them.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 16:13:52 +02:00
hsiegeln
d739094a56 fix: update ClickHouse DDL files with new column names instead of ALTER RENAME
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m9s
CI / docker (push) Successful in 45s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 43s
ClickHouse can't rename columns that are part of ORDER BY keys.
Updated V1-V8 DDL files directly with new column names (instance_id,
application_id) and removed V9 migration. Wipe ClickHouse and restart.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 12:40:54 +02:00
hsiegeln
91400defe9 fix: add missing V9 (ClickHouse) and V14 (PostgreSQL) identity column rename migrations
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m7s
CI / docker (push) Successful in 45s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 39s
Migration files were lost during worktree merge — recreated.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 12:33:02 +02:00
hsiegeln
909d713837 feat: rename agent identity fields for protocol v2 + add SHUTDOWN lifecycle state
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m7s
CI / docker (push) Successful in 45s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 22s
Align all internal naming with the agent team's protocol v2 identity rename:
- agentId → instanceId (unique per-JVM identifier)
- applicationName → applicationId (shared app identifier)
- AgentInfo: id → instanceId, name → displayName, application → applicationId

Add SHUTDOWN lifecycle state for graceful agent shutdowns:
- New POST /data/events endpoint receives agent lifecycle events
- AGENT_STOPPED event transitions agent to SHUTDOWN (skips STALE/DEAD)
- New POST /{id}/deregister endpoint removes agent from registry
- Server now distinguishes graceful shutdown from crash (heartbeat timeout)

Includes ClickHouse V9 and PostgreSQL V14 migrations for column renames.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 12:22:42 +02:00
hsiegeln
ad8dd73596 fix: update ChunkAccumulator tests for DiagramStore constructor param
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m5s
CI / docker (push) Successful in 1m6s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 52s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 10:58:27 +02:00
hsiegeln
e50c9fa60d fix: address SonarQube reliability issues
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 39s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
- ElkDiagramRenderer.getElkRoot(): add null guard to prevent NPE
  when node is null (SQ java:S2259)
- WriteBuffer: add offerOrWarn() that logs when buffer is full instead
  of silently dropping data. ChunkAccumulator now uses this method
  so ingestion backpressure is visible in logs (SQ java:S899)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 10:55:31 +02:00
hsiegeln
d4dbfa7ae6 fix: populate diagramContentHash in chunked ingestion pipeline
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 43s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
ChunkAccumulator now injects DiagramStore and looks up the content hash
when converting to MergedExecution. Without this, the detail page had
no diagram hash, so the overlay couldn't find the route diagram.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 10:50:34 +02:00
hsiegeln
59374482bc fix: replace PostgreSQL aggregate functions with ClickHouse -Merge combinators
RouteCatalogController, RouteMetricsController, AgentRegistrationController
all had inline SQL using SUM() on AggregateFunction columns from stats_1m_*
AggregatingMergeTree tables. Replace with countMerge/countIfMerge/sumMerge.
Also fix time_bucket() → toStartOfInterval() and ::double → toFloat64().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 10:49:06 +02:00
hsiegeln
43e187a023 fix: ChunkIngestionController ObjectMapper missing FAIL_ON_UNKNOWN_PROPERTIES
Adds DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES=false (required
by PROTOCOL.md) and explicit TypeReference<List<ExecutionChunk>> for
array parsing. Without this, batched chunks from ChunkedExporter
(2+ chunks in a JSON array) were silently rejected, causing final:true
chunks to be lost and all exchanges to go stale.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 10:45:12 +02:00
hsiegeln
bc1c71277c fix: resolve duplicate ExecutionStore bean conflict
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m5s
CI / docker (push) Successful in 49s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 55s
ClickHouseExecutionStore implements ExecutionStore, so the concrete bean
already satisfies the interface — remove redundant wrapper bean. Align
ChunkAccumulator and ExecutionFlushScheduler conditions to
cameleer.storage.executions flag.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 09:44:02 +02:00
hsiegeln
520181d241 test(clickhouse): add integration tests for execution read path and tree reconstruction
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m7s
CI / docker (push) Successful in 46s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Failing after 2m16s
SonarQube / sonarqube (push) Failing after 2m21s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 00:11:44 +02:00
hsiegeln
95b9dea5c4 feat(clickhouse): wire ClickHouseExecutionStore as active ExecutionStore
Add cameleer.storage.executions feature flag (default: clickhouse).
PostgresExecutionStore activates only when explicitly set to postgres.
Add by-seq snapshot endpoint for iteration-aware processor lookup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 00:09:14 +02:00
hsiegeln
151b96a680 feat: seq-based tree reconstruction for ClickHouse flat processor model
Dual-mode buildTree: detects seq presence and uses seq/parentSeq linkage
instead of processorId map. Handles duplicate processorIds across
iterations correctly. Old processorId-based mode kept for PG compat.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 00:07:20 +02:00
hsiegeln
0661fd995f feat(clickhouse): add read methods to ClickHouseExecutionStore
Implements ExecutionStore interface with findById (FINAL for
ReplacingMergeTree), findProcessors (ORDER BY seq), findProcessorById,
and findProcessorBySeq. Write methods unchanged.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 00:04:03 +02:00
hsiegeln
190ae2797d refactor: extend ProcessorRecord with seq/iteration fields for ClickHouse model
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 00:02:03 +02:00
hsiegeln
968117c41a feat(clickhouse): wire Phase 4 stores with feature flags
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m7s
CI / docker (push) Successful in 43s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 44s
Add conditional beans for ClickHouseDiagramStore, ClickHouseAgentEventRepository,
and ClickHouseLogStore. All default to ClickHouse (matchIfMissing=true).
PG/OS stores activate only when explicitly configured.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 23:44:10 +02:00
hsiegeln
7d7eb52afb feat(clickhouse): add ClickHouseLogStore with LogIndex interface
Extract LogIndex interface from OpenSearchLogIndex. Both ClickHouseLogStore
and OpenSearchLogIndex implement it. Controllers now inject LogIndex.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 23:42:07 +02:00
hsiegeln
c73e4abf68 feat(clickhouse): add ClickHouseAgentEventRepository with integration tests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 23:37:51 +02:00
hsiegeln
cd63d300b3 feat(clickhouse): add ClickHouseDiagramStore with integration tests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 23:35:32 +02:00
hsiegeln
f7daadaaa9 feat(clickhouse): add DDL for route_diagrams, agent_events, and logs tables
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 23:30:38 +02:00
hsiegeln
af080337f5 feat: comprehensive ClickHouse low-memory tuning and switch all storage to ClickHouse
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m9s
CI / docker (push) Successful in 42s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 58s
Replace partial memory config with full Altinity low-memory guide
settings. Revert container limit from 6Gi back to 4Gi — proper
tuning (mlock=false, reduced caches/pools/threads, disk spill for
aggregations) makes the original budget sufficient.

Switch all storage feature flags to ClickHouse:
- CAMELEER_STORAGE_SEARCH: opensearch → clickhouse
- CAMELEER_STORAGE_METRICS: postgres → clickhouse
- CAMELEER_STORAGE_STATS: already clickhouse

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 23:27:10 +02:00
hsiegeln
606f81a970 fix: align server with protocol v2 chunked transport spec
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m45s
CI / docker (push) Successful in 59s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 46s
- ChunkIngestionController: /data/chunks → /data/executions (matches
  PROTOCOL.md endpoint the agent actually posts to)
- ExecutionController: conditional on ClickHouse being disabled to
  avoid mapping conflict
- Persist originalExchangeId and replayExchangeId from ExecutionChunk
  envelope through to ClickHouse (was silently dropped)
- V5 migration adds the two new columns to executions table

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 23:18:35 +02:00
hsiegeln
154bce366a fix: remove references to deleted ProcessorExecution tree fields
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m15s
CI / docker (push) Successful in 44s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 1m0s
cameleer3-common removed children, loopIndex, splitIndex,
multicastIndex from ProcessorExecution (flat model only now).
Iteration context lives on synthetic wrapper nodes via processorType.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 23:00:11 +02:00
hsiegeln
a669df08bd fix(clickhouse): tune memory settings to prevent OOM on insert
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 40s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
ClickHouse 24.12 auto-sizes caches from the cgroup limit, leaving
insufficient headroom for MV processing and background merges.
Adds a custom config that shrinks mark/index/expression caches and
caps per-query memory at 2 GiB. Bumps container limit 4Gi → 6Gi.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 22:54:43 +02:00
hsiegeln
af18fc4142 Merge branch 'worktree-clickhouse-phase2'
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m8s
CI / docker (push) Successful in 45s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 42s
2026-03-31 22:06:35 +02:00
hsiegeln
1a00eed389 fix: schema initializer skips comment-only SQL segments
The V4 DDL had a semicolon inside a comment which caused the
split-on-semicolon logic to produce a comment-only segment that
ClickHouse rejected as empty query. Fixed the comment and made
the initializer strip comment-only segments before execution.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 22:06:31 +02:00
hsiegeln
0423518f72 feat: ClickHouse Phase 3 — Stats & Analytics (materialized views)
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m8s
CI / docker (push) Successful in 43s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 44s
- DDL for 5 AggregatingMergeTree tables + 5 materialized views
- ClickHouseStatsStore: all 15 StatsStore methods using -Merge combinators
- Stats/timeseries read from pre-aggregated MVs (countMerge, sumMerge, quantileMerge)
- SLA/topErrors/punchcard query raw executions FINAL table
- Feature flag: cameleer.storage.stats (default: clickhouse)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 21:52:13 +02:00
hsiegeln
9df00fdde0 feat(clickhouse): wire ClickHouseStatsStore with cameleer.storage.stats feature flag (default: clickhouse)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 21:51:45 +02:00
hsiegeln
052990bb59 feat(clickhouse): add ClickHouseStatsStore with -Merge aggregate queries
Implements StatsStore interface for ClickHouse using AggregatingMergeTree
tables with -Merge combinators (countMerge, countIfMerge, sumMerge,
quantileMerge). Uses literal SQL for aggregate table queries to avoid
ClickHouse JDBC driver PreparedStatement issues with AggregateFunction
columns. Raw table queries (SLA, topErrors, activeErrorTypes) use normal
prepared statements.

Includes 13 integration tests covering stats, timeseries, grouped
timeseries, SLA compliance, SLA counts by app/route, top errors, active
error types, punchcard, and processor stats. Also fixes AggregateFunction
type signatures in V4 DDL (count() takes no args, countIf takes UInt8).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 21:49:22 +02:00
hsiegeln
eb0d26814f feat(clickhouse): add stats materialized views DDL (5 tables + 5 MVs)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 20:11:38 +02:00
hsiegeln
c8e6bbe059 Merge branch 'worktree-clickhouse-phase2'
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m2s
CI / docker (push) Successful in 43s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 44s
2026-03-31 20:02:49 +02:00
hsiegeln
a9eabe97f7 fix: wire @Primary JdbcTemplate to the @Primary DataSource bean
The jdbcTemplate() method was calling dataSource(properties) directly,
creating a new DataSource instance instead of using the Spring-managed
@Primary bean. This caused some repositories to receive the ClickHouse
connection instead of PostgreSQL.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 20:02:44 +02:00
hsiegeln
e724607a66 feat: ClickHouse Phase 2 — Executions + Search (chunked transport)
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m36s
CI / docker (push) Successful in 3m21s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 41s
- DDL for executions (ReplacingMergeTree) and processor_executions (MergeTree with seq/parentSeq/iteration)
- ClickHouseExecutionStore with batch INSERT for both tables
- ChunkAccumulator: buffers exchange envelope across chunks, inserts processors immediately, writes execution on final chunk
- ExecutionFlushScheduler drains WriteBuffers to ClickHouse
- ChunkIngestionController: POST /api/v1/data/chunks endpoint
- ClickHouseSearchIndex: ngram-accelerated SQL search implementing SearchIndex interface
- Feature flags: cameleer.storage.search=opensearch|clickhouse
- Uses cameleer3-common ExecutionChunk and FlatProcessorRecord models

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 19:37:21 +02:00
hsiegeln
07f215b0fd refactor: replace server-side DTOs with cameleer3-common ExecutionChunk and FlatProcessorRecord
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 19:33:49 +02:00
hsiegeln
38551eac9d test(clickhouse): add end-to-end chunk pipeline integration test
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 19:24:55 +02:00
hsiegeln
31f7113b3f feat(clickhouse): wire ChunkAccumulator, flush scheduler, and search feature flag
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 19:21:19 +02:00
hsiegeln
6052407c82 feat(clickhouse): add ClickHouseSearchIndex with ngram-accelerated SQL search
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 19:18:01 +02:00
hsiegeln
776f2ce90d feat(clickhouse): add ExecutionFlushScheduler and ChunkIngestionController
ExecutionFlushScheduler drains MergedExecution and ProcessorBatch write
buffers on a fixed interval and delegates batch inserts to
ClickHouseExecutionStore. Also sweeps stale exchanges every 60s.

ChunkIngestionController exposes POST /api/v1/data/chunks, accepts
single or array ExecutionChunk payloads, and feeds them into the
ChunkAccumulator. Conditional on ChunkAccumulator bean (clickhouse.enabled).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 19:12:38 +02:00
hsiegeln
62420cf0c2 feat(clickhouse): add ChunkAccumulator for chunked execution ingestion
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 19:10:21 +02:00
hsiegeln
81f7f8afe1 feat(clickhouse): add ClickHouseExecutionStore with batch insert for chunked format
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 19:07:33 +02:00
hsiegeln
b30dfa39f4 feat(clickhouse): add executions and processor_executions DDL for chunked transport
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 19:04:19 +02:00
hsiegeln
20c8e17843 feat: add server-side ExecutionChunk and FlatProcessorRecord DTOs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 19:02:47 +02:00
a96fe59840 Merge pull request 'fix: add @Primary PG DataSource/JdbcTemplate to prevent CH bean conflict' (#99) from feature/clickhouse-phase1 into main
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m46s
CI / docker (push) Successful in 11s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 42s
Reviewed-on: cameleer/cameleer3-server#99
2026-03-31 18:21:00 +02:00
hsiegeln
7cf849269f fix: add @Primary PG DataSource/JdbcTemplate to prevent CH bean conflict
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m6s
CI / docker (push) Successful in 41s
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Successful in 38s
CI / cleanup-branch (pull_request) Has been skipped
CI / build (pull_request) Successful in 1m51s
CI / docker (pull_request) Has been skipped
CI / deploy (pull_request) Has been skipped
CI / deploy-feature (pull_request) Has been skipped
When clickhouse.enabled=true, the ClickHouse JdbcTemplate bean prevents
Spring Boot auto-config from creating the default PG JdbcTemplate.
All PG repositories then get the CH JdbcTemplate and fail with
"Table cameleer.audit_log does not exist".

Fix: explicitly create @Primary DataSource and JdbcTemplate from
DataSourceProperties so PG remains the default for unqualified injections.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 18:18:09 +02:00
76afcaa637 Merge pull request 'fix: cast DateTime64 to DateTime in ClickHouse TTL expression' (#98) from feature/clickhouse-phase1 into main
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m55s
CI / docker (push) Successful in 14s
CI / deploy (push) Successful in 30s
CI / deploy-feature (push) Has been skipped
Reviewed-on: cameleer/cameleer3-server#98
2026-03-31 18:10:58 +02:00
hsiegeln
b1c5cc0616 fix: cast DateTime64 to DateTime in ClickHouse TTL expression
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m23s
CI / cleanup-branch (pull_request) Has been skipped
CI / build (pull_request) Successful in 1m46s
CI / docker (pull_request) Has been skipped
CI / deploy (pull_request) Has been skipped
CI / deploy-feature (pull_request) Has been skipped
CI / docker (push) Successful in 1m8s
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Failing after 2m19s
2026-03-31 18:10:20 +02:00
8838077eff Merge pull request 'fix: remove unsupported async_insert params from ClickHouse JDBC URL' (#97) from feature/clickhouse-phase1 into main
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m39s
CI / docker (push) Successful in 10s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 34s
Reviewed-on: cameleer/cameleer3-server#97
2026-03-31 18:04:22 +02:00
hsiegeln
8eeaecf6f3 fix: remove unsupported async_insert params from ClickHouse JDBC URL
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m6s
CI / docker (push) Successful in 55s
CI / cleanup-branch (pull_request) Has been skipped
CI / build (pull_request) Successful in 1m39s
CI / deploy (push) Has been skipped
CI / docker (pull_request) Has been skipped
CI / deploy (pull_request) Has been skipped
CI / deploy-feature (push) Successful in 51s
CI / deploy-feature (pull_request) Has been skipped
clickhouse-jdbc 0.9.7 rejects async_insert and wait_for_async_insert as
unknown URL parameters. These are server-side settings, not driver config.
Can be set per-query later if needed via custom_settings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 18:02:53 +02:00
b54bef302d Merge pull request 'fix: ClickHouse auth credentials and non-fatal schema init' (#96) from feature/clickhouse-phase1 into main
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m48s
CI / docker (push) Successful in 9s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Failing after 2m17s
Reviewed-on: cameleer/cameleer3-server#96
2026-03-31 17:57:27 +02:00
hsiegeln
f8505401d7 fix: ClickHouse auth credentials and non-fatal schema init
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m5s
CI / docker (push) Successful in 43s
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Failing after 13s
CI / cleanup-branch (pull_request) Has been skipped
CI / build (pull_request) Successful in 1m47s
CI / docker (pull_request) Has been skipped
CI / deploy (pull_request) Has been skipped
CI / deploy-feature (pull_request) Has been skipped
- Set CLICKHOUSE_USER/PASSWORD via k8s secret (fixes "disabling network
  access for user 'default'" when no password is set)
- Add clickhouse-credentials secret to CI deploy + feature branch copy
- Pass CLICKHOUSE_USERNAME/PASSWORD env vars to server pod
- Make schema initializer non-fatal so server starts even if CH is
  temporarily unavailable

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:54:44 +02:00
a0f1a4aba4 Merge pull request 'feature/clickhouse-phase1' (#95) from feature/clickhouse-phase1 into main
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m5s
CI / docker (push) Successful in 9s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Failing after 2m41s
Reviewed-on: cameleer/cameleer3-server#95
2026-03-31 17:48:41 +02:00
hsiegeln
aa5fc1b830 ci: retrigger after transient GitHub actions/cache 500 error
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m44s
CI / cleanup-branch (pull_request) Has been skipped
CI / build (pull_request) Successful in 1m44s
CI / docker (pull_request) Has been skipped
CI / deploy (pull_request) Has been skipped
CI / deploy-feature (pull_request) Has been skipped
CI / docker (push) Successful in 11s
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Failing after 2m15s
2026-03-31 17:43:40 +02:00
hsiegeln
c42e13932b ci: deploy ClickHouse StatefulSet in main deploy job
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (pull_request) Failing after 45s
CI / cleanup-branch (pull_request) Has been skipped
CI / docker (pull_request) Has been skipped
CI / deploy (pull_request) Has been skipped
CI / deploy-feature (pull_request) Has been skipped
CI / build (push) Failing after 1m6s
CI / docker (push) Has been skipped
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Has been skipped
The deploy/clickhouse.yaml manifest was created but not referenced
in the CI workflow. Add kubectl apply between OpenSearch and Authentik.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:41:15 +02:00
hsiegeln
59dd629b0e fix: create cameleer database on ClickHouse startup
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (pull_request) Successful in 1m49s
CI / cleanup-branch (pull_request) Has been skipped
CI / docker (pull_request) Has been skipped
CI / deploy (pull_request) Has been skipped
CI / deploy-feature (pull_request) Has been skipped
CI / build (push) Successful in 1m7s
CI / docker (push) Successful in 10s
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been cancelled
ClickHouse only has the 'default' database out of the box. The JDBC URL
connects to 'cameleer', so the database must exist before the server starts.
Uses /docker-entrypoint-initdb.d/ init script via ConfigMap.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:31:17 +02:00
hsiegeln
697c689192 fix: rename ClickHouse tests to *IT pattern for CI compatibility
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 2m28s
CI / cleanup-branch (pull_request) Has been skipped
CI / build (pull_request) Successful in 2m27s
CI / docker (pull_request) Has been skipped
CI / deploy (pull_request) Has been skipped
CI / deploy-feature (pull_request) Has been skipped
CI / docker (push) Successful in 3m32s
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Failing after 2m17s
Testcontainers tests need Docker which isn't available in CI.
Rename to *IT so Surefire skips them (Failsafe runs them with -DskipITs=false).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:19:33 +02:00
hsiegeln
7a2a0ee649 test: add ClickHouse testcontainer to integration test base
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 2m29s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
CI / cleanup-branch (pull_request) Has been skipped
CI / build (pull_request) Failing after 2m28s
CI / docker (pull_request) Has been skipped
CI / deploy (pull_request) Has been skipped
CI / deploy-feature (pull_request) Has been skipped
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 17:09:09 +02:00
hsiegeln
1b991f99a3 deploy: add ClickHouse StatefulSet and server env vars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 17:08:42 +02:00
hsiegeln
21991b6cf8 feat: wire MetricsStore and MetricsQueryStore with feature flag
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 17:07:35 +02:00
hsiegeln
53766aeb56 feat: add ClickHouseMetricsQueryStore with time-bucketed queries
Implements MetricsQueryStore using ClickHouse toStartOfInterval() for
time-bucketed aggregation queries; verified with 4 Testcontainers tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 17:05:45 +02:00
hsiegeln
bf0e9ea418 refactor: extract MetricsQueryStore interface from AgentMetricsController
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 17:00:57 +02:00
hsiegeln
6e30b7ec65 feat: add ClickHouseMetricsStore with batch insert
TDD implementation of MetricsStore backed by ClickHouse. Uses native
Map(String,String) column type (no JSON cast), relies on ClickHouse
DEFAULT for server_received_at, and handles null tags by substituting
an empty HashMap. All 4 Testcontainers tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 16:58:20 +02:00
hsiegeln
08934376df feat: add ClickHouse schema initializer with agent_metrics DDL
Adds ClickHouseSchemaInitializer that runs on ApplicationReadyEvent,
scanning classpath:clickhouse/*.sql in filename order and executing each
statement. Adds V1__agent_metrics.sql with MergeTree table, tenant/agent
partitioning, and 365-day TTL.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 16:51:21 +02:00
hsiegeln
23f901279a feat: add ClickHouse DataSource and JdbcTemplate configuration
Adds ClickHouseProperties (bound to clickhouse.*), ClickHouseConfig
(conditional HikariDataSource + JdbcTemplate beans), and extends
application.yml with clickhouse.enabled/url/username/password and
cameleer.storage.metrics properties.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 16:51:14 +02:00
hsiegeln
6171827243 build: add clickhouse-jdbc and testcontainers-clickhouse dependencies
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 16:49:04 +02:00
hsiegeln
c77d8a7af0 docs: add Phase 1 implementation plan for ClickHouse migration
10-task TDD plan covering: CH dependency, config, schema init,
ClickHouseMetricsStore, MetricsQueryStore interface extraction,
ClickHouseMetricsQueryStore, feature flag wiring, k8s deployment,
integration tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 16:43:14 +02:00
hsiegeln
e7eda7a7b3 docs: add ClickHouse migration design and append-only protocol spec
Design for replacing PostgreSQL/TimescaleDB + OpenSearch with ClickHouse
OSS. Covers table schemas, ingestion pipeline (ExecutionAccumulator),
ngram search indexes, materialized views, multitenancy, and retention.

Companion doc proposes append-only execution protocol for the agent repo.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 16:36:22 +02:00
hsiegeln
ebe768711b fix: Cmd-K exchange selection reads exchangeId from URL params
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m1s
CI / docker (push) Successful in 57s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
ExchangesPage ignored the exchangeId URL parameter, so selecting an
exchange from the command palette navigated to the right URL but never
displayed the execution overlay. Now derives selection from URL params
as fallback, and LayoutShell passes selectedExchange in state for
exchange/attribute results.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 15:26:36 +02:00
hsiegeln
af45f93854 fix: add missing isReplay parameter to test constructors
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m1s
CI / docker (push) Successful in 57s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 41s
The ExecutionDocument and ExecutionRecord records gained an isReplay
field but the integration tests were not updated, breaking CI.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 15:08:12 +02:00
hsiegeln
da1d74309e fix: detect replay via replayExchangeId field, not just header
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 1m4s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
The X-Cameleer-Replay header is only available when inputSnapshot is
captured (DETAILED/DEEP engine level). The agent always sets
replayExchangeId on RouteExecution, so check that first.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 14:57:59 +02:00
hsiegeln
7a4d7b6915 fix: resolve 8 SonarQube reliability bugs
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 1m2s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
- ElkDiagramRenderer: guard against null containingNode before getElkRoot()
- OpenSearchAdminController: return 503/502 instead of 200 on errors
- DatabaseAdminController: return 503 instead of 200 on connection failure
- SpaForwardController: replace unbound {path} variables with /** wildcards
- WriteBuffer: check offer() return value and log on unexpected rejection
- ApiExceptionHandler: extract getReason() to local var for null safety
- Admin UI pages: handle isError state for disconnected service display

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 14:39:54 +02:00
hsiegeln
ab7031e6ed feat: add is_replay flag to execution pipeline and UI
Detect replayed exchanges via X-Cameleer-Replay header during ingestion,
persist the flag through PostgreSQL and OpenSearch, and surface it in
the dashboard (amber replay icon) and exchange detail chain view.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 14:39:40 +02:00
hsiegeln
cf3cec0164 feat: show replay marker on correlated chain entries
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m46s
CI / docker (push) Successful in 1m52s
CI / deploy (push) Successful in 51s
CI / deploy-feature (push) Has been skipped
SonarQube / sonarqube (push) Failing after 1m16s
Exchanges with a _replay attribute now display a small amber
RotateCcw icon between the status dot and route name in the
correlation chain. Tooltip also indicates (replay).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 23:26:55 +02:00
hsiegeln
79762c3f0d fix: audit replay with actual outcome, not premature SUCCESS
All checks were successful
CI / build (push) Successful in 2m8s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 1m7s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 42s
Replay audit log now records the agent's reply status (SUCCESS/FAILURE),
message, and error details. Timeout and internal errors are also logged
as FAILURE with the cause.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 23:14:36 +02:00
hsiegeln
715cbc1894 feat: synchronous replay endpoint with agent response status
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m8s
CI / docker (push) Successful in 56s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s
Add dedicated POST /agents/{id}/replay endpoint that uses
addCommandWithReply to wait for the agent ACK (30s timeout).
Returns the actual replay result (status, message, data) instead
of just a delivery confirmation.

Frontend toast now reflects the agent's response: "Replay completed"
on success, agent error message on failure, timeout message if the
agent doesn't respond.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 22:48:02 +02:00
hsiegeln
dd398178f0 docs: add route-control command to HOWTO and CLAUDE.md
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m35s
CI / docker (push) Successful in 13s
CI / deploy (push) Successful in 49s
CI / deploy-feature (push) Has been skipped
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 21:44:12 +02:00
hsiegeln
8b0d473fcd feat: add route control bar and fix replay protocol compliance
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m4s
CI / docker (push) Successful in 1m0s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Has been cancelled
Add ROUTE_CONTROL command type and route-control mapping in
AgentCommandController. New RouteControlBar component in the exchange
header shows Start/Stop/Suspend/Resume actions (grouped pill bar) and
a Replay button, gated by agent capabilities and OPERATOR/ADMIN role.

Fix useReplayExchange hook to match protocol section 16: payload now
uses { routeId, exchange: { body, headers }, originalExchangeId, nonce }
instead of the flat { headers, body } format.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 21:42:06 +02:00
hsiegeln
30e9b55379 fix: detail panel respects iteration filtering
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m35s
CI / docker (push) Successful in 1m12s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 48s
- findProcessorInTree now skips non-selected iteration wrappers so
  the returned ProcessorNode has data from the correct iteration
- Gate selectedProcessor on overlay presence so processors not
  executed in the current iteration don't show in the detail panel
- Header shows "Exchange Details" or "Processor Details" contextually

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 20:25:28 +02:00
hsiegeln
3091754b0f fix: dim compound containers when no descendants executed in overlay
CompoundNode (circuit breaker, choice, etc.) now renders at 0.35
opacity when the overlay is active but neither the compound itself
nor any of its diagram descendants appear in the execution overlay.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 20:13:40 +02:00
hsiegeln
26de222884 refactor: move config badges inline, fix trace config from server
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m1s
CI / docker (push) Successful in 56s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 40s
- Render hasTrace/hasTap/status badges inside the node card in both
  raw diagram and overlay modes (consistent positioning)
- Pulse only on trace badge in overlay mode when hasTraceData is true
- Fix nodeConfigs to read tracedProcessors from appConfig instead of
  never-synced tracing store

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 18:08:40 +02:00
hsiegeln
2f2f93f37e fix: move useCallback before early returns to fix hooks order
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 17:47:17 +02:00
hsiegeln
1b9a3b84a0 feat: add JSON download button to execution diagram
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 17:43:02 +02:00
hsiegeln
c77de4a232 fix: simplify detail panel header to just "Details"
Remove redundant processor name, status, ID, and duration from the
header bar — all visible in the Info tab and diagram overlay already.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 16:40:18 +02:00
hsiegeln
15b8c09e17 fix: position resolved URI directly below text lines in diagram overlay
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 16:35:28 +02:00
hsiegeln
77e87504d6 feat: agent row click navigates to detail page instead of slide-in
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m2s
CI / docker (push) Successful in 55s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 40s
Replace DetailPanel overlay with direct navigation to
/runtime/:appId/:instanceId on row click. Removes the slide-in panel,
AgentOverviewContent, and AgentPerformanceContent helper components.
The full AgentInstance page already provides all the same data plus
more (charts, routes, logs).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 16:28:12 +02:00
hsiegeln
d8a21f0724 feat: GitHub-style contribution grid for punchcard heatmap
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m3s
CI / docker (push) Successful in 54s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
Replace Recharts ScatterChart with compact SVG grid of small rounded
squares (11x11px, 2px gap). 7 rows (Mon-Sun) x 24 columns (hours).
Color intensity = value relative to max. Transactions = blue scale,
Errors = red scale. Toggle switches between modes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 15:49:45 +02:00
hsiegeln
4a91ca0774 feat: consolidate punchcard heatmaps into single toggle component
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m1s
CI / docker (push) Successful in 54s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Replace two separate Transaction/Error punchcard cards with a single
card containing a Transactions/Errors toggle. Uses internal state to
switch between modes without remounting the chart.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 15:45:22 +02:00
hsiegeln
52c22f1eb9 fix: dashboard flickering on poll, animation replay, and scroll
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m3s
CI / docker (push) Successful in 54s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
- Add placeholderData to useRouteMetrics and usePunchcard hooks so data
  stays stable between refetches instead of going undefined → flicker
- Disable Recharts animation on Treemap (isAnimationActive=false)
- Make .content scrollable (overflow-y: auto, flex: 1, min-height: 0)
  so charts below the fold are accessible

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 15:42:02 +02:00
hsiegeln
a517785050 chore: regenerate OpenAPI types and remove type assertion hacks
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m2s
CI / docker (push) Successful in 56s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 39s
Regenerated schema.d.ts from live backend — now includes slaCompliance
on ExecutionStats/RouteMetrics, filterMatched/duplicateMessage on
ProcessorNode, and all new dashboard endpoints (timeseries/by-app,
timeseries/by-route, punchcard, errors/top, app-settings).

Removed Record<string, unknown> casts that were working around the
stale schema.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 15:36:44 +02:00
hsiegeln
474738a894 fix: resolve TypeScript strict mode errors failing CI
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m4s
CI / docker (push) Successful in 1m25s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 41s
- StatusDot: status → variant (correct prop name)
- Badge: color="muted" → color="auto" (valid BadgeColor)
- AreaChart: remove stacked prop (not in AreaChartProps)
- DataTable: remove defaultSort prop (not in DataTableProps)
- TopError → ErrorRow with id field (DataTable requires T extends {id})
- slaCompliance: type assertion for runtime field not in TS schema
- PunchcardHeatmap Scatter shape: proper typing for custom renderer

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 15:26:26 +02:00
hsiegeln
41397ae067 feat: migrate Treemap and PunchcardHeatmap to Recharts
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 31s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Replace custom SVG chart implementations with Recharts components:
- Treemap: uses Recharts Treemap with custom content renderer for
  SLA-colored cells, labels, and click navigation
- PunchcardHeatmap: uses Recharts ScatterChart with custom Rectangle
  shape for weekday x hour heatmap grid cells

Both use ResponsiveContainer (no more explicit width/height props) and
rechartsTheme from the design system for consistent tooltip styling.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 15:20:29 +02:00
hsiegeln
dd91a4989b chore: update @cameleer/design-system to v0.1.21
Some checks failed
CI / build (push) Failing after 43s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
New exports: rechartsTheme (pre-configured Recharts prop objects matching
design system styling), CHART_COLORS (series color palette), and properly
exported ChartSeries/DataPoint interfaces. No breaking changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 15:03:27 +02:00
hsiegeln
f06f5f2bb1 docs: add CSS variable rule to CLAUDE.md
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 26s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Always use design system CSS variables for colors, never hardcode hex.
Applies to CSS modules, inline styles, and SVG fill/stroke attributes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 11:00:53 +02:00
hsiegeln
c8caf3dc44 fix: use CSS variables directly for gate state colors
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 25s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Use var(--amber) and var(--amber-bg) in SVG fill/stroke attributes
instead of hardcoded hex values. SVG presentation attributes resolve
CSS variables correctly, and this respects dark mode theme switching.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 10:59:07 +02:00
hsiegeln
2de10f6eb0 fix: use theme amber colors for gate state instead of arbitrary hex
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 26s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Use --amber (#C6820E) and --amber-bg (#FDF6E9) from the design system
theme instead of hardcoded #D97706/#FFFBEB.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 10:55:59 +02:00
hsiegeln
e2c0f203f9 feat: amber container for filter/idempotent gate state + red pulse on failed badge
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 29s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
When a filter processor rejects a message (filterMatched=false) or an
idempotent consumer detects a duplicate (duplicateMessage=true), the
compound container turns amber (header, border, body tint).

Also adds red pulsing rings on the failed processor badge (same SMIL
pattern as the teal hasTraceData pulse).

Backend: ProcessorNode gains filterMatched/duplicateMessage fields,
threaded from ProcessorExecution JSON path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 10:53:57 +02:00
hsiegeln
a383b9bcf4 feat: add red pulse effect to failed processor badges in diagram overlay
Failed processor nodes now show expanding/fading red rings around the
error badge (same SMIL animation pattern as the teal hasTraceData pulse).
Two staggered circles expand from r=6 to r=14 over 1.5s, making failures
immediately visible in complex route diagrams.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 10:42:35 +02:00
hsiegeln
6aeba1fe83 fix: side-by-side layout for treemap and punchcard heatmaps
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 29s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Treemap on left (3fr), two punchcards stacked on right (2fr) using
new .vizRow grid layout. Replaces full-width stacked arrangement.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 10:32:23 +02:00
hsiegeln
7a1625c297 fix: make treemap and punchcard responsive with viewBox scaling
Replaced hardcoded width/height on SVG elements with viewBox + width:100%
so both components fill their parent container instead of using fixed pixels.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 10:28:29 +02:00
hsiegeln
9d2d87e7e1 feat: add treemap and punchcard heatmap to dashboard L1/L2 (#94)
Treemap: rectangle area = transaction volume, color = SLA compliance
(green→red). Shows apps at L1, routes at L2. Click navigates deeper.

Punchcard heatmap: 7-day rolling weekday x 24-hour grid showing
transaction volume and error patterns. Two side-by-side views
(transactions + errors) reveal temporal clustering.

Backend: new GET /search/stats/punchcard endpoint aggregating
stats_1m_all/app by DOW x hour over rolling 7 days.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 10:26:26 +02:00
hsiegeln
b5c19b6774 feat: latency heatmap overlay on process diagram (#94)
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 29s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
SonarQube / sonarqube (push) Failing after 1m10s
Add latencyHeatmap prop to ProcessDiagram that colors nodes green→yellow→red
based on their relative contribution to route latency (pctOfRoute). Shows avg
duration label on each node. Threaded through CompoundNode for nested EIP
patterns. Heatmap is active only when no execution overlay is present.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 23:32:42 +02:00
hsiegeln
213aa86c47 feat: progressive drill-down dashboard with RED metrics and SLA compliance (#94)
Three-level dashboard driven by sidebar selection:
- L1 (no selection): all-apps overview with health table, per-app charts
- L2 (app selected): route performance table, error velocity, top errors
- L3 (route selected): processor table, latency heatmap data, bottleneck KPI

Backend: 3 new endpoints (timeseries/by-app, timeseries/by-route, errors/top),
per-app SLA settings (app_settings table, V12 migration), exact SLA compliance
from executions hypertable, error velocity with acceleration detection.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 23:29:20 +02:00
hsiegeln
b2ae37637d fix: update diagram tests for new cameleer3-common without flat nodes list
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m3s
CI / docker (push) Successful in 1m0s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 51s
RouteGraph no longer stores a separate nodes list; getNodes() computes
from root tree. Tests now build proper tree via setRoot() + setChildren()
instead of calling setNodes().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 19:41:04 +02:00
hsiegeln
7e968dc06b fix: use root tree for compound node detection instead of flat nodes list
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 1m4s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
The agent now sends shallow copies (without children) in the flat nodes
list. Build nodeById map by walking graph.getRoot() tree which preserves
children, falling back to flat list via putIfAbsent for compatibility.

Also adds EIP_FILTER, EIP_IDEMPOTENT_CONSUMER, EIP_RECIPIENT_LIST as
new compound container types per updated DIAGRAMS.md.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 19:34:40 +02:00
hsiegeln
0ec41bc02c docs: add dashboard design spec
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / build (push) Has been cancelled
Progressive drill-down dashboard following RED method (Rate, Errors,
Duration) with 3 scope levels driven by sidebar selection.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 19:02:35 +02:00
hsiegeln
59ddbb65b9 revert: re-apply EIP_CIRCUIT_BREAKER compound rendering
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m2s
CI / docker (push) Successful in 1m1s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Restores e8039f9. The compound rendering regression was caused by
the agent sending flat nodes without children, not the renderer code.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 18:22:43 +02:00
hsiegeln
673f0958c5 revert: temporarily revert EIP_CIRCUIT_BREAKER compound rendering
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m4s
CI / docker (push) Successful in 58s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s
Reverting e8039f9 to diagnose compound rendering regression affecting
all compound types (SPLIT, CHOICE, LOOP, DO_TRY) and error handlers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 18:15:10 +02:00
hsiegeln
e8039f9cc4 feat: render EIP_CIRCUIT_BREAKER as compound container with main/fallback lanes
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m1s
CI / docker (push) Successful in 58s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 41s
Follow the DO_TRY pattern: virtual _CB_MAIN wrapper for main path children,
onFallback rendered as _CB_FALLBACK section with purple dashed border.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 17:40:28 +02:00
hsiegeln
9eb2c2692b fix: render continuation edges exiting compound nodes (SPLIT, CHOICE)
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m2s
CI / docker (push) Successful in 42s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
The cross-root boundary check in createElkEdges() was too aggressive,
skipping all edges where source and target have different ELK roots.
Compound nodes are their own ELK roots, so valid continuation edges
from the last child inside a compound to the next sibling were lost.

Now allows edges when nodes share a common grandparent or when one
node exits/enters a compound boundary.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 16:39:14 +02:00
hsiegeln
090c51c809 feat: resolved URI display and drill-down for TO/TO_DYNAMIC nodes
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m2s
CI / docker (push) Successful in 55s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
- Show resolved endpoint URI as teal italic line on diagram nodes
  when execution overlay is active
- Enable drill-down for TO and TO_DYNAMIC nodes (not just DIRECT/SEDA)
- Use runtime resolvedEndpointUri from execution overlay for drill-down
  when static endpointUri doesn't match
- Increase node height from 50px to 56px to accommodate the third line

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 16:30:11 +02:00
hsiegeln
32cde5363f fix: show resolvedEndpointUri in info tab, reflect trace/tap state in toolbar
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m40s
CI / docker (push) Successful in 1m49s
CI / deploy (push) Successful in 53s
CI / deploy-feature (push) Has been skipped
- Info tab now reads processor.resolvedEndpointUri instead of hardcoded "-"
- Toolbar buttons highlight in teal/purple when trace/tap is active
- Tooltip changes to "Disable tracing" / "Edit tap" when active

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 15:45:06 +02:00
hsiegeln
604e5db874 fix: write has_trace_data to OpenSearch document during indexing
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m2s
CI / docker (push) Successful in 38s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
The toMap() method was missing the has_trace_data field, so it was
never indexed despite being read back in hitToSummary().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 14:39:57 +02:00
hsiegeln
a4fcb8810f fix: use actual lucide Footprints icon for trace badges
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m2s
CI / docker (push) Successful in 51s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
Replace hand-drawn teardrop paths (looked like plants) with the real
lucide Footprints SVG paths. Configured = bare teal icon, data captured
= white icon in solid teal circle with staggered pulse rings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 13:49:36 +02:00
hsiegeln
3d71345181 feat: trace data indicators, inline tap config, and detail tab gating
All checks were successful
CI / build (push) Successful in 1m46s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 1m25s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 1m57s
Trace data visibility:
- ProcessorNode now includes hasTraceData flag computed from captured
  body/headers during tree conversion
- ConfigBadge shows teal for tracing configured, green when data captured
- Search results show green footprints icon for exchanges with trace data
- New has_trace_data column on executions table (V11 migration with backfill)
- OpenSearch documents and ExecutionSummary include the flag

Inline tap configuration:
- Extracted reusable TapConfigModal component from RouteDetail
- Diagram context menu opens tap modal inline instead of navigating away
- Toggle-trace action works immediately with toast feedback
- Modal closes only on ESC, Cancel, Save, or Delete (not backdrop click)

Detail panel tab gating:
- Headers, Input, Output tabs disabled when no data is available
- Works at both exchange and processor level
- Falls back to Info tab when active tab becomes empty

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 13:08:58 +02:00
hsiegeln
5103f40196 feat: replace Unicode diagram icons with lucide SVG icons
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 58s
CI / docker (push) Successful in 54s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Each of the ~40 node types now has a distinct, semantically meaningful
lucide icon rendered as crisp SVG paths. Compound node headers also
show their icon left-aligned in the header bar.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 11:56:19 +02:00
hsiegeln
09a60c5a6c feat: add camel logo and random desert-themed subtitles to login page
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 59s
CI / docker (push) Successful in 52s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
25 rotating cameleer-themed login subtitles picked randomly on each
page load. Also adds the camel logo SVG next to the app name.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 11:10:27 +02:00
hsiegeln
7a84914866 fix: use cameleer logo as favicon, upgrade design system to v0.1.20, fix DataTable scroll
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 57s
CI / docker (push) Successful in 1m16s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
- Replace placeholder clock favicon with cameleer camel logo SVG
- Upgrade @cameleer/design-system from v0.1.19 to v0.1.20
- Add minHeight: 0 to main element to complete flex chain for fillHeight DataTable

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 11:02:32 +02:00
hsiegeln
88c51b75bf docs: mark design system update instructions as done in v0.1.19
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 56s
CI / docker (push) Successful in 10s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 35s
SonarQube / sonarqube (push) Failing after 1m57s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 22:20:34 +01:00
hsiegeln
3f87f37095 fix: register JavaTimeModule on DetailService ObjectMapper
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 58s
CI / docker (push) Successful in 40s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Same issue as IngestionService — the ObjectMapper deserializing
processors_json lacked JavaTimeModule, causing Instant parsing to fail
silently and falling back to the broken flat reconstruction.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 22:12:06 +01:00
hsiegeln
ac4476ccd6 fix: register JavaTimeModule on ObjectMapper for processors_json serialization
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m1s
CI / docker (push) Successful in 40s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 51s
The ObjectMapper used to serialize the processor tree to JSON lacked
JavaTimeModule, causing Instant fields (startTime, endTime) to fail
silently — processors_json was always null.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 22:02:05 +01:00
hsiegeln
30344d29b1 feat: store raw processor tree JSON and add error categorization fields
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m0s
CI / docker (push) Successful in 53s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Fixes iteration overlay corruption caused by flat storage collapsing
duplicate processorIds across loop iterations.

Server:
- Store raw processor tree as processors_json JSONB on executions table
- Detail endpoint serves from processors_json (faithful tree), falls back
  to flat record reconstruction for older executions
- V10 migration: processors_json, error categorization (errorType,
  errorCategory, rootCauseType, rootCauseMessage), OTel (traceId, spanId),
  circuit breaker (circuitBreakerState, fallbackTriggered), drops
  erroneous splitDepth/loopDepth columns
- Add all new fields through full ingestion/storage/API chain

UI:
- Fix overlay wrapper filtering: check wrapper type before status filter
- Add new fields to schema.d.ts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 21:44:54 +01:00
hsiegeln
f12f5f3c8d feat: color minimap nodes by execution overlay state
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 57s
CI / docker (push) Successful in 53s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
Minimap reflects execution overlay: green for completed, red for failed,
grey for skipped nodes. ENDPOINT nodes are always green when overlay is
active (route entry point, same as main diagram logic).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 19:52:12 +01:00
hsiegeln
c6f70968a2 fix: update tests for new ProcessorRecord fields
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m0s
CI / docker (push) Successful in 54s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
Add resolvedEndpointUri, splitDepth, loopDepth arguments to
ProcessorRecord constructors in TreeReconstructionTest and
PostgresExecutionStoreIT.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 19:05:29 +01:00
hsiegeln
faf5d505f4 feat: support iteration wrapper nodes and filter overlay by selected iteration
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 38s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Server:
- Add split_depth and loop_depth columns (V9 migration)
- Persist splitDepth/loopDepth with reflection fallback for older agent versions

UI:
- Detect iterations via wrapper processorTypes (loopIteration, splitIteration, multicastBranch)
- Filter overlay by selected iteration at the wrapper level
- Skip non-selected iteration wrappers entirely (wrapper + children)
- Don't add synthetic wrappers to overlay (no diagram node correspondence)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 18:57:27 +01:00
hsiegeln
c4b396e618 feat: persist and expose resolvedEndpointUri for execution-level drill-down
Wire resolvedEndpointUri through the full chain:
- V9 migration adds resolved_endpoint_uri column
- IngestionService extracts from ProcessorExecution
- PostgresExecutionStore persists and reads the column
- ProcessorNode includes field in detail API response
- UI schema updated for ProcessorNode and PositionedNode

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 18:37:11 +01:00
hsiegeln
e5e6175aca feat: use endpointUri for cross-route drill-down instead of label parsing
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m2s
CI / docker (push) Successful in 1m0s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s
Server:
- Add endpointUri to PositionedNode (from RouteNode)
- Add fromEndpointUri to RouteSummary (catalog API)
- Catalog controller resolves endpoint URI from diagram store

UI:
- Build endpointRouteMap from catalog's fromEndpointUri field
- Drill-down uses exact match on node.endpointUri against the map
- Remove label parsing heuristics (extractTargetEndpoint, camelToKebab)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 18:31:08 +01:00
hsiegeln
0516207e83 fix: vertically center DO_TRY block relative to outer flow nodes
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m0s
CI / docker (push) Successful in 51s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Remove PORT_ALIGNMENT_DEFAULT=BEGIN so NETWORK_SIMPLEX centers edges
at the vertical midpoint of the compound instead of the top.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 17:47:34 +01:00
hsiegeln
d79e7d0168 fix: color edges into compound nodes green when descendants were executed
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m11s
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / docker (push) Has been cancelled
Edges into/out of compound nodes (DO_TRY, EIP_CHOICE, etc.) now show as
traversed (green) when any descendant node was executed, instead of grey.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 17:45:30 +01:00
hsiegeln
7c88b03956 fix: left-align DO_TRY sections and shrink container to fit content
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m22s
CI / docker (push) Successful in 42s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 41s
- Left-align all sections (try_body, doFinally, doCatch) within DO_TRY
- Shrink DO_TRY height to match actual content, removing bottom padding

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 17:36:01 +01:00
hsiegeln
55e1c7cbb5 fix: improve DO_TRY diagram layout and node text clipping
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m5s
CI / docker (push) Successful in 1m14s
CI / deploy (push) Successful in 49s
CI / deploy-feature (push) Has been skipped
- Use NETWORK_SIMPLEX placement for vertical centering of root flow nodes
- Skip structural edges from all compound nodes to descendants (not just DO_TRY)
- Reduce DO_TRY section spacing from NODE_SPACING*0.4 to fixed 20px
- Use SVG clipPath for node text instead of character-count truncation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 17:28:07 +01:00
hsiegeln
6a1d199da6 fix: detect ARM64 architecture for sonar-scanner download
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m20s
CI / docker (push) Successful in 13s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 35s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 17:22:03 +01:00
hsiegeln
459f4d2e0c fix: improve diagram node readability and add UI to SonarQube scan
All checks were successful
CI / build (push) Successful in 1m8s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 1m0s
CI / deploy (push) Successful in 41s
CI / deploy-feature (push) Has been skipped
- Increase node width (160→220), height (40→50), spacing (90→120)
- Use SVG clipPath for text instead of character-count truncation
- Add UI sources, ESLint report, and sonar-scanner CLI to SonarQube workflow

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 17:16:36 +01:00
hsiegeln
27249c2440 feat: upgrade design system to v0.1.19, use onNavigate/fillHeight, add SonarQube workflow
All checks were successful
CI / build (push) Successful in 1m36s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 2m10s
CI / deploy (push) Successful in 50s
CI / deploy-feature (push) Has been skipped
- Use Sidebar onNavigate callback instead of display:contents click interception
- Use DataTable fillHeight prop instead of manual scroll wrapper divs
- Fix DataTable scroll/pagination by adding overflow:hidden to content container
- Fix left panel in split view to use flex column instead of overflow:auto
- Make error tab stack trace scrollable for large traces
- Add nightly SonarQube workflow with manual trigger support

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 16:57:12 +01:00
hsiegeln
f59423bc91 docs: add design system update instructions for Sidebar onNavigate and DataTable fillHeight
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m8s
CI / docker (push) Successful in 1m1s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 35s
2026-03-28 16:20:45 +01:00
hsiegeln
e5be9f81e0 fix(ui): restore agents in sidebar for ops quick access
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / build (push) Has been cancelled
2026-03-28 16:20:07 +01:00
hsiegeln
9f281c3354 chore(ui): remove dead code from navigation redesign
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m3s
CI / docker (push) Successful in 1m2s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Deleted:
- ScopeTrail component (replaced by inline breadcrumb in TopBar)
- ExchangeList component (replaced by Dashboard DataTable)
- ExchangeDetail page (replaced by inline split view)

Removed from Dashboard:
- flattenProcessors() function (unused after detail panel removal)
- 11 dead CSS classes (panelSection, overviewGrid, errorBlock,
  inspectLink, openDetailLink, filterBar, etc.)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 16:14:15 +01:00
hsiegeln
f2a094f349 fix(ui): position config badges fully above node to avoid overlap
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m4s
CI / docker (push) Successful in 55s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
2026-03-28 16:06:34 +01:00
hsiegeln
dd1cae6f70 feat(ui): replace text badges with droplet/footprint icons matching context menu
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / build (push) Has been cancelled
2026-03-28 16:05:45 +01:00
hsiegeln
7903a300db fix(ui): restore TRACE/TAP badges on diagram nodes via nodeConfigs
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / build (push) Has been cancelled
2026-03-28 16:04:53 +01:00
hsiegeln
5873e6a57c fix(ui): keep execution overlay active when drilled down into sub-routes
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m5s
CI / docker (push) Successful in 59s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Has been cancelled
2026-03-28 16:02:44 +01:00
hsiegeln
816a034d4a feat(ui): show process diagram when route is selected in sidebar
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m47s
CI / docker (push) Successful in 1m3s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
2026-03-28 15:58:38 +01:00
hsiegeln
2fade7192a fix(ui): prevent text selection on double-click in process diagram
Some checks failed
CI / build (push) Successful in 1m45s
CI / cleanup-branch (push) Has been skipped
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / docker (push) Has been cancelled
2026-03-28 15:56:36 +01:00
hsiegeln
175e62f514 docs: update navigation redesign spec to reflect final implementation
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m3s
CI / docker (push) Successful in 8s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 35s
2026-03-28 15:51:37 +01:00
hsiegeln
b4c9be9334 feat(ui): browser Back/Forward restores exchange selection via history state
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m1s
CI / docker (push) Successful in 57s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
Each exchange selection (from table or correlation chain) pushes a
browser history entry with the selected exchange in location.state.
When the user navigates away (to agent details, app scope, etc.) and
presses Back, the previous history entry is restored and the split
view with the diagram reappears exactly as they left it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 15:48:38 +01:00
hsiegeln
8b276a92a7 fix(ui): clicking app or route in exchange header clears selection and returns to table
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m2s
CI / docker (push) Successful in 56s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
2026-03-28 15:42:45 +01:00
hsiegeln
01c6d5c131 fix(ui): consistent attribute badge colors based on value hash across all views
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 59s
CI / docker (push) Successful in 55s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 35s
2026-03-28 15:37:49 +01:00
hsiegeln
626501cb04 feat(ui): add Log tab to diagram detail panel with exchange/processor filtering
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m1s
CI / docker (push) Successful in 56s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s
2026-03-28 15:32:55 +01:00
hsiegeln
3362417907 fix(ui): remove Duration label from correlation row, keep value only
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m1s
CI / docker (push) Successful in 54s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Has been cancelled
2026-03-28 15:30:22 +01:00
hsiegeln
7b2622fca9 fix(ui): move correlation duration to far right
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / build (push) Has been cancelled
2026-03-28 15:29:35 +01:00
hsiegeln
24d760af8a feat(ui): show total correlation duration (oldest start to latest end)
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / build (push) Has been cancelled
2026-03-28 15:28:51 +01:00
hsiegeln
d32bde58e2 fix(ui): correlated exchange click updates local state instead of navigating
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 55s
CI / docker (push) Successful in 53s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
2026-03-28 15:26:01 +01:00
hsiegeln
3d86d57a80 fix(ui): always show correlation section, display message when none found
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m0s
CI / docker (push) Has started running
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
2026-03-28 15:24:20 +01:00
hsiegeln
29f4be542b fix(ui): exchange selection uses state, not URL navigation
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m0s
CI / docker (push) Successful in 55s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Row click no longer navigates to /exchanges/:app/:route/:id which was
changing the search scope. Instead, Dashboard calls onExchangeSelect
callback and ExchangesPage manages the selected exchange as local state.
The search criteria and scope are preserved when selecting an exchange.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 15:20:17 +01:00
hsiegeln
2f2e503447 feat(ui): split agent links (app→overview, id→detail), color server icon by state
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m2s
CI / docker (push) Successful in 52s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
2026-03-28 15:16:54 +01:00
hsiegeln
7ee57ca975 feat(ui): make app/route/agent clickable in exchange header for navigation
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m2s
CI / docker (push) Successful in 54s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Has been cancelled
2026-03-28 15:14:48 +01:00
hsiegeln
c8fcee9d09 feat(ui): add route and agent icons in exchange header
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 58s
CI / docker (push) Successful in 52s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Has been cancelled
2026-03-28 15:12:37 +01:00
hsiegeln
0ed30d92f1 fix(ui): use application name as agent name in exchange header
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m1s
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / docker (push) Has been cancelled
2026-03-28 15:11:23 +01:00
hsiegeln
4e59b0bcd0 fix(ui): remove exchange ID, reorder to app/route, add agent label
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / build (push) Has been cancelled
2026-03-28 15:10:56 +01:00
hsiegeln
eaeef6f0b2 fix(ui): move agent ID before duration in exchange header
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 58s
CI / docker (push) Successful in 52s
CI / deploy-feature (push) Has been cancelled
CI / deploy (push) Has been cancelled
2026-03-28 15:09:00 +01:00
hsiegeln
9f0c2e1225 feat(ui): show agent ID in exchange header info row
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m0s
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
2026-03-28 15:07:58 +01:00
hsiegeln
e934b31164 feat(ui): show tap-collected attributes as badges in exchange header
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / build (push) Has been cancelled
2026-03-28 15:07:23 +01:00
hsiegeln
77d871c4f8 fix(ui): sort headers alphabetically in diagram detail panel
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m1s
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
2026-03-28 15:05:47 +01:00
hsiegeln
4296d41cad fix(ui): show full exchange ID without truncation
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m1s
CI / docker (push) Successful in 52s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
2026-03-28 15:02:52 +01:00
hsiegeln
a5ba684c7d feat(ui): redesign ExchangeHeader with info bar, arrows, and navigation
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m3s
CI / docker (push) Successful in 56s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Has been cancelled
- Always shows exchange info row: status dot, badge, ID, route, app, duration
- Correlation chain: arrow connectors between nodes, route name + duration per node
- Click on correlated exchange navigates to /exchanges/:app/:route/:exchangeId
- Compact styling with bg-raised background, proper visual hierarchy
- Horizontal scroll for long correlation chains

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 15:00:45 +01:00
hsiegeln
a658ed9135 Revert "fix(ui): pin DataTable pagination to bottom, table body scrolls independently"
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 58s
CI / docker (push) Successful in 9s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 35s
This reverts commit b863370511.
2026-03-28 14:57:16 +01:00
hsiegeln
b863370511 fix(ui): pin DataTable pagination to bottom, table body scrolls independently
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / build (push) Has been cancelled
2026-03-28 14:56:23 +01:00
hsiegeln
048f6566a9 fix(ui): make exchange table fill page height with vertical scroll
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 58s
CI / docker (push) Successful in 50s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Has been cancelled
2026-03-28 14:54:29 +01:00
hsiegeln
5cb3de03af fix(ui): remove whitespace between components for integrated layout
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m1s
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / docker (push) Has been cancelled
2026-03-28 14:52:53 +01:00
hsiegeln
ef9d8c8066 fix(ui): remove summary section from ExchangeHeader, keep only correlation chain
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 59s
CI / docker (push) Successful in 53s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Has been cancelled
2026-03-28 14:50:42 +01:00
hsiegeln
1ca4cac396 fix(ui): restore proper correlation chain styling with StatusDot, route names, colored borders
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / build (push) Has been cancelled
2026-03-28 14:49:45 +01:00
hsiegeln
6b06e7f86b fix(ui): remove shortcuts bar from Dashboard
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m0s
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
2026-03-28 14:47:57 +01:00
hsiegeln
e703a9d39d fix(ui): remove exchange summary bar from ExecutionDiagram
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / build (push) Has been cancelled
2026-03-28 14:47:03 +01:00
hsiegeln
67bae5640c refactor(ui): remove KPI strip from Dashboard — metrics now in tab bar
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m2s
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / docker (push) Has been cancelled
2026-03-28 14:45:08 +01:00
hsiegeln
c06f0c89e5 feat(ui): add compact KPI metrics in tab bar (Total, Err%, Avg, P99)
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m1s
CI / docker (push) Successful in 51s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Has been cancelled
New TabKpis component shows scope-aware metrics with trend arrows
aligned right in the content tab bar. Each metric shows current value
and an arrow indicating change vs previous period (green=good, red=bad).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 14:42:58 +01:00
hsiegeln
73560d761d fix(ui): pass onNodeAction to diagram components to restore context menu
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m10s
CI / docker (push) Successful in 1m1s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
2026-03-28 14:37:58 +01:00
hsiegeln
4ed804141a fix(ui): add top offset to diagram reset view to clear breadcrumb bar
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m10s
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / docker (push) Has been cancelled
2026-03-28 14:36:24 +01:00
hsiegeln
de2281cad2 fix(ui): move minimap above zoom controls in bottom-right corner
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m7s
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / docker (push) Has been cancelled
2026-03-28 14:35:04 +01:00
hsiegeln
5af20d0f63 refactor(ui): remove detail panel slide-in and inspect column from exchange table
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m12s
CI / docker (push) Successful in 1m5s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Has been cancelled
Row click now navigates directly to the split view with diagram.
Removed: DetailPanel, inspect column, unused imports (ExternalLink,
ProcessorTimeline, RouteFlow, useExecutionDetail, useDiagramLayout,
buildFlowSegments).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 14:32:20 +01:00
hsiegeln
91171590e6 feat(ui): add draggable splitter between search results and diagram panel
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m9s
CI / docker (push) Successful in 1m1s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
2026-03-28 14:29:19 +01:00
hsiegeln
699ef86f8f fix(ui): use Tabs instead of SegmentedTabs for content navigation
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m4s
CI / docker (push) Has started running
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
2026-03-28 14:27:28 +01:00
hsiegeln
d63a9f8ce7 fix(ui): move scope trail into TopBar breadcrumb instead of separate element
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / build (push) Has been cancelled
2026-03-28 14:26:36 +01:00
hsiegeln
77c73fe3e6 fix(ui): use display:contents on sidebar wrapper to preserve flex layout
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m2s
CI / deploy (push) Has been cancelled
CI / docker (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
2026-03-28 14:25:20 +01:00
hsiegeln
1e6de17084 fix(ui): restore layout — same table everywhere, 50:50 split, full-height sidebar, tab styling
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m2s
CI / docker (push) Successful in 59s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
- Sidebar wrapper gets height:100% to fill window
- Route-scoped Exchanges uses same Dashboard table (not compact ExchangeList)
- 50:50 grid split: table on left, diagram on right when route selected
- ContentTabs gets border-bottom and surface background for visibility

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 14:22:34 +01:00
7ee7076eec Merge pull request 'feat/navigation-redesign' (#92) from feat/navigation-redesign into main
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m0s
CI / docker (push) Successful in 9s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 35s
Reviewed-on: cameleer/cameleer3-server#92
2026-03-28 14:09:38 +01:00
hsiegeln
698b97d536 fix(ui): update Dashboard links to use new exchange URL structure
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m5s
CI / docker (push) Successful in 54s
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Successful in 40s
CI / cleanup-branch (pull_request) Has been skipped
CI / build (pull_request) Successful in 1m8s
CI / docker (pull_request) Has been skipped
CI / deploy (pull_request) Has been skipped
CI / deploy-feature (pull_request) Has been skipped
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 14:02:49 +01:00
hsiegeln
4fe418cc89 feat(ui): integrate ContentTabs, ScopeTrail, and sidebar scope interception
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 14:01:52 +01:00
hsiegeln
66abb1fe3a feat(ui): restructure router for tab-based navigation with legacy redirects
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 13:59:20 +01:00
hsiegeln
611c201887 feat(ui): add RuntimePage and DashboardPage tab wrappers
Thin wrapper pages that conditionally render AgentHealth/AgentInstance
and RoutesMetrics/RouteDetail based on URL params for the nav redesign.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 13:58:10 +01:00
hsiegeln
f2abe296ee feat(ui): add ExchangesPage with full-width and 3-column modes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 13:57:13 +01:00
hsiegeln
fc27880d96 feat(ui): add ExchangeHeader component with correlation chain
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 13:55:13 +01:00
hsiegeln
8219c54422 feat(ui): add ExchangeList compact component for 3-column layout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 13:55:08 +01:00
hsiegeln
c1b156bdb4 feat(ui): add ContentTabs component (Exchanges | Dashboard | Runtime)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 13:52:54 +01:00
hsiegeln
0eb377b515 feat(ui): add ScopeTrail component for scope-based breadcrumbs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 13:52:49 +01:00
hsiegeln
facf7fb6ef feat(ui): add useScope hook for tab+scope URL management 2026-03-28 13:51:35 +01:00
hsiegeln
90be1875e0 refactor: simplify ElkDiagramRenderer layout code
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 59s
CI / docker (push) Successful in 40s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
- Introduce LayoutContext to bundle 8 accumulator params into 1 object
- Extract computeLayout (261 lines) into 6 focused sub-methods:
  buildNodeIndex, partitionNodes, createElkRoot, createElkEdges,
  postProcessDoTrySections, extractLayout
- Consolidate duplicated DO_TRY handler iteration via orderedHandlerChildren
- De-duplicate ELK root configuration (main + handler roots)
- Add DO_TRY test cases for section ordering and uniform width
- Clean up orphaned Javadoc comments

No behavioral changes. 882 → 841 lines.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 12:20:08 +01:00
hsiegeln
065517f032 fix: align main flow at DO_TRY top and stretch sections to full width
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 59s
CI / docker (push) Successful in 41s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
Port alignment BEGIN on DO_TRY compounds makes edges attach at the top
instead of center, keeping the main flow level. Post-processing also
stretches all DO_TRY sections (doFinally, doCatch) to match the widest
section's width for visual consistency.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 10:26:28 +01:00
hsiegeln
99b97c53dd fix: restore node click/dblclick by limiting pointer capture to empty space
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 59s
CI / docker (push) Successful in 54s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 35s
setPointerCapture on the SVG redirected click/dblclick events away from
node <g> elements, breaking drill-down (double-click) and potentially
click selection. Now only capture the pointer when clicking on empty SVG
space, preserving normal event flow on nodes while keeping drag-to-pan.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 10:11:47 +01:00
hsiegeln
79e5caaf7a fix: post-process ELK graph to enforce DO_TRY section order
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m2s
CI / docker (push) Successful in 1m1s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
ELK's partitioning doesn't reliably order disconnected children within
a compound node. Instead, let ELK lay out freely then re-stack sections
in correct order (try_body → doFinally → doCatch) by adjusting Y
positions in the ELK graph before extraction. This propagates correctly
to both node and edge coordinates via getAbsoluteY().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 10:03:50 +01:00
hsiegeln
5b5fa28ba0 fix: use ELK partitioning to enforce DO_TRY section order
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 57s
CI / docker (push) Successful in 41s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Invisible ordering edges caused horizontal layout. Replace with ELK's
partitioning feature which explicitly assigns sections to ordered layers:
try_body (partition 0) → doFinally (1) → doCatch (2).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 09:44:16 +01:00
hsiegeln
3b2c5ccdbe fix: use invisible ordering edges to enforce DO_TRY section order
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m1s
CI / docker (push) Successful in 39s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
Layer constraints (FIRST/LAST) don't work for disconnected components
in ELK's layered algorithm. Replace with invisible edges that chain
try_body → doFinally → doCatch to guarantee correct top-to-bottom order.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 09:37:49 +01:00
hsiegeln
c8d824d347 fix: only skip DO_TRY edges to internal children, keep continuation edges
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m1s
CI / docker (push) Successful in 40s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
The previous fix skipped ALL edges from DO_TRY nodes, which also
removed the continuation edge to the next node in the main flow
(causing LOG nodes to appear disconnected). Now checks if the target
is a descendant of the DO_TRY ELK node — only internal edges are
skipped, continuation edges to the next main flow node are kept.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 09:18:26 +01:00
hsiegeln
615a3c6e99 fix: order DO_TRY sections as try-body, finally, catch and reduce spacing
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 59s
CI / docker (push) Successful in 38s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
ELK TB layout places children in insertion order. Now explicitly adds
DO_FINALLY before DO_CATCH so the visual order inside DO_TRY is:
try body (top) → finally → catch blocks (bottom). Also reduces
internal spacing to keep the compound more compact.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 09:15:40 +01:00
hsiegeln
dbf64ecb48 feat: render doTry/doCatch/doFinally like route-level handler sections
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m3s
CI / docker (push) Successful in 55s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s
Backend: DO_TRY compounds now use a virtual _TRY_BODY wrapper with LR
layout for the try body, while DO_CATCH/DO_FINALLY stack below as
separate sections (TB). Edges from DO_TRY are skipped like route-level
handler edges. Removes ELK-v2 debug logging.

Frontend: _TRY_BODY renders as transparent wrapper, DO_CATCH as red
tinted section, DO_FINALLY as teal section. DO_FINALLY color changed
from red to teal (completion handler, not error).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 09:04:36 +01:00
hsiegeln
1702200a60 feat: Cmd+K Enter applies full-text search to dashboard
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m47s
CI / docker (push) Successful in 1m16s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
When pressing Enter in the command palette without explicitly selecting
a result (via arrow keys or mouse), the search query is now applied as
a server-side full-text filter on the Dashboard table. Explicit
selection still navigates to the exchange. Updates design system to
v0.1.18 for the new onSubmit prop.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 23:33:39 +01:00
hsiegeln
004574d442 fix: allow drag-to-pan over diagram nodes and compounds
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 58s
CI / docker (push) Successful in 55s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Previously onPointerDown bailed out when the target was inside a node
(data-node-id), blocking pan entirely over nodes and compound groups.
Now panning always starts, and a didPan ref distinguishes drag from
click — node click handlers skip selection when the user was dragging.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 23:21:05 +01:00
hsiegeln
41111b082c chore: replace Unicode/emoji icons with Lucide React
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m14s
CI / docker (push) Successful in 1m11s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Adds lucide-react and replaces all HTML entity and emoji icons across
the UI with proper SVG icon components. Tree-shaken — only imported
icons are bundled.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 23:16:39 +01:00
hsiegeln
e9b1c94d1a fix: move status filtering server-side in Dashboard search
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 59s
CI / docker (push) Successful in 54s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 35s
The Dashboard was fetching 50 results without a status filter and
filtering client-side, causing fewer matches when filtering by error
compared to route-specific pages that filter server-side. Now passes
statusFilters to the OpenSearch query. Backend supports comma-separated
status values for multi-select filters.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 23:00:32 +01:00
hsiegeln
0d7d04501c chore: resize minimap to match zoom controls width (140x90)
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 58s
CI / docker (push) Successful in 54s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 22:41:06 +01:00
hsiegeln
6393e5096f chore: move minimap to top-right corner of diagram
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Has started running
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 22:40:28 +01:00
hsiegeln
4af71aabac fix: use graph root + edge walk to separate main flow from handlers
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 58s
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
Root cause: graph.getNodes() is a flat list with duplicates — handler
compound children appear both nested inside their parent AND as
top-level entries. The previous separation tried to filter the flat
list but missed the duplicates, leaving handler children in rootNode.

New approach: walk from graph.getRoot() following non-ERROR edges to
discover main flow nodes. Edges targeting handler compounds (ON_EXCEPTION,
ON_COMPLETION) are not followed. This cleanly separates main flow from
handler sections using the graph's own structure.

Falls back to flat list filtering (old behavior) when graph.getRoot()
is null (legacy/test graphs).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 22:39:04 +01:00
hsiegeln
acb7cade90 fix: exclude handler compound children from main flow ELK graph
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 56s
CI / docker (push) Successful in 39s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 35s
Root cause found: RouteGraph.getNodes() is a FLAT list that includes
handler compound children (log8, setBody1, etc.) as top-level entries
alongside the main flow nodes. The handler separation only identified
the compound PARENTS (ON_EXCEPTION) but not their children, so 7
handler children leaked into rootNode as main flow nodes, causing
ELK to place the real main flow at wrong Y positions.

Fix: two-pass separation — first identify handler compounds and
collect ALL descendant IDs, then build mainNodes excluding both
handler compounds AND their descendants.

Debug logging left in temporarily for verification.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 22:33:40 +01:00
hsiegeln
19d3c8fa93 debug: v2 ELK logging to verify handler separation in new build
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 57s
CI / docker (push) Successful in 38s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 35s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 22:29:23 +01:00
hsiegeln
990d607d4b fix: normalize main flow section to (0,0) origin in frontend
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 58s
CI / docker (push) Successful in 49s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
The root cause of the Y-offset: ELK places main flow nodes at
arbitrary positions (e.g., y=679) within its root graph, and the
frontend rendered them at those raw positions. Handler sections were
already normalized via shiftNodes, but the main section was not.

Now useDiagramData.ts applies the same normalization to the main
section: computes bounding box, shifts nodes and edges so the section
starts at (0,0). This fixes the Y-offset regardless of what ELK
produces internally.

Removed the backend normalizePositions (was ineffective because handler
nodes at y=12 dominated the global minimum, preventing meaningful shift
of main flow nodes at y=679).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 22:26:35 +01:00
hsiegeln
0df7735d20 fix: comprehensive ElkDiagramRenderer cleanup and Y-offset fix
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 55s
CI / docker (push) Successful in 38s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 35s
Based on thorough code review, fixes all identified issues:

1. **Y-offset root cause**: Added post-layout normalization that shifts
   all positioned nodes and edges so the bounding box starts at (0,0).
   ELK can place nodes at arbitrary positions within its root graph;
   normalizing compensates regardless of what ELK computes internally.

2. **Bounding box**: Compute from recursively flattened node tree +
   edge point bounds. Removes double-counting of compound children
   (children have absolute coords, not relative to parent).

3. **SVG double-drawing**: Compound children were drawn both inside
   drawCompoundContainer and again in the allNodes loop. Now collects
   compound child IDs and skips them in the second pass.

4. **findNode**: Now recurses into children for nested compound lookup.

5. **colorForType**: Removed redundant double-check on EIP_TYPES.

6. **Dead code removed**: routeNodeMap/indexNodeRecursive (populated but
   never read), MIN_NODE_WIDTH/CHAR_WIDTH/LABEL_PADDING (unused).

7. **Static initialization**: LayoutMetaDataProvider registration moved
   from constructor to static block (runs once, not per instance).

8. **Debug logging removed**: Removed diagnostic System.out.println.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 22:20:33 +01:00
hsiegeln
7926179ed9 debug: add ELK root layout logging to diagnose Y-offset issue
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 57s
CI / docker (push) Successful in 38s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 34s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 22:14:16 +01:00
hsiegeln
1855153dbe fix: proper LCA and bounding box for multi-root ELK layout
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 58s
CI / docker (push) Successful in 38s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 35s
1. findCommonParent: replaced with correct lowest common ancestor
   algorithm using ancestor set intersection (previous version only
   walked from node 'a', not a true LCA)

2. Bounding box: compute totalWidth/totalHeight from actual positioned
   node coordinates instead of rootNode.getWidth/Height. The rootNode
   dimensions don't account for handler sections in separate ELK roots.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 22:09:16 +01:00
hsiegeln
3751762c69 fix: use correct ELK root for handler node coordinate extraction
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 58s
CI / docker (push) Successful in 39s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
Handler section nodes were positioned relative to rootNode, but they
live under separate handlerRoot ELK graphs. Using getElkRoot() to find
each node's actual root ensures correct absolute coordinates.

This combined with the POLYLINE edge routing should eliminate the
Y-offset misalignment between main flow nodes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 21:53:36 +01:00
hsiegeln
56f98671ca fix: straight edge routing and handler section edge extraction
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 58s
CI / docker (push) Successful in 53s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Backend:
- Set POLYLINE edge routing on ELK root — eliminates curved/bent edges
  between horizontally aligned nodes
- Collect edges from handler section roots (not just main root) so
  internal handler edges are included in the layout output
- Use correct root reference for coordinate calculation per edge

Frontend:
- Render ALL edge points as line segments (polylines), not cubic bezier.
  ELK bend points are waypoints, not bezier control points — the cubic
  bezier interpretation caused false curves.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 20:59:38 +01:00
hsiegeln
cbe41d7ac7 feat: configure-tap action navigates to AppConfig page
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 57s
CI / docker (push) Successful in 52s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 35s
The tap button in the node toolbar now navigates to
/admin/appconfig?app=<application>&processor=<nodeId>, which
auto-selects the application in the AppConfigPage. The AppConfigPage
reads the ?app query param to open the detail panel for that app.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 20:23:19 +01:00
hsiegeln
bd8e95c6ce fix: add HIERARCHY_HANDLING to handler section ELK roots
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 57s
CI / docker (push) Successful in 38s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Handler section ELK roots were missing INCLUDE_CHILDREN, causing
edges between a handler compound and its children to fail with
UnsupportedGraphException (cross-hierarchy edge resolution).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 20:15:36 +01:00
hsiegeln
fee9b4bd83 fix: skip edges that cross ELK root graph boundaries
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 59s
CI / docker (push) Successful in 38s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s
Edges connecting main flow nodes to handler section nodes (ON_EXCEPTION,
ON_COMPLETION) now span different ELK root graphs. ELK throws
UnsupportedGraphException when an edge connects nodes in different
layout hierarchies. Skip these cross-root edges — the frontend doesn't
render them anyway (handler sections are separated visually).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 20:11:25 +01:00
hsiegeln
7ec683aca0 chore: replace toolbar icons — footprints for trace, tap for tap config
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 59s
CI / docker (push) Successful in 51s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Has been cancelled
- Toggle tracing: "T" → 👣 (footprints — trace = following the path)
- Configure tap: ✎ (pencil) → 🚰 (water tap — tap = intercept the flow)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 20:09:12 +01:00
hsiegeln
ac750b603f fix: enable scrollbar on detail panel tab content
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 59s
CI / docker (push) Successful in 51s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
The flex chain from detailArea → detailPanel → tabContent lacked
min-height: 0, so flex children never shrank below content height
and overflow-y: auto never triggered. Added min-height: 0 and
flex: 1 to propagate the height constraint correctly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 20:05:48 +01:00
hsiegeln
5306be3f2e fix: lay out handler sections in separate ELK graphs
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 58s
CI / docker (push) Successful in 37s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
ON_EXCEPTION, ON_COMPLETION, and ERROR_HANDLER compounds were included
in the same root ELK graph as the main flow. ELK's layered algorithm
offset the main flow nodes vertically to accommodate the handler
compounds, causing bent arrows between the ENDPOINT and first processor.

Now handler sections get their own independent ELK root graphs. The
frontend already separates and repositions them, so they just need
correct internal layout — not positioning relative to the main flow.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 20:03:20 +01:00
hsiegeln
b0dcd0ac6b fix: update test ProcessorRecord constructors for iteration fields
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 55s
CI / docker (push) Successful in 51s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
Tests were using the old 18-param constructor, missing the 5 new
iteration fields (loopIndex, loopSize, splitIndex, splitSize,
multicastIndex) added in V8 migration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 19:56:54 +01:00
hsiegeln
159e4adf07 chore: remove /dev/diagram test page
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 32s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
No longer needed — the ProcessDiagram is now integrated into
ExchangeDetail via the ExecutionDiagram wrapper.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 19:55:07 +01:00
hsiegeln
085c4e395b feat: execution overlay & debugger (sub-project 2)
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 36s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Adds execution overlay to the ProcessDiagram component, turning it into
an after-the-fact debugger for Camel route executions.

Backend:
- Flyway V8: iteration fields (loop/split/multicast index/size) on processor_executions
- Snapshot-by-processorId endpoint for robust processor lookup
- ELK LINEAR_SEGMENTS node placement for consistent Y-alignment

Frontend:
- ExecutionDiagram wrapper: exchange bar, resizable splitter, detail panel
- Node overlay: green tint+checkmark (completed), red tint+! (failed), dimmed (skipped)
- Edge overlay: green solid (traversed), dashed gray (not traversed)
- Per-compound iteration stepper for loops/splits/multicasts
- 7-tab detail panel: Info, Headers, Input, Output, Error, Config, Timeline
- Jump to Error: selects + centers viewport on failed processor
- Triggered error handler sections highlighted with solid red frame
- Drill-down disables overlay (sub-routes show topology only)
- Integrated into ExchangeDetail page Flow view

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 19:51:55 +01:00
hsiegeln
d7166b6d0a feat: Jump to Error centers the failed node in the viewport
Added centerOnNodeId prop to ProcessDiagram. When set, the diagram
pans to center the specified node in the viewport. Jump to Error
now selects the failed processor AND centers the viewport on it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 19:51:00 +01:00
hsiegeln
25e23c0b87 feat: highlight triggered error handler sections
When an onException/error handler section has any executed processors
(overlay entries), it renders with a stronger red tint (8% vs 3%),
a solid red border frame, and a solid divider line. This makes it
easy to identify which handler was triggered when multiple exist.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 19:47:57 +01:00
hsiegeln
cf9e847f84 fix: use design system CodeBlock for error stack trace
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 19:45:54 +01:00
hsiegeln
bfd76261ef fix: disable execution overlay when drilled into sub-route
The execution overlay data maps to the root route's processor IDs. When
drilled into a sub-route, those IDs don't match, causing all nodes to
appear dimmed. Now clears the overlay and shows pure topology when
viewing a sub-route via drill-down.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 19:43:51 +01:00
hsiegeln
0b8efa1998 fix: drill-down uses route-based fetch instead of pre-loaded layout
When drilled into a sub-route, the pre-fetched diagramLayout (loaded by
content hash for the root execution) doesn't contain the sub-route's
diagram. Only use the pre-loaded layout for the root route; fall back to
useDiagramByRoute for drilled-down sub-routes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 19:40:20 +01:00
hsiegeln
3027e9b24f fix: scrollable headers/timeline, CodeBlock for body, ELK node alignment
- Make headers tab and timeline tab scrollable when content overflows
- Replace custom <pre> code block with design system CodeBlock component
  for body tabs (Input/Output) to match existing styleguide
- Add LINEAR_SEGMENTS node placement strategy to ELK layout to fix
  Y-offset misalignment between nodes in left-to-right diagrams
  (e.g., ENDPOINT at different Y level than subsequent processors)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 19:34:25 +01:00
hsiegeln
3d5d462de0 fix: ENDPOINT node execution state, badge position, and edge traversal
- Synthesize COMPLETED state for ENDPOINT nodes when overlay is active
  (endpoints are route entry points, not in the processor execution tree)
- Move status badge (check/error) inside the card (top-right, below top bar)
  to avoid collision with ConfigBadge (TRACE/TAP) badges
- Include ENDPOINT nodes in edge traversal check so the edge from
  endpoint to first processor renders as green/traversed

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 19:29:30 +01:00
hsiegeln
f675451384 fix: use non-passive wheel listener to prevent page scroll during diagram zoom
React's onWheel is passive by default, so preventDefault() doesn't stop
page scrolling. Attach native wheel listener with { passive: false } via
useEffect instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 19:24:09 +01:00
hsiegeln
021a52e56b feat: integrate ExecutionDiagram into ExchangeDetail flow view
Replace the RouteFlow-based flow view with the new ExecutionDiagram
component which provides execution overlay, iteration stepping, and
an integrated detail panel. The gantt view and all other page sections
remain unchanged.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 19:12:11 +01:00
hsiegeln
5ccefa3cdb feat: add ExecutionDiagram wrapper component
Composes ProcessDiagram with execution overlay data, exchange summary
bar, resizable splitter, and detail panel into a single root component.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 19:05:43 +01:00
hsiegeln
e4c66b1311 feat: add DetailPanel with 7 tabs for execution diagram overlay
Implements the bottom detail panel with processor header bar, tab bar
(Info, Headers, Input, Output, Error, Config, Timeline), and all tab
content components. Info shows processor/exchange metadata in a grid,
Headers fetches per-processor snapshots for side-by-side display,
Input/Output render formatted code blocks, Error extracts exception
types, Config is a placeholder, and Timeline renders a Gantt chart.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 19:01:53 +01:00
hsiegeln
5da03d0938 feat: add useExecutionOverlay and useIterationState hooks
useExecutionOverlay maps processor tree to overlay state map, handling
iteration filtering, sub-route failure detection, and trace data flags.
useIterationState detects compound nodes with iterated children and
manages per-compound iteration selection.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 18:56:38 +01:00
hsiegeln
3af1d1f3b6 feat: add useProcessorSnapshotById hook for snapshot-by-processorId endpoint
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 18:54:01 +01:00
hsiegeln
1984c597de feat: add iteration stepper to compound nodes and thread overlay props
Add a left/right stepper widget to compound node headers (LOOP, SPLIT,
MULTICAST) when iteration overlay data is present. Thread executionOverlay,
overlayActive, iterationState, and onIterationChange props through
ProcessDiagram -> CompoundNode -> children and ProcessDiagram ->
ErrorSection -> children so leaf DiagramNode instances render with
execution state (green/red badges, dimming for skipped nodes).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 18:52:32 +01:00
hsiegeln
3029704051 feat: add traversed/not-traversed visual states to DiagramEdge
Add green solid edges for traversed paths and dashed gray for
not-traversed when execution overlay is active. Includes green
arrowhead marker and overlay threading through CompoundNode and
ErrorSection.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 18:47:59 +01:00
hsiegeln
2b805ec196 feat: add execution overlay visual states to DiagramNode
DiagramNode now accepts executionState and overlayActive props to render
execution status: green tint + checkmark badge for completed nodes, red
tint + exclamation badge for failed nodes, dimmed opacity for skipped
nodes. Duration is shown at bottom-right, and a drill-down arrow appears
for sub-route failures.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 18:44:16 +01:00
hsiegeln
ff59dc5d57 feat: add execution overlay types and extend ProcessDiagram with diagramLayout prop
Define the execution overlay type system (NodeExecutionState, IterationInfo,
DetailTab) and extend ProcessDiagramProps with optional overlay props. Add
diagramLayout prop so ExecutionDiagram can pass a pre-fetched layout by content
hash, bypassing the internal route-based fetch in useDiagramData.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 18:40:57 +01:00
hsiegeln
3928743ea7 feat: update OpenAPI spec and TypeScript types for execution overlay
Add iteration fields (loopIndex, loopSize, splitIndex, splitSize,
multicastIndex) to ProcessorNode schema. Add new endpoint path
/executions/{executionId}/processors/by-id/{processorId}/snapshot.
Remove stale diagramNodeId field that was dropped in V6 migration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 18:38:09 +01:00
hsiegeln
cf6c4bd60c feat: add snapshot-by-processorId endpoint for robust processor lookup
Add GET /executions/{id}/processors/by-id/{processorId}/snapshot endpoint
that fetches processor snapshot data by processorId instead of positional
index, which is fragile when the tree structure changes. The existing
index-based endpoint remains unchanged for backward compatibility.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 18:34:45 +01:00
hsiegeln
edd841ffeb feat: add iteration fields to processor execution storage
Add loop_index, loop_size, split_index, split_size, multicast_index
columns to processor_executions table and thread them through the
full storage → ingestion → detail pipeline. These fields enable
execution overlay to display iteration context for loop, split,
and multicast EIPs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 18:32:47 +01:00
hsiegeln
889f0e5263 chore: add .worktrees/ to .gitignore for worktree isolation 2026-03-27 18:27:34 +01:00
hsiegeln
3a41e1f1d3 docs: add execution overlay implementation plan (sub-project 2)
12 tasks covering backend prerequisites (iteration fields, snapshot-by-id
endpoint), ProcessDiagram overlay props, node/edge visual states, compound
iteration stepper, detail panel with 7 tabs, ExecutionDiagram wrapper,
and ExchangeDetail page integration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 18:25:47 +01:00
hsiegeln
509159417b docs: add execution overlay & debugger design spec (sub-project 2)
Design for overlaying real execution data onto the ProcessDiagram:
- Node status visualization (green OK, red failed, dimmed skipped)
- Per-compound iteration stepping for loops/splits
- Tabbed detail panel (Info, Headers, Input, Output, Error, Config, Timeline)
- Jump to Error with cross-route drill-down
- Backend prerequisites for iteration fields and snapshot-by-id endpoint

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 18:13:03 +01:00
hsiegeln
30c8fe1091 feat: add minimap overview to process diagram
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m0s
CI / docker (push) Successful in 57s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Small overview panel in the bottom-left showing the full diagram
layout with colored node rectangles and an amber viewport indicator.
Click or drag on the minimap to pan the main diagram.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 17:16:05 +01:00
hsiegeln
b1ff05439a docs: update design spec and increase section gap to 80px
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m5s
CI / docker (push) Successful in 54s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 35s
Update design spec with implementation notes covering recursive
compound nesting, edge z-ordering, ON_COMPLETION sections, drill-down
navigation, CSS transform zoom, and HTML overlay toolbar.

Increase SECTION_GAP to 80px for better visual separation between
completion and error handler sections.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 17:10:01 +01:00
hsiegeln
eb9c20e734 feat: drill-down into sub-routes with breadcrumb navigation
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m3s
CI / docker (push) Successful in 55s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 43s
Double-click a DIRECT or SEDA node to navigate into that route's
diagram. Breadcrumbs show the route stack and allow clicking back
to any level. Escape key goes back one level.

Route ID resolution handles camelCase endpoint URIs mapping to
kebab-case route IDs (e.g. direct:callGetProduct → call-get-product)
using the catalog's known route IDs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 16:58:35 +01:00
hsiegeln
f6220a9f89 feat: support ON_COMPLETION handler sections in diagram
Add ON_COMPLETION to backend COMPOUND_TYPES and frontend rendering.
Completion handlers render as teal-tinted sections between the main
flow and error handlers, structurally parallel to onException.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 16:45:10 +01:00
hsiegeln
9b7626f6ff fix: diagram rendering improvements
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 57s
CI / docker (push) Successful in 52s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
- Recursive compound rendering: CompoundNode checks if children are
  themselves compound types (WHEN inside CHOICE) and renders them
  recursively. Added EIP_WHEN, EIP_OTHERWISE, DO_CATCH, DO_FINALLY
  to frontend COMPOUND_TYPES.
- Edge z-ordering: edges are distributed to their containing compound
  and rendered after the background rect, so they're not hidden behind
  compound containers.
- Error section sizing: normalize error handler node coordinates to
  start at (0,0), compute red tint background height from actual
  content with symmetric padding for vertical centering.
- Toolbar as HTML overlay: moved from SVG foreignObject to absolute-
  positioned HTML div so it stays fixed size at any zoom level. Uses
  design system tokens for consistent styling.
- Zoom: replaced viewBox approach with CSS transform on content group.
  Default zoom is 100% anchored top-left. Fit-to-view still available
  via button.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 16:33:24 +01:00
hsiegeln
20d1182259 fix: recursive compound nesting, fixed node width, zoom crash
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m0s
CI / docker (push) Successful in 52s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
ELK renderer:
- Add EIP_WHEN, EIP_OTHERWISE, DO_CATCH, DO_FINALLY to COMPOUND_TYPES
  so branch body processors nest inside their containers
- Rewrite node creation and result extraction as recursive methods
  to support compound-inside-compound (CHOICE → WHEN → processors)
- Use fixed NODE_WIDTH=160 for leaf nodes instead of variable width

Frontend:
- Fix mousewheel crash: capture getBoundingClientRect() before
  setState updater (React nulls currentTarget after handler returns)
- Anchor fitToView to top-left instead of centering

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 14:26:35 +01:00
hsiegeln
afcb7d3175 fix: DevDiagram page uses time range and correct catalog shape
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m0s
CI / docker (push) Successful in 54s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
The dev diagram page was calling useRouteCatalog() without time range
params (returned empty) and parsing the wrong response shape (expected
flat {application, routeId} but catalog returns {appId, routes[]}).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 14:05:32 +01:00
hsiegeln
ac32396a57 feat: add interactive ProcessDiagram SVG component (sub-project 1/3)
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m0s
CI / docker (push) Successful in 56s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s
New interactive route diagram component with SVG rendering using
server-computed ELK layout coordinates. TIBCO BW5-inspired top-bar
card node style with zoom/pan, hover toolbars, config badges, and
error handler sections below the main flow.

Backend: add direction query parameter (LR/TB) to diagram render
endpoints, defaulting to left-to-right layout.

Frontend: 14-file ProcessDiagram component in ui/src/components/
with DiagramNode, CompoundNode, DiagramEdge, ConfigBadge, NodeToolbar,
ErrorSection, ZoomControls, and supporting hooks. Dev test page at
/dev/diagram for validation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 13:55:29 +01:00
hsiegeln
78e12f5cf9 fix: separate onException/errorHandler into distinct RouteFlow segments
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 57s
CI / docker (push) Successful in 52s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
ON_EXCEPTION and ERROR_HANDLER nodes are now treated as compound containers
in the ELK diagram renderer, nesting their children. The frontend
diagram-mapping builds separate FlowSegments for each error handler,
displayed as distinct sections in the RouteFlow component.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 09:15:06 +01:00
hsiegeln
62709ce80b feat: include tap attributes in cmd-K full-text search
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m4s
CI / docker (push) Successful in 1m13s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
Add attributes_text flattened field to OpenSearch indexing for both
execution and processor levels. Include in full-text search queries,
wildcard matching, and highlighting. Merge processor-level attributes
into ExecutionSummary. Add 'attribute' category to CommandPalette
(design-system 0.1.17) with per-key-value results in the search UI.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 08:13:58 +01:00
hsiegeln
ea88042ef5 fix: exclude search endpoint from audit log
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m1s
CI / docker (push) Successful in 37s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 28s
POST /api/v1/search/executions is a read-only query using POST for the
request body. Skip it in AuditInterceptor to avoid flooding the audit
log with search operations.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 23:55:24 +01:00
hsiegeln
cde79bd172 fix: remove stale diagramNodeId from test ProcessorRecord constructors
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m0s
CI / docker (push) Successful in 1m16s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 27s
TreeReconstructionTest and PostgresExecutionStoreIT still passed the
removed diagramNodeId parameter. Missed by mvn compile (main only);
caught by mvn verify (test compilation).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 23:40:13 +01:00
hsiegeln
a2a8e4ae3f feat: rename logForwardingLevel to applicationLogLevel, add agentLogLevel
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 39s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Align with cameleer3-common rename: logForwardingLevel → applicationLogLevel
(root logger) and new agentLogLevel (com.cameleer3 logger). Both fields
are on ApplicationConfig, pushed via config-update. UI shows "App Log Level"
and "Agent Log Level" on AppConfig slide-in, AgentHealth config bar, and
AppConfigDetailPage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 23:36:31 +01:00
hsiegeln
6e187ccb48 feat: native TRACE log level with design system 0.1.16
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 35s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Map TRACE to its own 'trace' level instead of grouping with DEBUG,
now that the design system LogViewer supports it natively.
Bump @cameleer/design-system to 0.1.16.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 23:07:42 +01:00
hsiegeln
862a27b0b8 feat: add TRACE log level support across UI
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 34s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Add TRACE option to log forwarding level dropdowns (AppConfig,
AgentHealth), badge color mapping, and log filter ButtonGroups
on all pages that display application logs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 23:03:15 +01:00
hsiegeln
d6c1f2c25b refactor: derive processor-route mapping from diagrams instead of executions
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 37s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Store application_name in route_diagrams at ingestion time (V7 migration),
resolve from agent registry same as ExecutionController. Move
findProcessorRouteMapping from ExecutionStore to DiagramStore using a
JSONB query that extracts node IDs directly from stored RouteGraph
definitions. This makes the mapping available as soon as diagrams are
sent, before any executions are recorded.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 23:00:10 +01:00
hsiegeln
100b780b47 refactor: remove diagramNodeId indirection, use processorId directly
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 37s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Agent now uses Camel processorId as RouteNode.id, eliminating the
nodeId mapping layer. Drop diagram_node_id column (V6 migration),
remove from ProcessorRecord/ProcessorNode/IngestionService/DetailService,
add /processor-routes endpoint for processorId→routeId lookup,
simplify frontend diagram-mapping and ExchangeDetail overlays,
replace N diagram fetches in AppConfigPage with single hook.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 22:44:07 +01:00
hsiegeln
bd63a8ce95 feat: App Config slide-in with Route column, clickable taps, and edit toolbar
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m4s
CI / docker (push) Successful in 1m19s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 27s
- Add Route column to Traces & Taps table (diagram-based mapping, pending backend fix)
- Make tap badges clickable to navigate to route's Taps tab
- Add edit/save/cancel toolbar with design system Button components
- Move Sampling Rate to last position in settings grid
- Support ?tab= URL param on RouteDetail for direct tab navigation
- Bump @cameleer/design-system to 0.1.15 (DetailPanel overlay + backdrop)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 22:26:28 +01:00
hsiegeln
ef9ec6069f fix: improve App Config slide-in panel layout
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 59s
CI / docker (push) Successful in 54s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 26s
- Narrowed panel from 640px to 520px so main table columns stay visible
- Settings grid uses CSS grid (3 columns) for proper wrapping
- Removed unused PanelActions component that caused white footer bar

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:49:03 +01:00
hsiegeln
bf84f1814f feat: convert App Config detail to slide-in DetailPanel
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m20s
CI / docker (push) Successful in 1m24s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 28s
Replaces the separate AppConfigDetailPage route with a 640px-wide
DetailPanel that slides in when clicking a row on the App Config
overview table. All editing functionality (settings, traces & taps,
route recording) is preserved inside the panel.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:44:30 +01:00
hsiegeln
00efaf0ca0 chore: bump @cameleer/design-system to 0.1.14
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m0s
CI / docker (push) Successful in 1m14s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 26s
Picks up LogViewer background fix (removes --bg-inset for consistent
card backgrounds).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:35:11 +01:00
hsiegeln
900b6f45c5 fix: use pencil and trash icons for tap row actions
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m3s
CI / docker (push) Successful in 1m25s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 27s
Replaces text "Edit"/"Del" buttons with pencil and trash can icon
buttons matching the style used elsewhere in the UI.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:32:05 +01:00
hsiegeln
dd6ea7563f feat: use Toggle switch for metrics setting on AgentHealth config bar
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 58s
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / docker (push) Has been cancelled
Replaces the plain checkbox with the design system Toggle component
for consistency with the recording toggle on RouteDetail and
AppConfigDetailPage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:30:35 +01:00
hsiegeln
57bb84a2df fix: align edit and save/cancel buttons after badges on AgentHealth
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 56s
CI / docker (push) Successful in 54s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Has been cancelled
Moved edit pencil and save/cancel actions to sit right after the last
badge field instead of at the start or far right of the config bar.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:28:30 +01:00
hsiegeln
a0fbf785c3 fix: move config edit button to right side of badges on AgentHealth
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 56s
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / docker (push) Has been cancelled
Moved the pencil edit button after the badge fields and added
margin-left: auto to push it to the far right of the config bar.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:27:01 +01:00
hsiegeln
91e51d4f6a feat: show configured taps count on Admin App Config overview
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 54s
CI / docker (push) Successful in 55s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
New Taps column shows enabled/total count as a badge (e.g. "2/3")
next to the existing Traced column.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:22:59 +01:00
hsiegeln
b52d588fc5 feat: add tooltips to tap attribute type selector buttons
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 55s
CI / docker (push) Successful in 50s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
Each type option now shows a descriptive tooltip on hover explaining
its purpose: Business Object (key identifiers), Correlation (cross-route
linking), Event (business events), Custom (general purpose).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 19:47:39 +01:00
hsiegeln
23b23bbb66 fix: replace crypto.randomUUID with fallback for non-HTTPS contexts
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 52s
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / docker (push) Has been cancelled
crypto.randomUUID() requires a secure context (HTTPS). Since the server
may be accessed via HTTP, use a timestamp + random string ID instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 19:46:32 +01:00
hsiegeln
82b47f4364 fix: use design system status tokens for test expression result alerts
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 55s
CI / docker (push) Successful in 47s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
Replaces hardcoded dark-theme hex fallbacks with proper tokens from
tokens.css: --success-bg/--success-border/--success for success and
--error-bg/--error-border/--error for errors. Works in both themes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 19:38:24 +01:00
hsiegeln
e4b2dd2604 fix: use design system tokens for tap type selector active state
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 56s
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / docker (push) Has been cancelled
The active type option was invisible because --accent-primary doesn't
exist in the design system. Now uses --amber-bg/--amber-deep/--amber
from tokens.css for a clearly visible selected state matching the
brand accent palette.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 19:37:12 +01:00
hsiegeln
3b31e69ae4 chore: regenerate openapi.json and schema.d.ts from live server
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 54s
CI / docker (push) Successful in 48s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
Updated types now include attributes on ExecutionDetail, ProcessorNode,
and ExecutionSummary from the actual API. Removed stale detail.children
fallback that no longer exists in the schema.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 19:22:55 +01:00
hsiegeln
499fd7f8e8 fix: accept ISO datetime for audit log from/to parameters
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 54s
CI / docker (push) Successful in 37s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
The frontend sends full ISO timestamps (e.g. 2026-03-19T17:55:29Z) but
the controller expected LocalDate (yyyy-MM-dd). This caused null parsing,
which threw NullPointerException in the repository WHERE clause. Changed
to accept Instant directly with sensible defaults (last 7 days).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 19:07:09 +01:00
hsiegeln
1080c76e99 feat: wire attributes from RouteExecution/ProcessorExecution into storage
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 54s
CI / docker (push) Successful in 36s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
Replaces null placeholders with actual getAttributes() calls now that
cameleer3-common SNAPSHOT is resolved with attributes support.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 19:03:18 +01:00
hsiegeln
7f58bca0e6 chore: update IngestionService TODO comments for attributes wiring
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 59s
CI / docker (push) Successful in 50s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 18:59:17 +01:00
hsiegeln
c087e4af08 fix: add missing attributes parameter to test record constructors
Tests were not updated when attributes field was added to ExecutionRecord,
ProcessorRecord, ProcessorDoc, and ExecutionDocument records.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 18:58:44 +01:00
hsiegeln
387ed44989 fix: add missing attributes parameter to test record constructors
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 18:58:32 +01:00
hsiegeln
64b677696e feat(ui): restructure AppConfigDetailPage into 3 sections
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 32s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Merge Logging + Observability into unified "Settings" section with
flex-wrap badge grid including new compressSuccess toggle. Merge
Traced Processors with Taps into "Traces & Taps" section showing
capture mode and tap badges per processor. Add "Route Recording"
section with per-route toggles sourced from route catalog. All new
fields (compressSuccess, routeRecording) included in form state
and save payload.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 18:48:14 +01:00
hsiegeln
78813ea15f feat(ui): add taps DataTable, CRUD modal with test expression to RouteDetail
- Replace taps tab placeholder with full DataTable showing all route taps
- Add columns: attribute, processor, expression, language, target, type, enabled toggle, actions
- Add tap modal with form fields: attribute name, processor select, language, target, expression, type selector
- Implement inline enable/disable toggle per tap row
- Add ConfirmDialog for tap deletion
- Add test expression section with Recent Exchange and Custom Payload tabs
- Add save/edit/delete tap operations via application config update
- Add all supporting CSS module classes (no inline styles)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 18:44:36 +01:00
hsiegeln
807e191397 feat(ui): add recording toggle, active taps KPI, and taps tab to RouteDetail
- Add Toggle for route recording on/off in the route header
- Fetch application config to determine recording state and route taps
- Add Active Taps KPI card showing enabled/total tap counts
- Add Taps tab to the tabbed section with placeholder content

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 18:44:06 +01:00
hsiegeln
47ff122c48 feat: add Attributes column to Dashboard exchanges table
Shows up to 2 attribute badges (color="auto") per row with a +N overflow
indicator; empty rows render a muted dash. Uses CSS module classes only.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 18:36:53 +01:00
hsiegeln
eb796f531f feat(ui): add replay modal to ExchangeDetail page
Add a Replay button in the exchange header that opens a modal allowing
users to re-send the exchange to a live agent. The modal pre-populates
headers and body from the original exchange input, provides an agent
selector filtered to live agents for the application, and supports
editable header key-value rows with add/remove.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 18:35:00 +01:00
hsiegeln
a3706cf7c2 feat(ui): display business attributes on ExchangeDetail page
Show route-level attributes as Badge strips in the exchange header
card, and per-processor attributes above the message IN/OUT panels.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 18:33:16 +01:00
hsiegeln
2b1d49c032 feat: add TapDefinition, extend ApplicationConfig, and add API hooks
- Add TapDefinition interface for tap configuration
- Extend ApplicationConfig with taps, tapVersion, routeRecording, compressSuccess
- Add useTestExpression mutation hook (manual fetch to new endpoint)
- Add useReplayExchange mutation hook (uses api client, targets single agent)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 18:29:52 +01:00
hsiegeln
ae1ee38441 feat: add attributes fields to schema.d.ts types
Add optional `attributes?: Record<string, string>` to ExecutionSummary,
ExecutionDetail, and ProcessorNode in the manually-maintained OpenAPI
schema to reflect the new backend attributes support.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 18:29:47 +01:00
hsiegeln
d6d96aad07 feat: add TEST_EXPRESSION command with request-reply infrastructure
Adds CompletableFuture-based request-reply mechanism for commands that
need synchronous results. CommandReply record in core, pendingReplies
map in AgentRegistryService, test-expression endpoint on config controller
with 5s timeout. CommandAckRequest extended with optional data field.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 18:27:59 +01:00
hsiegeln
2d6cc4c634 feat(search): deserialize and surface attributes in detail service and OpenSearch indexing (Task 4)
DetailService deserializes attributes JSON from ExecutionRecord/ProcessorRecord and
passes them to ExecutionDetail and ProcessorNode constructors. ExecutionDocument and
ProcessorDoc carry attributes as a JSON string. SearchIndexer passes attributes when
building documents. OpenSearchIndex includes attributes in indexed maps and
deserializes them when constructing ExecutionSummary from search hits.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 18:23:47 +01:00
hsiegeln
ca5250c134 feat(ingestion): wire attributes through ingestion pipeline into PostgreSQL (Task 3)
IngestionService passes attributes (currently null, pending cameleer3-common update)
to ExecutionRecord and ProcessorRecord. PostgresExecutionStore includes the
attributes column in INSERT and ON CONFLICT UPDATE (with COALESCE), and reads
it back in both row mappers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 18:23:38 +01:00
hsiegeln
64f797bd96 feat(core): add attributes field to storage records and detail/summary models (Task 2)
Adds Map<String,String> attributes to ExecutionRecord, ProcessorRecord,
ExecutionDetail, ProcessorNode, and ExecutionSummary. ExecutionStore records
carry attributes as a JSON string; detail/summary models carry deserialized maps.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 18:23:32 +01:00
hsiegeln
f08461cf35 feat(db): add attributes JSONB columns to executions and processor_executions (Task 1)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 18:23:26 +01:00
hsiegeln
2b5d803a60 docs: add implementation plan for taps, attributes, replay UI features
14-task plan covering: database migration, attributes pipeline, test-expression
command with request-reply, OpenAPI regeneration, frontend types/hooks,
ExchangeDetail attributes + replay modal, Dashboard attributes column,
RouteDetail recording toggle + taps tab + tap CRUD modal, and
AppConfigDetailPage restructure.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 18:13:58 +01:00
hsiegeln
e3902cd85f docs: add UI design spec for taps, attributes, replay, recording & compression
Covers all 5 new agent features: tap management on RouteDetail, business
attributes display on ExchangeDetail/Dashboard, enhanced replay with
editable payload, per-route recording toggles, and success compression.
Includes backend prerequisites, RBAC matrix, and TypeScript interfaces.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 17:48:20 +01:00
hsiegeln
25ca8d5132 feat: show log indices on OpenSearch admin page
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 54s
CI / docker (push) Successful in 47s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 35s
Add prefix query parameter to /admin/opensearch/indices endpoint so
the UI can fetch execution and log indices separately. OpenSearch admin
page now shows two card sections: Execution Indices and Log Indices,
each with doc count and size summary. Page restyled with CSS module
replacing inline styles. Delete endpoint also allows log index deletion.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 16:47:44 +01:00
hsiegeln
0d94132c98 feat: SOC2 audit log completeness — hybrid interceptor + explicit calls
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 54s
CI / docker (push) Successful in 51s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Add AuditInterceptor as a safety net that auto-audits any POST/PUT/DELETE
without an explicit audit call (excludes data ingestion + heartbeat).
AuditService sets a request attribute so the interceptor skips when
explicit logging already happened.

New explicit audit calls:
- ApplicationConfigController: view/update app config
- AgentCommandController: send/broadcast commands (AGENT category)
- AgentRegistrationController: agent register + token refresh
- UiAuthController: UI token refresh
- OidcAuthController: OIDC callback failure
- AuditLogController: view audit log (sensitive read)
- UserAdminController: view users (sensitive read)
- OidcConfigAdminController: view OIDC config (sensitive read)

New AuditCategory.AGENT added. Frontend audit log filter updated.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 16:41:10 +01:00
hsiegeln
0e6de69cd9 feat: add App Config detail page with view/edit mode
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 53s
CI / docker (push) Successful in 52s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s
Click a row in the admin App Config table to navigate to a dedicated
detail page at /admin/appconfig/:appId. Shows all config fields as
badges in view mode; pencil toggles to edit mode with dropdowns.

Traced processors are now editable (capture mode dropdown + remove
button per processor). Sections and header use card styling for
visual contrast. OidcConfigPage gets the same card treatment.

List page simplified to read-only badge overview with row click
navigation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 16:15:27 +01:00
hsiegeln
e53274bcb9 fix: LogViewer and EventFeed scroll to top on load
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 56s
CI / docker (push) Successful in 1m9s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 35s
Update design system to v0.1.13 where both components scroll to the
top (newest entries) instead of the bottom, matching the descending
sort order used across the UI.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:54:56 +01:00
hsiegeln
4433b26bf8 fix: move pencil/save buttons to start of config bar for consistency
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 57s
CI / docker (push) Successful in 50s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 35s
Pencil icon and Save/Cancel buttons now appear at the left side of
the AgentHealth config bar, matching the admin overview table where
the edit column is at the start of each row.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:38:36 +01:00
hsiegeln
74fa08f41f fix: visible Save/Cancel buttons on AgentHealth config edit mode
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 56s
CI / docker (push) Successful in 52s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
Replace subtle Unicode checkmark/X with proper labeled buttons styled
as primary (Save) and secondary (Cancel) for better visibility.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 13:20:11 +01:00
hsiegeln
4b66d78cf4 refactor: config settings shown as badges with pencil-to-edit
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 56s
CI / docker (push) Successful in 47s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
Settings (log level, engine level, payload capture, metrics) now
display as color-coded badges by default. Clicking the pencil icon
enters edit mode where badges become dropdowns. Save (checkmark)
persists changes and reverts to badge view; cancel discards changes.

Applied consistently on both the admin App Config page and the
AgentHealth config bar.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 13:12:56 +01:00
hsiegeln
b1c2950b1e fix: add id field to AppConfigPage DataTable rows
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 2m51s
CI / docker (push) Successful in 1m9s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 35s
DataTable requires rows with an { id: string } constraint. Map
ApplicationConfig to ConfigRow adding id from the application field.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 12:55:19 +01:00
hsiegeln
b0484459a2 feat: add application config overview and inline editing
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 22s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Add admin page at /admin/appconfig with a DataTable showing all
application configurations. Inline dropdowns allow editing log level,
engine level, payload capture mode, and metrics toggle directly from
the table. Changes push to agents via SSE immediately.

Also adds a config bar on the AgentHealth page (/agents/:appId) for
per-application config management with the same 4 settings.

Backend: GET /api/v1/config list endpoint, findAll() on repository,
sensible defaults for logForwardingLevel/engineLevel/payloadCaptureMode.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 12:51:07 +01:00
hsiegeln
056a6f0ff5 feat: sidebar exchange counts respect selected time range
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 2m47s
CI / docker (push) Successful in 48s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s
The /routes/catalog endpoint now accepts optional from/to query
parameters instead of hardcoding a 24h window. The UI passes the
global filter time range so sidebar counts match what the user sees.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 12:21:10 +01:00
hsiegeln
f4bf38fcba feat: add inspect column to agent instance data table
All checks were successful
CI / build (push) Successful in 58s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 58s
CI / deploy (push) Successful in 35s
CI / deploy-feature (push) Has been skipped
Add a dedicated inspect button column (↗) to navigate to the agent
instance page, consistent with the exchange inspect pattern on the
Dashboard. Row click still opens the detail slide-in panel.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 12:04:06 +01:00
hsiegeln
15632a2170 fix: show full exchange ID in breadcrumb
All checks were successful
CI / build (push) Successful in 53s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 47s
CI / deploy (push) Successful in 35s
CI / deploy-feature (push) Has been skipped
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:49:41 +01:00
hsiegeln
479b67cd2d refactor: consolidate breadcrumbs to single TopBar instance
All checks were successful
CI / build (push) Successful in 1m1s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 1m11s
CI / deploy (push) Successful in 35s
CI / deploy-feature (push) Has been skipped
Remove duplicate in-page breadcrumbs (ExchangeDetail, AgentHealth scope
trail) and improve the global TopBar breadcrumb with semantic labels and
a context-based override for pages with richer navigation data.

- Add BreadcrumbProvider from design system v0.1.12
- LayoutShell: label map prettifies URL segments (apps→Applications, etc.)
- ExchangeDetail: uses useBreadcrumb() to set semantic trail via context
- AgentHealth: remove scope trail, keep live-count badge standalone

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:40:37 +01:00
hsiegeln
bde0459416 fix: prevent log viewer flicker on ExchangeDetail page
All checks were successful
CI / build (push) Successful in 1m0s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 1m12s
CI / deploy (push) Successful in 35s
CI / deploy-feature (push) Has been skipped
Skip global time range in the logs query key when filtering by
exchangeId (exchange logs are historical, the sliding time window is
irrelevant). Add placeholderData to keep previous results visible
during query key transitions on other pages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:03:38 +01:00
hsiegeln
a01712e68c fix: use .keyword suffix on both exchangeId term queries
All checks were successful
CI / build (push) Successful in 1m1s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 41s
CI / deploy (push) Successful in 36s
CI / deploy-feature (push) Has been skipped
Defensive: use .keyword on the top-level exchangeId field too, in
case indices were created before the explicit keyword mapping was
added to the template.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 10:45:59 +01:00
hsiegeln
9aa78f681d fix: use .keyword suffix for MDC exchangeId term query
Some checks failed
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / cleanup-branch (push) Has been cancelled
CI / build (push) Has been cancelled
Dynamically mapped string fields in OpenSearch are multi-field
(text + keyword). Term queries require the .keyword sub-field for
exact matching.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 10:45:14 +01:00
hsiegeln
befefe457f fix: query both top-level and MDC exchangeId for log search
All checks were successful
CI / build (push) Successful in 1m1s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 49s
CI / deploy (push) Successful in 39s
CI / deploy-feature (push) Has been skipped
Existing log records only have exchangeId inside the mdc object, not
as a top-level indexed field. Use a bool should clause to match on
either exchangeId (new records) or mdc.camel.exchangeId (old records).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 10:40:42 +01:00
hsiegeln
ea665ff411 feat: exchange-level log viewer on ExchangeDetail page
All checks were successful
CI / build (push) Successful in 1m0s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 49s
CI / deploy (push) Successful in 37s
CI / deploy-feature (push) Has been skipped
Index exchangeId from Camel MDC (camel.exchangeId) as a top-level
keyword field in OpenSearch log indices. Add exchangeId filter to
the log query API and frontend hook. Show a LogViewer on the
ExchangeDetail page filtered to that exchange's logs, with search
input and level filter pills.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 10:26:30 +01:00
hsiegeln
f9bd492191 chore: update design system to v0.1.11 (live time range fix)
All checks were successful
CI / build (push) Successful in 56s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 1m9s
CI / deploy (push) Successful in 39s
CI / deploy-feature (push) Has been skipped
The GlobalFilterProvider now recomputes the preset time range every
10s when auto-refresh is on, so timeRange.end stays fresh instead of
being frozen at the moment the preset was clicked.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 09:57:43 +01:00
hsiegeln
1be303b801 feat: add application log panel to agent health page
All checks were successful
CI / build (push) Successful in 55s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 48s
CI / deploy (push) Successful in 37s
CI / deploy-feature (push) Has been skipped
Add the same log + timeline side-by-side layout from AgentInstance to
the AgentHealth page (/agents/{appId}). Includes search input, level
filter pills, sort toggle, and refresh button — matching the instance
page design exactly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 22:54:07 +01:00
hsiegeln
d57249906a fix: refresh buttons use "now" as to-date for queries
All checks were successful
CI / build (push) Successful in 56s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 47s
CI / deploy (push) Successful in 41s
CI / deploy-feature (push) Has been skipped
Instead of calling refetch() with stale time params, the refresh
buttons now set a toOverride state to new Date().toISOString(). This
flows into the query key, triggering a fresh fetch with the current
time as the upper bound. Both useApplicationLogs and useAgentEvents
hooks accept an optional toOverride parameter.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 22:41:00 +01:00
hsiegeln
6a24dd01e9 fix: add exchange body fields to schema.d.ts for CI tsc check
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 54s
CI / docker (push) Successful in 9s
CI / deploy (push) Successful in 19s
CI / deploy-feature (push) Has been skipped
The CI build runs tsc --noEmit which failed because the ExecutionDetail
type in schema.d.ts was missing the new inputBody/outputBody/inputHeaders/
outputHeaders fields added to the backend DTO.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 22:06:26 +01:00
hsiegeln
e10f021c54 use self hosted image for build
Some checks failed
CI / build (push) Failing after 26s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
2026-03-25 22:03:19 +01:00
hsiegeln
b3c5e87230 fix: expose exchange body in API, fix RouteFlow index mapping
Some checks failed
CI / build (push) Failing after 25s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Add inputBody/outputBody/inputHeaders/outputHeaders to ExecutionDetail
DTO so exchange-level bodies are returned by the detail endpoint. Show
"Exchange Input" and "Exchange Output" panels on the detail page when
the data is available.

Fix RouteFlow node click selecting the wrong processor snapshot by
building a flowToTreeIndex mapping that correctly translates flow
display index → diagram node index → processorId → processor tree
index. Previously the diagram node index was used directly as the
processor tree index, which broke when the two orderings differed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 22:02:26 +01:00
hsiegeln
9b63443842 feat: add sort toggle and refresh buttons to log/timeline panels
All checks were successful
CI / build (push) Successful in 55s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 50s
CI / deploy (push) Successful in 42s
CI / deploy-feature (push) Has been skipped
Remove auto-scroll override hack. Add sort order toggle (asc/desc
by time) and manual refresh button to both the application log and
agent events timeline panels on AgentInstance and AgentHealth pages.
Default is descending (newest first); toggling reverses the array.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 19:53:33 +01:00
hsiegeln
cd30c2d9b5 fix: match log/timeline height, DESC sort with scroll-to-top
All checks were successful
CI / build (push) Successful in 55s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 52s
CI / deploy (push) Successful in 39s
CI / deploy-feature (push) Has been skipped
Give logCard the same max-height and flex layout as timelineCard so
both columns are equal height. Revert .toReversed() so events stay
in DESC order (newest at top). Override EventFeed's auto-scroll-to-
bottom with a requestAnimationFrame that resets scrollTop to 0 after
mount, keeping newest entries visible at the top of both panels.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 19:12:08 +01:00
hsiegeln
b612941aae feat: wire up application logs from OpenSearch, fix event autoscroll
All checks were successful
CI / build (push) Successful in 55s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 51s
CI / deploy (push) Successful in 37s
CI / deploy-feature (push) Has been skipped
Add GET /api/v1/logs endpoint to query application logs stored in
OpenSearch with filters for application, agent, level, time range,
and text search. Wire up the AgentInstance LogViewer with real data
and an EventFeed-style toolbar (search input + level filter pills).

Fix agent events timeline autoscroll by reversing the DESC-ordered
events so newest entries appear at the bottom where EventFeed
autoscrolls to.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 18:56:13 +01:00
hsiegeln
20ee448f4e fix: OpenSearch status field mismatch, adopt RouteFlow flows prop
All checks were successful
CI / build (push) Successful in 56s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 1m43s
CI / deploy (push) Successful in 38s
CI / deploy-feature (push) Has been skipped
Fix admin OpenSearch page always showing "Disconnected" by aligning
frontend field names (reachable/nodeCount/host) with backend DTO.

Update design system to v0.1.10 and adopt the new multi-flow RouteFlow
API — error-handler nodes now render as labeled segments with error
variant instead of relying on legacy auto-separation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 18:34:58 +01:00
hsiegeln
2bbca8ae38 fix: force SNAPSHOT update in Docker build (-U flag)
All checks were successful
CI / build (push) Successful in 55s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 40s
CI / deploy (push) Successful in 38s
CI / deploy-feature (push) Has been skipped
Same issue as the CI build — Docker layer cache can serve a stale
cameleer3-common SNAPSHOT.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 13:36:07 +01:00
hsiegeln
fea50b51ae fix: force SNAPSHOT update in CI build (-U flag)
Some checks failed
CI / build (push) Successful in 55s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Failing after 23s
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Maven cache can serve stale cameleer3-common SNAPSHOTs. The -U flag
forces Maven to check the remote registry for updated SNAPSHOTs on
every build.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 13:33:59 +01:00
79d37118e0 chore: use pre-baked build images from cameleer-build-images
Some checks failed
CI / build (push) Failing after 40s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Replace maven:3.9-eclipse-temurin-17 with cameleer-build:1 (includes
Node.js 22, curl, jq). Replace docker:27 with cameleer-docker-builder:1
(includes git, curl, jq). Removes per-build tool installation steps.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 13:26:11 +01:00
hsiegeln
7fd55ea8ba fix: remove core LogIndexService to fix CI snapshot resolution
Some checks failed
CI / build (push) Failing after 1m11s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
LogIndexService in server-core imported LogEntry from cameleer3-common,
but the SNAPSHOT on the registry may not have it yet when the server CI
runs. Moved the dependency to server-app where both the controller and
OpenSearch implementation live.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 13:11:11 +01:00
hsiegeln
c96fbef5d5 ci: retry after cameleer3-common publish
Some checks failed
CI / build (push) Failing after 50s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 13:05:23 +01:00
hsiegeln
7423e2ca14 feat: add application log ingestion with OpenSearch storage
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 59s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Agents can now send application log entries in batches via POST /api/v1/data/logs.
Logs are indexed directly into OpenSearch daily indices (logs-{yyyy-MM-dd}) using
the bulk API. Index template defines explicit mappings for full-text search readiness.

New DTOs (LogEntry, LogBatch) added to cameleer3-common in the agent repo.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 11:53:27 +01:00
hsiegeln
bf600f8c5f fix: read version and updated_at from SQL columns in config repository
All checks were successful
CI / build (push) Successful in 12m13s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 44s
CI / deploy (push) Successful in 39s
CI / deploy-feature (push) Has been skipped
The findByApplication query only read config_val JSONB, ignoring the
version and updated_at SQL columns. The JSON blob contained version 0
from the original save, so agents saw no config and fell back to defaults.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 10:22:13 +01:00
hsiegeln
996ea65293 feat: LIVE/PAUSED toggle controls data fetching on sidebar navigation
All checks were successful
CI / build (push) Successful in 1m13s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 55s
CI / deploy (push) Successful in 39s
CI / deploy-feature (push) Has been skipped
LIVE: sidebar clicks trigger initial fetch + polling for the new route.
PAUSED: sidebar clicks navigate but queries are disabled — no fetches
until the user switches back to LIVE.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 10:01:14 +01:00
hsiegeln
9866dd5f23 fix: move design system dev install after COPY to bust Docker cache
All checks were successful
CI / build (push) Successful in 1m23s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 1m12s
CI / deploy (push) Successful in 38s
CI / deploy-feature (push) Has been skipped
The npm install @cameleer/design-system@dev was in the same cached layer
as npm ci, so Docker never re-ran it when the registry had a new version.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 09:37:51 +01:00
hsiegeln
d9c8816647 feat: add OpenSearch highlight snippets to search results
All checks were successful
CI / build (push) Successful in 1m23s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 54s
CI / deploy (push) Successful in 39s
CI / deploy-feature (push) Has been skipped
- Add highlight field to ExecutionSummary record
- Request highlight fragments from OpenSearch when full-text search is active
- Pass matchContext to command palette for display

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 09:29:07 +01:00
hsiegeln
b32c97c02b feat: fix Cmd-K shortcut and add exchange full-text search to command palette
All checks were successful
CI / build (push) Successful in 1m43s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 1m17s
CI / deploy (push) Successful in 40s
CI / deploy-feature (push) Has been skipped
- Add missing onOpen prop to CommandPalette (fixes Ctrl+K/Cmd+K)
- Wire server-side exchange search with debounced text query
- Use design system dev snapshot from Gitea registry in CI builds

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 08:57:24 +01:00
hsiegeln
552f02d25c fix: add JWT auth to application config API calls
All checks were successful
CI / build (push) Successful in 1m42s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 57s
CI / deploy (push) Successful in 39s
CI / deploy-feature (push) Has been skipped
Raw fetch() had no auth headers, causing 401s that silently broke tracing toggle.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 08:19:44 +01:00
hsiegeln
9f9968abab chore: upgrade cameleer3-common to 1.0-SNAPSHOT and enable snapshot resolution
All checks were successful
CI / build (push) Successful in 1m44s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 3m27s
CI / deploy (push) Successful in 39s
CI / deploy-feature (push) Has been skipped
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 08:04:29 +01:00
hsiegeln
69a3eb192f feat: persistent per-application config with GET/PUT endpoints
Some checks failed
CI / build (push) Failing after 1m10s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Add application_config table (V4 migration), repository, and REST
controller. GET /api/v1/config/{app} returns config, PUT saves and
pushes CONFIG_UPDATE to all LIVE agents via SSE. UI tracing toggle
now uses config API instead of direct SET_TRACED_PROCESSORS command.
Tracing store syncs with server config on load.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 07:42:55 +01:00
hsiegeln
488a32f319 feat: show tracing badges on processor nodes
All checks were successful
CI / build (push) Successful in 1m18s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 1m12s
CI / deploy (push) Successful in 40s
CI / deploy-feature (push) Has been skipped
Update design system to 0.1.8 and pass NodeBadge[] to both
ProcessorTimeline and RouteFlow. Traced processors display a
blue "TRACED" badge that updates reactively via Zustand store.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 23:10:37 +01:00
hsiegeln
bf57fd139b fix: show tracing action on all Flow view nodes
All checks were successful
CI / build (push) Successful in 1m26s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 53s
CI / deploy (push) Successful in 39s
CI / deploy-feature (push) Has been skipped
Use diagram node ID as fallback processorId when no processor
execution match exists (e.g. error handlers that didn't trigger).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 22:46:52 +01:00
hsiegeln
581d53a33e fix: match SET_TRACED_PROCESSORS payload to agent protocol
Some checks failed
CI / build (push) Successful in 1m28s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 55s
CI / deploy-feature (push) Has been cancelled
CI / deploy (push) Has been cancelled
Payload now sends {processors: {id: "BOTH"}} map instead of
{routeId, processorIds[]} array. Tracing state keyed by application
name (global, not per-route) matching agent behavior.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 22:43:55 +01:00
hsiegeln
f4dd2b3415 feat: add processor tracing toggle to exchange detail views
All checks were successful
CI / build (push) Successful in 1m22s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 52s
CI / deploy (push) Successful in 39s
CI / deploy-feature (push) Has been skipped
Wire getActions on ProcessorTimeline and RouteFlow to send
SET_TRACED_PROCESSORS commands to all agents of the same application.
Tracing state managed via Zustand store with optimistic UI and rollback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 22:30:26 +01:00
hsiegeln
7532cc9d59 chore: update @cameleer/design-system to 0.1.7
All checks were successful
CI / build (push) Successful in 1m14s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 1m8s
CI / deploy (push) Successful in 39s
CI / deploy-feature (push) Has been skipped
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 21:59:40 +01:00
hsiegeln
e7590d72fd fix: restore Swagger UI on api-docs page
All checks were successful
CI / build (push) Successful in 1m23s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 50s
CI / deploy (push) Successful in 38s
CI / deploy-feature (push) Has been skipped
- Change Vite proxy pattern from /api to /api/ so /api-docs client
  route is not captured and proxied to the backend
- Fix SwaggerUIBundle init: remove empty presets/layout overrides that
  crashed the internal persistConfigs function
- Use correct CSS import (swagger-ui.css instead of index.css)
- Add requestInterceptor to auto-attach JWT token to Try-it-out calls
- Add swagger-ui-bundle to optimizeDeps.include for reliable loading
- Remove unused swagger-ui-dist.d.ts type declarations

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 20:53:48 +01:00
hsiegeln
57ce1db248 add metrics ingestion diagnostics and upgrade cameleer3-common to 0.0.3
All checks were successful
CI / build (push) Successful in 1m34s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 3m20s
CI / deploy (push) Successful in 39s
CI / deploy-feature (push) Has been skipped
- Add logging to MetricsController: warn on parse failures, debug on
  received metrics, buffer depth on 503
- Add GET /api/v1/admin/database/metrics-pipeline diagnostic endpoint
  (buffer depth, row count, distinct agents/metrics, latest timestamp)
- Fix BackpressureIT test JSON to match actual MetricsSnapshot schema
  (collectedAt/metricName/metricValue instead of timestamp/metrics)
- Upgrade cameleer3-common from 1.0-SNAPSHOT to 0.0.3 (adds engineLevel)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 20:23:26 +01:00
hsiegeln
c97d730a00 fix: show N/A for agent heap/CPU when no JVM metrics available
All checks were successful
CI / build (push) Successful in 1m22s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 55s
CI / deploy (push) Successful in 39s
CI / deploy-feature (push) Has been skipped
Indeterminate progress bars were misleading when agents don't report
JVM metrics — replaced with plain "N/A" text.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 19:46:58 +01:00
hsiegeln
581c4f9ad9 fix: restore registry URL in package-lock.json for CI
All checks were successful
CI / build (push) Successful in 1m16s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 1m12s
CI / deploy (push) Successful in 39s
CI / deploy-feature (push) Has been skipped
The lock file had "resolved": "../../design-system" from a local
install, causing npm ci in CI to silently skip the package.
Reinstalled from registry to fix the resolved URL.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 19:15:44 +01:00
hsiegeln
ef6bc4be21 fix: add npm registry auth token for UI build in CI
Some checks failed
CI / build (push) Failing after 39s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
The Build UI step ran npm ci without authenticating to the Gitea npm
registry, causing @cameleer/design-system to fail to resolve. Add
REGISTRY_TOKEN to .npmrc before npm ci.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 19:12:35 +01:00
hsiegeln
8534bb8839 chore: upgrade @cameleer/design-system to v0.1.6
Some checks failed
CI / build (push) Failing after 39s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 19:07:13 +01:00
hsiegeln
a5bc7cf6d1 fix: use self-portaling DetailPanel from design system v0.1.5
Some checks failed
CI / build (push) Failing after 57s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
DetailPanel now portals itself to #cameleer-detail-panel-root (a div
AppShell places as a sibling of .main in the top-level flex row).
Pages just render <DetailPanel> inline — no manual createPortal,
no context, no prop drilling.

Remove the old #detail-panel-portal div from LayoutShell and the
createPortal wrappers from Dashboard and AgentHealth.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 19:00:02 +01:00
hsiegeln
5d2eff4f73 fix: normalize null fields from unconfigured OIDC response
All checks were successful
CI / build (push) Successful in 1m16s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 53s
CI / deploy (push) Successful in 40s
CI / deploy-feature (push) Has been skipped
When no OIDC config exists, the backend returns an object with all
null fields (via OidcAdminConfigResponse.unconfigured()). Normalize
all null values to sensible defaults when loading the form instead
of passing nulls through to Input components and .map() calls.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 18:44:02 +01:00
hsiegeln
9a4a4dc1af fix: handle null defaultRoles in OIDC config page
Some checks failed
CI / build (push) Has been cancelled
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / cleanup-branch (push) Has been cancelled
The API returns defaultRoles as null when no roles are configured.
Add null guards on all defaultRoles accesses to prevent .map() crash.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 18:41:59 +01:00
hsiegeln
f3241e904f fix: use createPortal for DetailPanel instead of context+useEffect
Some checks failed
CI / build (push) Successful in 1m21s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 53s
CI / deploy-feature (push) Has been cancelled
CI / deploy (push) Has been cancelled
The previous approach used useEffect+context to hoist DetailPanel
content to the AppShell level, but the dependency-free useEffect
caused a re-render loop that broke sidebar navigation.

Replace with createPortal: pages render DetailPanel inline in their
JSX but portal it to a target div (#detail-panel-portal) at the
AppShell level. No state lifting, no re-render loops.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 18:38:59 +01:00
hsiegeln
5de792744e fix: hoist DetailPanel into AppShell detail slot for proper slide-in
All checks were successful
CI / build (push) Successful in 1m22s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 51s
CI / deploy (push) Successful in 38s
CI / deploy-feature (push) Has been skipped
DetailPanel is a flex sibling that slides in from the right — it must
be rendered at the AppShell level via the detail prop, not inside the
page content. Add DetailPanelContext so pages can push their panel
content up to LayoutShell, which passes it to AppShell.detail.

Applied to Dashboard (exchange detail) and AgentHealth (instance detail).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 18:28:03 +01:00
hsiegeln
0a5f4a03b5 chore: upgrade @cameleer/design-system to v0.1.4
All checks were successful
CI / build (push) Successful in 1m13s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 1m11s
CI / deploy (push) Successful in 37s
CI / deploy-feature (push) Has been skipped
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 18:18:20 +01:00
hsiegeln
4ac11551c9 feat: add auto-refresh toggle wired to all polling queries
Some checks failed
CI / build (push) Failing after 51s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Upgrade @cameleer/design-system to ^0.1.3 which adds LIVE/PAUSED
toggle to TopBar backed by autoRefresh state in GlobalFilterProvider.

Add useRefreshInterval() hook that returns the polling interval when
auto-refresh is on, or false when paused. Wire it into all query
hooks that use refetchInterval (executions, catalog, agents, metrics,
admin database/opensearch).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 18:10:32 +01:00
hsiegeln
6fea5f2c5b fix: use .keyword suffix for text field sorting in OpenSearch
All checks were successful
CI / build (push) Successful in 1m22s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 44s
CI / deploy (push) Successful in 39s
CI / deploy-feature (push) Has been skipped
OpenSearch dynamically maps string fields as text with a .keyword
subfield. Sorting on text fields throws an error; only .keyword,
date, and numeric fields support sorting. Add .keyword suffix to
all string sort columns (status, routeId, agentId, executionId,
correlationId, applicationName) while keeping start_time and
duration_ms as-is.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 17:56:18 +01:00
hsiegeln
b7cac68ee1 fix: filter exchanges by application and restore snake_case sort columns
All checks were successful
CI / build (push) Successful in 1m23s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 41s
CI / deploy (push) Successful in 39s
CI / deploy-feature (push) Has been skipped
Add application_name filter to OpenSearch query builder — sidebar
app selection now correctly filters the exchange list. The
application field was being resolved to agentIds in the controller
but never applied as a query filter in OpenSearch.

Also restore snake_case sort column mapping since the OpenSearch
toMap() serializer uses snake_case field names (start_time, route_id,
etc.), not camelCase.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 17:41:07 +01:00
hsiegeln
cdbe330c47 fix: support all sortable columns and use camelCase for OpenSearch
All checks were successful
CI / build (push) Successful in 1m24s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 45s
CI / deploy (push) Successful in 37s
CI / deploy-feature (push) Has been skipped
Add executionId and applicationName to allowed sort fields. Fix sort
column mapping to use camelCase field names matching the OpenSearch
ExecutionDocument fields instead of snake_case DB column names. This
was causing sorts on most columns to either silently fall back to
startTime or return empty results from OpenSearch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 17:37:01 +01:00
53e9073dca fix: update ExecutionRecord constructor in stats test for new fields
All checks were successful
CI / build (push) Successful in 1m13s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 1m9s
CI / deploy (push) Successful in 38s
CI / deploy-feature (push) Has been skipped
2026-03-24 17:26:07 +01:00
b8c316727e fix: update ExecutionRecord constructor calls in tests for new fields
Some checks failed
CI / build (push) Has started running
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / cleanup-branch (push) Has been cancelled
2026-03-24 17:25:48 +01:00
hsiegeln
48455cd559 fix: use server-side sorting for paginated tables
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 1m10s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Upgrade @cameleer/design-system to v0.1.1 which adds onSortChange
callback to DataTable. Wire it up in Dashboard (exchanges), AuditLog,
and RouteDetail (recent executions) so sorting triggers a new API
request with sortField/sortDir instead of only sorting the current page.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 17:05:17 +01:00
aa3d9f375b Merge pull request 'feat: agent protocol v2 — engine levels, enriched acks, route snapshots' (#91) from fix/agent-protocol-v2 into main
Some checks failed
CI / build (push) Failing after 1m0s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
Reviewed-on: cameleer/cameleer3-server#91
2026-03-24 16:50:09 +01:00
hsiegeln
e54d20bcb7 feat: migrate login page to design system styling
All checks were successful
CI / build (push) Successful in 1m26s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 57s
CI / deploy (push) Successful in 38s
CI / deploy-feature (push) Has been skipped
Replace inline styles with CSS module matching the design system's
LoginForm visual patterns. Uses proper DS class structure (divider,
social section, form fields) while keeping username-based auth
instead of the DS component's email validation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 16:44:52 +01:00
hsiegeln
81f85aa82d feat: replace UI with design system example pages wired to real API
Some checks failed
CI / build (push) Successful in 1m18s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 55s
CI / deploy-feature (push) Has been cancelled
CI / deploy (push) Has been cancelled
Migrate all page components from the @cameleer/design-system v0.0.3
example UI, replacing mock data with real backend API hooks. This brings
richer visuals (KpiStrip, GroupCard, RouteFlow, ProcessorTimeline,
DateRangePicker, expandable rows) while preserving all existing API
integration, auth, and routing infrastructure.

Pages migrated: Dashboard, RoutesMetrics, RouteDetail, ExchangeDetail,
AgentHealth, AgentInstance, OidcConfig, AuditLog, RBAC (Users/Groups/Roles).
Also enhanced LayoutShell CommandPalette with real search data from catalog.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 16:42:16 +01:00
2887fe9599 feat: add V3 migration for engine_level and route-level snapshot columns
Some checks failed
CI / build (push) Failing after 51s
CI / cleanup-branch (push) Has been skipped
CI / build (pull_request) Failing after 52s
CI / cleanup-branch (pull_request) Has been skipped
CI / docker (push) Has been skipped
CI / docker (pull_request) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
CI / deploy (pull_request) Has been skipped
CI / deploy-feature (pull_request) Has been skipped
2026-03-24 16:13:11 +01:00
b1679b110c feat: add engine_level and route-level snapshot columns to PostgresExecutionStore
Some checks failed
CI / docker (push) Has been cancelled
CI / build (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / cleanup-branch (push) Has been cancelled
Add engine_level, input_body, output_body, input_headers, output_headers
to the executions INSERT/SELECT/UPSERT and row mapper. Required for
REGULAR mode where route-level payloads exist but no processor records.

Note: requires ALTER TABLE migration to add the new columns.
2026-03-24 16:12:46 +01:00
e7835e1100 feat: map engineLevel and route-level snapshots in IngestionService
Some checks failed
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / cleanup-branch (push) Has been cancelled
CI / build (push) Has been cancelled
Extract inputBody/outputBody/inputHeaders/outputHeaders from RouteExecution
snapshots and pass to ExecutionRecord. Maps engineLevel field. Critical for
REGULAR mode where no processor records exist but route-level payloads do.
2026-03-24 16:11:55 +01:00
ed65b87af2 feat: add engineLevel and route-level snapshot fields to ExecutionRecord
Some checks failed
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / cleanup-branch (push) Has been cancelled
CI / build (push) Has been cancelled
Adds engineLevel (NONE/MINIMAL/REGULAR/COMPLETE) and inputBody/outputBody/
inputHeaders/outputHeaders to ExecutionRecord so REGULAR mode route-level
payloads are persisted (previously only processor-level records had payloads).
2026-03-24 16:11:26 +01:00
4a99e6cf6b feat: support enriched command ack with status/message + set-traced-processors command type
Some checks failed
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / cleanup-branch (push) Has been cancelled
CI / build (push) Has been cancelled
- Add @RequestBody(required=false) CommandAckRequest to ack endpoint for
  receiving agent command results (backward compat with old agents)
- Record command results in agent event log via AgentEventService
- Add set-traced-processors to mapCommandType switch
- Inject AgentEventService dependency
2026-03-24 16:11:04 +01:00
4d9a9ff851 feat: add CommandAckRequest DTO for enriched command acknowledgments
Some checks failed
CI / build (push) Has started running
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / cleanup-branch (push) Has been cancelled
2026-03-24 16:10:27 +01:00
292a38fe30 feat: add SET_TRACED_PROCESSORS command type for per-processor overrides
Some checks failed
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / cleanup-branch (push) Has been cancelled
CI / build (push) Has been cancelled
2026-03-24 16:10:21 +01:00
867 changed files with 83562 additions and 12129 deletions

View File

@@ -0,0 +1,110 @@
---
paths:
- "cameleer-server-app/**"
---
# App Module Key Classes
`cameleer-server-app/src/main/java/com/cameleer/server/app/`
## controller/ — REST endpoints
- `AgentRegistrationController` — POST /register, POST /heartbeat, GET / (list), POST /refresh-token
- `AgentSseController` — GET /sse (Server-Sent Events connection)
- `AgentCommandController` — POST /broadcast, POST /{agentId}, POST /{agentId}/ack
- `AppController` — CRUD /api/v1/apps, POST /{appId}/upload-jar, GET /{appId}/versions
- `DeploymentController` — GET/POST /api/v1/apps/{appId}/deployments, POST /{id}/stop, POST /{id}/promote, GET /{id}/logs
- `EnvironmentAdminController` — CRUD /api/v1/admin/environments, PUT /{id}/jar-retention
- `ExecutionController` — GET /api/v1/executions (search + detail)
- `SearchController` — POST /api/v1/search, GET /routes, GET /top-errors, GET /punchcard
- `LogQueryController` — GET /api/v1/logs (filters: source, application, agentId, exchangeId, level, logger, q, environment, time range)
- `LogIngestionController` — POST /api/v1/data/logs (accepts `List<LogEntry>` JSON array, each entry has `source`: app/agent). Logs WARN for: missing agent identity, unregistered agents, empty payloads, buffer-full drops, deserialization failures. Normal acceptance at DEBUG.
- `CatalogController` — GET /api/v1/catalog (unified app catalog merging PG managed apps + in-memory agents + CH stats), DELETE /api/v1/catalog/{applicationId} (ADMIN: dismiss app, purge all CH data + PG record). Auto-filters discovered apps older than `discoveryttldays` with no live agents.
- `ChunkIngestionController` — POST /api/v1/ingestion/chunk/{executions|metrics|diagrams}
- `UserAdminController` — CRUD /api/v1/admin/users, POST /{id}/roles, POST /{id}/set-password
- `RoleAdminController` — CRUD /api/v1/admin/roles
- `GroupAdminController` — CRUD /api/v1/admin/groups
- `OidcConfigAdminController` — GET/POST /api/v1/admin/oidc, POST /test
- `SensitiveKeysAdminController` — GET/PUT /api/v1/admin/sensitive-keys. GET returns 200 with config or 204 if not configured. PUT accepts `{ keys: [...] }` with optional `?pushToAgents=true` to fan out merged keys to all LIVE agents. Stored in `server_config` table (key `sensitive_keys`).
- `AuditLogController` — GET /api/v1/admin/audit
- `MetricsController` — GET /api/v1/metrics, GET /timeseries
- `DiagramController` — GET /api/v1/diagrams/{id}, POST /
- `DiagramRenderController` — POST /api/v1/diagrams/render (ELK layout)
- `ClaimMappingAdminController` — CRUD /api/v1/admin/claim-mappings, POST /test (accepts inline rules + claims for preview without saving)
- `LicenseAdminController` — GET/POST /api/v1/admin/license
- `AgentEventsController` — GET /api/v1/agent-events (agent state change history)
- `AgentMetricsController` — GET /api/v1/agent-metrics (JVM/Camel metrics per agent instance)
- `AppSettingsController` — GET/PUT /api/v1/apps/{appId}/settings
- `ApplicationConfigController` — GET/PUT /api/v1/apps/{appId}/config (traced processors, route recording, sensitive keys per app)
- `ClickHouseAdminController` — GET /api/v1/admin/clickhouse (ClickHouse admin, conditional on infrastructure endpoints)
- `DatabaseAdminController` — GET /api/v1/admin/database (PG admin, conditional on infrastructure endpoints)
- `DetailController` — GET /api/v1/detail (execution detail with processor tree)
- `EventIngestionController` — POST /api/v1/data/events (agent event ingestion)
- `RbacStatsController` — GET /api/v1/admin/rbac/stats
- `RouteCatalogController` — GET /api/v1/routes/catalog (merged route catalog from registry + ClickHouse)
- `RouteMetricsController` — GET /api/v1/route-metrics (per-route Camel metrics)
- `ThresholdAdminController` — CRUD /api/v1/admin/thresholds
- `UsageAnalyticsController` — GET /api/v1/admin/usage (ClickHouse usage_events)
## runtime/ — Docker orchestration
- `DockerRuntimeOrchestrator` — implements RuntimeOrchestrator; Docker Java client (zerodep transport), container lifecycle
- `DeploymentExecutor`@Async staged deploy: PRE_FLIGHT -> PULL_IMAGE -> CREATE_NETWORK -> START_REPLICAS -> HEALTH_CHECK -> SWAP_TRAFFIC -> COMPLETE. Container names are `{tenantId}-{envSlug}-{appSlug}-{replicaIndex}` (globally unique on Docker daemon). Sets per-replica `CAMELEER_AGENT_INSTANCEID` env var to `{envSlug}-{appSlug}-{replicaIndex}`.
- `DockerNetworkManager` — ensures bridge networks (cameleer-traefik, cameleer-env-{slug}), connects containers
- `DockerEventMonitor` — persistent Docker event stream listener (die, oom, start, stop), updates deployment status
- `TraefikLabelBuilder` — generates Traefik Docker labels for path-based or subdomain routing. Also emits `cameleer.replica` and `cameleer.instance-id` labels per container for labels-first identity.
- `PrometheusLabelBuilder` — generates Prometheus Docker labels (`prometheus.scrape/path/port`) per runtime type for `docker_sd_configs` auto-discovery
- `ContainerLogForwarder` — streams Docker container stdout/stderr to ClickHouse with `source='container'`. One follow-stream thread per container, batches lines every 2s/50 lines via `ClickHouseLogStore.insertBufferedBatch()`. 60-second max capture timeout.
- `DisabledRuntimeOrchestrator` — no-op when runtime not enabled
## metrics/ — Prometheus observability
- `ServerMetrics` — centralized business metrics: gauges (agents by state, SSE connections, buffer depths), counters (ingestion drops, agent transitions, deployment outcomes, auth failures), timers (flush duration, deployment duration). Exposed via `/api/v1/prometheus`.
## storage/ — PostgreSQL repositories (JdbcTemplate)
- `PostgresAppRepository`, `PostgresAppVersionRepository`, `PostgresEnvironmentRepository`
- `PostgresDeploymentRepository` — includes JSONB replica_states, deploy_stage, findByContainerId
- `PostgresUserRepository`, `PostgresRoleRepository`, `PostgresGroupRepository`
- `PostgresAuditRepository`, `PostgresOidcConfigRepository`, `PostgresClaimMappingRepository`, `PostgresSensitiveKeysRepository`
- `PostgresAppSettingsRepository`, `PostgresApplicationConfigRepository`, `PostgresThresholdRepository`
## storage/ — ClickHouse stores
- `ClickHouseExecutionStore`, `ClickHouseMetricsStore`, `ClickHouseMetricsQueryStore`
- `ClickHouseStatsStore` — pre-aggregated stats, punchcard
- `ClickHouseDiagramStore`, `ClickHouseAgentEventRepository`
- `ClickHouseUsageTracker` — usage_events for billing
- `ClickHouseRouteCatalogStore` — persistent route catalog with first_seen cache, warm-loaded on startup
## search/ — ClickHouse search and log stores
- `ClickHouseLogStore` — log storage and query, MDC-based exchange/processor correlation
- `ClickHouseSearchIndex` — full-text search
## security/ — Spring Security
- `SecurityConfig` — WebSecurityFilterChain, JWT filter, CORS, OIDC conditional
- `JwtAuthenticationFilter` — OncePerRequestFilter, validates Bearer tokens
- `JwtServiceImpl` — HMAC-SHA256 JWT (Nimbus JOSE)
- `OidcAuthController` — /api/v1/auth/oidc (login-uri, token-exchange, logout)
- `OidcTokenExchanger` — code -> tokens, role extraction from access_token then id_token
- `OidcProviderHelper` — OIDC discovery, JWK source cache
## agent/ — Agent lifecycle
- `SseConnectionManager` — manages per-agent SSE connections, delivers commands
- `AgentLifecycleMonitor`@Scheduled 10s, LIVE->STALE->DEAD transitions
- `SsePayloadSigner` — Ed25519 signs SSE payloads for agent verification
## retention/ — JAR cleanup
- `JarRetentionJob`@Scheduled 03:00 daily, per-environment retention, skips deployed versions
## config/ — Spring beans
- `RuntimeOrchestratorAutoConfig` — conditional Docker/Disabled orchestrator + NetworkManager + EventMonitor
- `RuntimeBeanConfig` — DeploymentExecutor, AppService, EnvironmentService
- `SecurityBeanConfig` — JwtService, Ed25519, BootstrapTokenValidator
- `StorageBeanConfig` — all repositories
- `ClickHouseConfig` — ClickHouse JdbcTemplate, schema initializer

24
.claude/rules/cicd.md Normal file
View File

@@ -0,0 +1,24 @@
---
paths:
- ".gitea/**"
- "deploy/**"
- "Dockerfile"
- "docker-entrypoint.sh"
---
# CI/CD & Deployment
- CI workflow: `.gitea/workflows/ci.yml` — build -> docker -> deploy on push to main or feature branches
- Build step skips integration tests (`-DskipITs`) — Testcontainers needs Docker daemon
- Docker: multi-stage build (`Dockerfile`), `$BUILDPLATFORM` for native Maven on ARM64 runner, amd64 runtime. `docker-entrypoint.sh` imports `/certs/ca.pem` into JVM truststore before starting the app (supports custom CAs for OIDC discovery without `CAMELEER_SERVER_SECURITY_OIDCTLSSKIPVERIFY`).
- `REGISTRY_TOKEN` build arg required for `cameleer-common` dependency resolution
- Registry: `gitea.siegeln.net/cameleer/cameleer-server` (container images)
- K8s manifests in `deploy/` — Kustomize base + overlays (main/feature), shared infra (PostgreSQL, ClickHouse, Logto) as top-level manifests
- Deployment target: k3s at 192.168.50.86, namespace `cameleer` (main), `cam-<slug>` (feature branches)
- Feature branches: isolated namespace, PG schema; Traefik Ingress at `<slug>-api.cameleer.siegeln.net`
- Secrets managed in CI deploy step (idempotent `--dry-run=client | kubectl apply`): `cameleer-auth`, `cameleer-postgres-credentials`, `cameleer-clickhouse-credentials`
- K8s probes: server uses `/api/v1/health`, PostgreSQL uses `pg_isready -U "$POSTGRES_USER"` (env var, not hardcoded)
- K8s security: server and database pods run with `securityContext.runAsNonRoot`. UI (nginx) runs without securityContext (needs root for entrypoint setup).
- Docker: server Dockerfile has no default credentials — all DB config comes from env vars at runtime
- Docker build uses buildx registry cache + `--provenance=false` for Gitea compatibility
- CI: branch slug sanitization extracted to `.gitea/sanitize-branch.sh`, sourced by docker and deploy-feature jobs

View File

@@ -0,0 +1,98 @@
---
paths:
- "cameleer-server-core/**"
---
# Core Module Key Classes
`cameleer-server-core/src/main/java/com/cameleer/server/core/`
## agent/ — Agent lifecycle and commands
- `AgentRegistryService` — in-memory registry (ConcurrentHashMap), register/heartbeat/lifecycle
- `AgentInfo` — record: id, name, application, environmentId, version, routeIds, capabilities, state
- `AgentCommand` — record: id, type, targetAgent, payload, createdAt, expiresAt
- `AgentEventService` — records agent state changes, heartbeats
- `AgentState` — enum: LIVE, STALE, DEAD, SHUTDOWN
- `CommandType` — enum for command types (config-update, deep-trace, replay, route-control, etc.)
- `CommandStatus` — enum for command acknowledgement states
- `CommandReply` — record: command execution result from agent
- `AgentEventRecord`, `AgentEventRepository` — event persistence
- `AgentEventListener` — callback interface for agent events
- `RouteStateRegistry` — tracks per-agent route states
## runtime/ — App/Environment/Deployment domain
- `App` — record: id, environmentId, slug, displayName, containerConfig (JSONB)
- `AppVersion` — record: id, appId, version, jarPath, detectedRuntimeType, detectedMainClass
- `Environment` — record: id, slug, jarRetentionCount
- `Deployment` — record: id, appId, appVersionId, environmentId, status, targetState, deploymentStrategy, replicaStates (JSONB), deployStage, containerId, containerName
- `DeploymentStatus` — enum: STOPPED, STARTING, RUNNING, DEGRADED, STOPPING, FAILED
- `DeployStage` — enum: PRE_FLIGHT, PULL_IMAGE, CREATE_NETWORK, START_REPLICAS, HEALTH_CHECK, SWAP_TRAFFIC, COMPLETE
- `DeploymentService` — createDeployment (deletes terminal deployments first), markRunning, markFailed, markStopped
- `RuntimeType` — enum: AUTO, SPRING_BOOT, QUARKUS, PLAIN_JAVA, NATIVE
- `RuntimeDetector` — probes JAR files at upload time: detects runtime from manifest Main-Class (Spring Boot loader, Quarkus entry point, plain Java) or native binary (non-ZIP magic bytes)
- `ContainerRequest` — record: 20 fields for Docker container creation (includes runtimeType, customArgs, mainClass)
- `ContainerStatus` — record: state, running, exitCode, error
- `ResolvedContainerConfig` — record: typed config with memoryLimitMb, memoryReserveMb, cpuRequest, cpuLimit, appPort, exposedPorts, customEnvVars, stripPathPrefix, sslOffloading, routingMode, routingDomain, serverUrl, replicas, deploymentStrategy, routeControlEnabled, replayEnabled, runtimeType, customArgs, extraNetworks
- `RoutingMode` — enum for routing strategies
- `ConfigMerger` — pure function: resolve(globalDefaults, envConfig, appConfig) -> ResolvedContainerConfig
- `RuntimeOrchestrator` — interface: startContainer, stopContainer, getContainerStatus, getLogs, startLogCapture, stopLogCapture
- `AppRepository`, `AppVersionRepository`, `EnvironmentRepository`, `DeploymentRepository` — repository interfaces
- `AppService`, `EnvironmentService` — domain services
## search/ — Execution search and stats
- `SearchService` — search, count, stats, statsForApp, timeseries, timeseriesForApp, timeseriesForRoute, timeseriesGroupedByApp, timeseriesGroupedByRoute, slaCompliance, slaCountsByApp, slaCountsByRoute, topErrors, activeErrorTypes, punchcard, distinctAttributeKeys
- `SearchRequest` / `SearchResult` — search DTOs
- `ExecutionStats`, `ExecutionSummary` — stats aggregation records
- `StatsTimeseries`, `TopError` — timeseries and error DTOs
- `LogSearchRequest` / `LogSearchResponse` — log search DTOs
## storage/ — Storage abstractions
- `ExecutionStore`, `MetricsStore`, `MetricsQueryStore`, `StatsStore`, `DiagramStore`, `RouteCatalogStore`, `SearchIndex`, `LogIndex` — interfaces
- `RouteCatalogEntry` — record: applicationId, routeId, environment, firstSeen, lastSeen
- `LogEntryResult` — log query result record
- `model/``ExecutionDocument`, `MetricTimeSeries`, `MetricsSnapshot`
## rbac/ — Role-based access control
- `RbacService` — interface: role/group CRUD, assignRoleToUser, removeRoleFromUser, addUserToGroup, removeUserFromGroup, getDirectRolesForUser, getEffectiveRolesForUser, clearManagedAssignments, assignManagedRole, addUserToManagedGroup, getStats, listUsers
- `SystemRole` — enum: AGENT, VIEWER, OPERATOR, ADMIN; `normalizeScope()` maps scopes
- `UserDetail`, `RoleDetail`, `GroupDetail` — records
- `UserSummary`, `RoleSummary`, `GroupSummary` — lightweight list records
- `RbacStats` — aggregate stats record
- `AssignmentOrigin` — enum: DIRECT, CLAIM_MAPPING (tracks how roles were assigned)
- `ClaimMappingRule` — record: OIDC claim-to-role mapping rule
- `ClaimMappingService` — interface: CRUD for claim mapping rules
- `ClaimMappingRepository` — persistence interface
- `RoleRepository`, `GroupRepository` — persistence interfaces
## admin/ — Server-wide admin config
- `SensitiveKeysConfig` — record: keys (List<String>, immutable)
- `SensitiveKeysRepository` — interface: find(), save()
- `SensitiveKeysMerger` — pure function: merge(global, perApp) -> union with case-insensitive dedup, preserves first-seen casing. Returns null when both inputs null.
- `AppSettings`, `AppSettingsRepository` — per-app settings config and persistence
- `ThresholdConfig`, `ThresholdRepository` — alerting threshold config and persistence
- `AuditService` — audit logging facade
- `AuditRecord`, `AuditResult`, `AuditCategory`, `AuditRepository` — audit trail records and persistence
## security/ — Auth
- `JwtService` — interface: createAccessToken, createRefreshToken, validateAccessToken, validateRefreshToken
- `Ed25519SigningService` — interface: sign, getPublicKeyBase64 (config signing)
- `OidcConfig` — record: enabled, issuerUri, clientId, clientSecret, rolesClaim, defaultRoles, autoSignup, displayNameClaim, userIdClaim, audience, additionalScopes
- `OidcConfigRepository` — persistence interface
- `PasswordPolicyValidator` — min 12 chars, 3-of-4 character classes, no username match
- `UserInfo`, `UserRepository` — user identity records and persistence
- `InvalidTokenException` — thrown on revoked/expired tokens
## ingestion/ — Buffered data pipeline
- `IngestionService` — ingestExecution, ingestMetric, ingestLog, ingestDiagram
- `ChunkAccumulator` — batches data for efficient flush
- `WriteBuffer` — bounded ring buffer for async flush
- `BufferedLogEntry` — log entry wrapper with metadata
- `MergedExecution`, `TaggedExecution`, `TaggedDiagram` — tagged ingestion records

View File

@@ -0,0 +1,76 @@
---
paths:
- "cameleer-server-app/**/runtime/**"
- "cameleer-server-core/**/runtime/**"
- "deploy/**"
- "docker-compose*.yml"
- "Dockerfile"
- "docker-entrypoint.sh"
---
# Docker Orchestration
When deployed via the cameleer-saas platform, this server orchestrates customer app containers using Docker. Key components:
- **ConfigMerger** (`core/runtime/ConfigMerger.java`) — pure function: resolve(globalDefaults, envConfig, appConfig) -> ResolvedContainerConfig. Three-layer merge: global (application.yml) -> environment (defaultContainerConfig JSONB) -> app (containerConfig JSONB). Includes `runtimeType` (default `"auto"`) and `customArgs` (default `""`).
- **TraefikLabelBuilder** (`app/runtime/TraefikLabelBuilder.java`) — generates Traefik Docker labels for path-based (`/{envSlug}/{appSlug}/`) or subdomain-based (`{appSlug}-{envSlug}.{domain}`) routing. Supports strip-prefix and SSL offloading toggles. Also sets per-replica identity labels: `cameleer.replica` (index) and `cameleer.instance-id` (`{envSlug}-{appSlug}-{replicaIndex}`). Internal processing uses labels (not container name parsing) for extensibility.
- **PrometheusLabelBuilder** (`app/runtime/PrometheusLabelBuilder.java`) — generates Prometheus `docker_sd_configs` labels per resolved runtime type: Spring Boot `/actuator/prometheus:8081`, Quarkus/native `/q/metrics:9000`, plain Java `/metrics:9464`. Labels merged into container metadata alongside Traefik labels at deploy time.
- **DockerNetworkManager** (`app/runtime/DockerNetworkManager.java`) — manages two Docker network tiers:
- `cameleer-traefik` — shared network; Traefik, server, and all app containers attach here. Server joined via docker-compose with `cameleer-server` DNS alias.
- `cameleer-env-{slug}` — per-environment isolated network; containers in the same environment discover each other via Docker DNS. In SaaS mode, env networks are tenant-scoped: `cameleer-env-{tenantId}-{envSlug}` (overloaded `envNetworkName(tenantId, envSlug)` method) to prevent cross-tenant collisions when multiple tenants have identically-named environments.
- **DockerEventMonitor** (`app/runtime/DockerEventMonitor.java`) — persistent Docker event stream listener for containers with `managed-by=cameleer-server` label. Detects die/oom/start/stop events and updates deployment replica states. Periodic reconciliation (@Scheduled every 30s) inspects actual container state and corrects deployment status mismatches (fixes stale DEGRADED with all replicas healthy).
- **DeploymentProgress** (`ui/src/components/DeploymentProgress.tsx`) — UI step indicator showing 7 deploy stages with amber active/green completed styling.
- **ContainerLogForwarder** (`app/runtime/ContainerLogForwarder.java`) — streams Docker container stdout/stderr to ClickHouse `logs` table with `source='container'`. Uses `docker logs --follow` per container, batches lines every 2s or 50 lines. Parses Docker timestamp prefix, infers log level via regex. `DeploymentExecutor` starts capture after each replica launches with the replica's `instanceId` (`{envSlug}-{appSlug}-{replicaIndex}`); `DockerEventMonitor` stops capture on die/oom. 60-second max capture timeout with 30s cleanup scheduler. Thread pool of 10 daemon threads. Container logs use the same `instanceId` as the agent (set via `CAMELEER_AGENT_INSTANCEID` env var) for unified log correlation at the instance level.
- **StartupLogPanel** (`ui/src/components/StartupLogPanel.tsx`) — collapsible log panel rendered below `DeploymentProgress`. Queries `/api/v1/logs?source=container&application={appSlug}&environment={envSlug}`. Auto-polls every 3s while deployment is STARTING; shows green "live" badge during polling, red "stopped" badge on FAILED. Uses `useStartupLogs` hook and `LogViewer` (design system).
## DeploymentExecutor Details
Primary network for app containers is set via `CAMELEER_SERVER_RUNTIME_DOCKERNETWORK` env var (in SaaS mode: `cameleer-tenant-{slug}`); apps also connect to `cameleer-traefik` (routing) and `cameleer-env-{tenantId}-{envSlug}` (per-environment discovery) as additional networks. Resolves `runtimeType: auto` to concrete type from `AppVersion.detectedRuntimeType` at PRE_FLIGHT (fails deployment if unresolvable). Builds Docker entrypoint per runtime type (all JVM types use `-javaagent:/app/agent.jar -jar`, plain Java uses `-cp` with main class, native runs binary directly). Sets per-replica `CAMELEER_AGENT_INSTANCEID` env var to `{envSlug}-{appSlug}-{replicaIndex}` so container logs and agent logs share the same instance identity. Sets `CAMELEER_AGENT_*` env vars from `ResolvedContainerConfig` (routeControlEnabled, replayEnabled, health port). These are startup-only agent properties — changing them requires redeployment.
## Deployment Status Model
| Status | Meaning |
|--------|---------|
| `STOPPED` | Intentionally stopped or initial state |
| `STARTING` | Deploy in progress |
| `RUNNING` | All replicas healthy and serving |
| `DEGRADED` | Some replicas healthy, some dead |
| `STOPPING` | Graceful shutdown in progress |
| `FAILED` | Terminal failure (pre-flight, health check, or crash) |
**Replica support**: deployments can specify a replica count. `DEGRADED` is used when at least one but not all replicas are healthy.
**Deploy stages** (`DeployStage`): PRE_FLIGHT -> PULL_IMAGE -> CREATE_NETWORK -> START_REPLICAS -> HEALTH_CHECK -> SWAP_TRAFFIC -> COMPLETE (or FAILED at any stage).
**Blue/green strategy**: when re-deploying, new replicas are started and health-checked before old ones are stopped, minimising downtime.
**Deployment uniqueness**: `DeploymentService.createDeployment()` deletes any STOPPED/FAILED deployments for the same app+environment before creating a new one, preventing duplicate rows.
## JAR Management
- **Retention policy** per environment: configurable maximum number of JAR versions to keep. Older JARs are deleted automatically.
- **Nightly cleanup job** (`JarRetentionJob`, Spring `@Scheduled` 03:00): purges JARs exceeding the retention limit and removes orphaned files not referenced by any app version. Skips versions currently deployed.
- **Volume-based JAR mounting** for Docker-in-Docker setups: set `CAMELEER_SERVER_RUNTIME_JARDOCKERVOLUME` to the Docker volume name that contains the JAR storage directory. When set, the orchestrator mounts this volume into the container instead of bind-mounting the host path (required when the SaaS container itself runs inside Docker and the host path is not accessible from sibling containers).
## Runtime Type Detection
The server detects the app framework from uploaded JARs and builds Docker entrypoints. The agent shaded JAR bundles the log appender, so no separate `cameleer-log-appender.jar` or `PropertiesLauncher` is needed:
- **Detection** (`RuntimeDetector`): runs at JAR upload time. Checks ZIP magic bytes (non-ZIP = native binary), then probes `META-INF/MANIFEST.MF` Main-Class: Spring Boot loader prefix -> `spring-boot`, Quarkus entry point -> `quarkus`, other Main-Class -> `plain-java` (extracts class name). Results stored on `AppVersion` (`detected_runtime_type`, `detected_main_class`).
- **Runtime types** (`RuntimeType` enum): `AUTO`, `SPRING_BOOT`, `QUARKUS`, `PLAIN_JAVA`, `NATIVE`. Configurable per app/environment via `containerConfig.runtimeType` (default `"auto"`).
- **Entrypoint per type**: All JVM types use `java -javaagent:/app/agent.jar -jar app.jar`. Plain Java uses `-cp` with explicit main class instead of `-jar`. Native runs the binary directly.
- **Custom arguments** (`containerConfig.customArgs`): freeform string appended to the start command. Validated against a strict pattern to prevent shell injection (entrypoint uses `sh -c`).
- **AUTO resolution**: at deploy time (PRE_FLIGHT), `"auto"` resolves to the detected type from `AppVersion`. Fails deployment if detection was unsuccessful — user must set type explicitly.
- **UI**: Resources tab shows Runtime Type dropdown (with detection hint from latest uploaded version) and Custom Arguments text field.
## SaaS Multi-Tenant Network Isolation
In SaaS mode, each tenant's server and its deployed apps are isolated at the Docker network level:
- **Tenant network** (`cameleer-tenant-{slug}`) — primary internal bridge for all of a tenant's containers. Set as `CAMELEER_SERVER_RUNTIME_DOCKERNETWORK` for the tenant's server instance. Tenant A's apps cannot reach tenant B's apps.
- **Shared services network** — server also connects to the shared infrastructure network (PostgreSQL, ClickHouse, Logto) and `cameleer-traefik` for HTTP routing.
- **Tenant-scoped environment networks** (`cameleer-env-{tenantId}-{envSlug}`) — per-environment discovery is scoped per tenant, so `alpha-corp`'s "dev" environment network is separate from `beta-corp`'s "dev" environment network.
## nginx / Reverse Proxy
- `client_max_body_size 200m` is required in the nginx config to allow JAR uploads up to 200 MB. Without this, large JAR uploads return 413.

98
.claude/rules/gitnexus.md Normal file
View File

@@ -0,0 +1,98 @@
# GitNexus — Code Intelligence
This project is indexed by GitNexus as **cameleer-server** (6306 symbols, 15892 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.
## Always Do
- **MUST run impact analysis before editing any symbol.** Before modifying a function, class, or method, run `gitnexus_impact({target: "symbolName", direction: "upstream"})` and report the blast radius (direct callers, affected processes, risk level) to the user.
- **MUST run `gitnexus_detect_changes()` before committing** to verify your changes only affect expected symbols and execution flows.
- **MUST warn the user** if impact analysis returns HIGH or CRITICAL risk before proceeding with edits.
- When exploring unfamiliar code, use `gitnexus_query({query: "concept"})` to find execution flows instead of grepping. It returns process-grouped results ranked by relevance.
- When you need full context on a specific symbol — callers, callees, which execution flows it participates in — use `gitnexus_context({name: "symbolName"})`.
## When Debugging
1. `gitnexus_query({query: "<error or symptom>"})` — find execution flows related to the issue
2. `gitnexus_context({name: "<suspect function>"})` — see all callers, callees, and process participation
3. `READ gitnexus://repo/cameleer-server/process/{processName}` — trace the full execution flow step by step
4. For regressions: `gitnexus_detect_changes({scope: "compare", base_ref: "main"})` — see what your branch changed
## When Refactoring
- **Renaming**: MUST use `gitnexus_rename({symbol_name: "old", new_name: "new", dry_run: true})` first. Review the preview — graph edits are safe, text_search edits need manual review. Then run with `dry_run: false`.
- **Extracting/Splitting**: MUST run `gitnexus_context({name: "target"})` to see all incoming/outgoing refs, then `gitnexus_impact({target: "target", direction: "upstream"})` to find all external callers before moving code.
- After any refactor: run `gitnexus_detect_changes({scope: "all"})` to verify only expected files changed.
## Never Do
- NEVER edit a function, class, or method without first running `gitnexus_impact` on it.
- NEVER ignore HIGH or CRITICAL risk warnings from impact analysis.
- NEVER rename symbols with find-and-replace — use `gitnexus_rename` which understands the call graph.
- NEVER commit changes without running `gitnexus_detect_changes()` to check affected scope.
## Tools Quick Reference
| Tool | When to use | Command |
|------|-------------|---------|
| `query` | Find code by concept | `gitnexus_query({query: "auth validation"})` |
| `context` | 360-degree view of one symbol | `gitnexus_context({name: "validateUser"})` |
| `impact` | Blast radius before editing | `gitnexus_impact({target: "X", direction: "upstream"})` |
| `detect_changes` | Pre-commit scope check | `gitnexus_detect_changes({scope: "staged"})` |
| `rename` | Safe multi-file rename | `gitnexus_rename({symbol_name: "old", new_name: "new", dry_run: true})` |
| `cypher` | Custom graph queries | `gitnexus_cypher({query: "MATCH ..."})` |
## Impact Risk Levels
| Depth | Meaning | Action |
|-------|---------|--------|
| d=1 | WILL BREAK — direct callers/importers | MUST update these |
| d=2 | LIKELY AFFECTED — indirect deps | Should test |
| d=3 | MAY NEED TESTING — transitive | Test if critical path |
## Resources
| Resource | Use for |
|----------|---------|
| `gitnexus://repo/cameleer-server/context` | Codebase overview, check index freshness |
| `gitnexus://repo/cameleer-server/clusters` | All functional areas |
| `gitnexus://repo/cameleer-server/processes` | All execution flows |
| `gitnexus://repo/cameleer-server/process/{name}` | Step-by-step execution trace |
## Self-Check Before Finishing
Before completing any code modification task, verify:
1. `gitnexus_impact` was run for all modified symbols
2. No HIGH/CRITICAL risk warnings were ignored
3. `gitnexus_detect_changes()` confirms changes match expected scope
4. All d=1 (WILL BREAK) dependents were updated
## Keeping the Index Fresh
After committing code changes, the GitNexus index becomes stale. Re-run analyze to update it:
```bash
npx gitnexus analyze
```
If the index previously included embeddings, preserve them by adding `--embeddings`:
```bash
npx gitnexus analyze --embeddings
```
To check whether embeddings exist, inspect `.gitnexus/meta.json` — the `stats.embeddings` field shows the count (0 means no embeddings). **Running analyze without `--embeddings` will delete any previously generated embeddings.**
> Claude Code users: A PostToolUse hook handles this automatically after `git commit` and `git merge`.
## CLI
| Task | Read this skill file |
|------|---------------------|
| Understand architecture / "How does X work?" | `.claude/skills/gitnexus/gitnexus-exploring/SKILL.md` |
| Blast radius / "What breaks if I change X?" | `.claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md` |
| Trace bugs / "Why is X failing?" | `.claude/skills/gitnexus/gitnexus-debugging/SKILL.md` |
| Rename / extract / split / refactor | `.claude/skills/gitnexus/gitnexus-refactoring/SKILL.md` |
| Tools, resources, schema reference | `.claude/skills/gitnexus/gitnexus-guide/SKILL.md` |
| Index, status, clean, wiki CLI commands | `.claude/skills/gitnexus/gitnexus-cli/SKILL.md` |

85
.claude/rules/metrics.md Normal file
View File

@@ -0,0 +1,85 @@
---
paths:
- "cameleer-server-app/**/metrics/**"
- "cameleer-server-app/**/ServerMetrics*"
- "ui/src/pages/RuntimeTab/**"
- "ui/src/pages/DashboardTab/**"
---
# Prometheus Metrics
Server exposes `/api/v1/prometheus` (unauthenticated, Prometheus text format). Spring Boot Actuator provides JVM, GC, thread pool, and `http.server.requests` metrics automatically. Business metrics via `ServerMetrics` component:
## Gauges (auto-polled)
| Metric | Tags | Source |
|--------|------|--------|
| `cameleer.agents.connected` | `state` (live, stale, dead, shutdown) | `AgentRegistryService.findByState()` |
| `cameleer.agents.sse.active` | — | `SseConnectionManager.getConnectionCount()` |
| `cameleer.ingestion.buffer.size` | `type` (execution, processor, log, metrics) | `WriteBuffer.size()` |
| `cameleer.ingestion.accumulator.pending` | — | `ChunkAccumulator.getPendingCount()` |
## Counters
| Metric | Tags | Instrumented in |
|--------|------|-----------------|
| `cameleer.ingestion.drops` | `reason` (buffer_full, no_agent, no_identity) | `LogIngestionController` |
| `cameleer.agents.transitions` | `transition` (went_stale, went_dead, recovered) | `AgentLifecycleMonitor` |
| `cameleer.deployments.outcome` | `status` (running, failed, degraded) | `DeploymentExecutor` |
| `cameleer.auth.failures` | `reason` (invalid_token, revoked, oidc_rejected) | `JwtAuthenticationFilter` |
## Timers
| Metric | Tags | Instrumented in |
|--------|------|-----------------|
| `cameleer.ingestion.flush.duration` | `type` (execution, processor, log) | `ExecutionFlushScheduler` |
| `cameleer.deployments.duration` | — | `DeploymentExecutor` |
## Agent container Prometheus labels (set by PrometheusLabelBuilder at deploy time)
| Runtime Type | `prometheus.path` | `prometheus.port` |
|---|---|---|
| `spring-boot` | `/actuator/prometheus` | `8081` |
| `quarkus` / `native` | `/q/metrics` | `9000` |
| `plain-java` | `/metrics` | `9464` |
All containers also get `prometheus.scrape=true`. These labels enable Prometheus `docker_sd_configs` auto-discovery.
## Agent Metric Names (Micrometer)
Agents send `MetricsSnapshot` records with Micrometer-convention metric names. The server stores them generically (ClickHouse `agent_metrics.metric_name`). The UI references specific names in `AgentInstance.tsx` for JVM charts.
### JVM metrics (used by UI)
| Metric name | UI usage |
|---|---|
| `process.cpu.usage.value` | CPU % stat card + chart |
| `jvm.memory.used.value` | Heap MB stat card + chart (tags: `area=heap`) |
| `jvm.memory.max.value` | Heap max for % calculation (tags: `area=heap`) |
| `jvm.threads.live.value` | Thread count chart |
| `jvm.gc.pause.total_time` | GC time chart |
### Camel route metrics (stored, queried by dashboard)
| Metric name | Type | Tags |
|---|---|---|
| `camel.exchanges.succeeded.count` | counter | `routeId`, `camelContext` |
| `camel.exchanges.failed.count` | counter | `routeId`, `camelContext` |
| `camel.exchanges.total.count` | counter | `routeId`, `camelContext` |
| `camel.exchanges.failures.handled.count` | counter | `routeId`, `camelContext` |
| `camel.route.policy.count` | count | `routeId`, `camelContext` |
| `camel.route.policy.total_time` | total | `routeId`, `camelContext` |
| `camel.route.policy.max` | gauge | `routeId`, `camelContext` |
| `camel.routes.running.value` | gauge | — |
Mean processing time = `camel.route.policy.total_time / camel.route.policy.count`. Min processing time is not available (Micrometer does not track minimums).
### Cameleer agent metrics
| Metric name | Type | Tags |
|---|---|---|
| `cameleer.chunks.exported.count` | counter | `instanceId` |
| `cameleer.chunks.dropped.count` | counter | `instanceId`, `reason` |
| `cameleer.sse.reconnects.count` | counter | `instanceId` |
| `cameleer.taps.evaluated.count` | counter | `instanceId` |
| `cameleer.metrics.exported.count` | counter | `instanceId` |

43
.claude/rules/ui.md Normal file
View File

@@ -0,0 +1,43 @@
---
paths:
- "ui/**"
---
# UI Structure
The UI has 4 main tabs: **Exchanges**, **Dashboard**, **Runtime**, **Deployments**.
- **Exchanges** — route execution search and detail (`ui/src/pages/Exchanges/`)
- **Dashboard** — metrics and stats with L1/L2/L3 drill-down (`ui/src/pages/DashboardTab/`)
- **Runtime** — live agent status, logs, commands (`ui/src/pages/RuntimeTab/`). AgentHealth supports compact view (dense health-tinted cards) and expanded view (full GroupCard+DataTable per app). View mode persisted to localStorage.
- **Deployments** — app management, JAR upload, deployment lifecycle (`ui/src/pages/AppsTab/`)
- Config sub-tabs: **Monitoring | Resources | Variables | Traces & Taps | Route Recording**
- Create app: full page at `/apps/new` (not a modal)
- Deployment progress: `ui/src/components/DeploymentProgress.tsx` (7-stage step indicator)
**Admin pages** (ADMIN-only, under `/admin/`):
- **Sensitive Keys** (`ui/src/pages/Admin/SensitiveKeysPage.tsx`) — global sensitive key masking config. Shows agent built-in defaults as outlined Badge reference, editable Tag pills for custom keys, amber-highlighted push-to-agents toggle. Keys add to (not replace) agent defaults. Per-app sensitive key additions managed via `ApplicationConfigController` API. Note: `AppConfigDetailPage.tsx` exists but is not routed in `router.tsx`.
## Key UI Files
- `ui/src/router.tsx` — React Router v6 routes
- `ui/src/config.ts` — apiBaseUrl, basePath
- `ui/src/auth/auth-store.ts` — Zustand: accessToken, user, roles, login/logout
- `ui/src/api/environment-store.ts` — Zustand: selected environment (localStorage)
- `ui/src/components/ContentTabs.tsx` — main tab switcher
- `ui/src/components/ExecutionDiagram/` — interactive trace view (canvas)
- `ui/src/components/ProcessDiagram/` — ELK-rendered route diagram
- `ui/src/hooks/useScope.ts` — TabKey type, scope inference
- `ui/src/components/StartupLogPanel.tsx` — deployment startup log viewer (container logs from ClickHouse, polls 3s while STARTING)
- `ui/src/api/queries/logs.ts``useStartupLogs` hook for container startup log polling, `useLogs`/`useApplicationLogs` for general log search
## UI Styling
- Always use `@cameleer/design-system` CSS variables for colors (`var(--amber)`, `var(--error)`, `var(--success)`, etc.) — never hardcode hex values. This applies to CSS modules, inline styles, and SVG `fill`/`stroke` attributes. SVG presentation attributes resolve `var()` correctly. All colors use CSS variables (no hardcoded hex).
- Shared CSS modules in `ui/src/styles/` (table-section, log-panel, rate-colors, refresh-indicator, chart-card, section-card) — import these instead of duplicating patterns.
- Shared `PageLoader` component replaces copy-pasted spinner patterns.
- Design system components used consistently: `Select`, `Tabs`, `Toggle`, `Button`, `LogViewer`, `Label` — prefer DS components over raw HTML elements. `LogViewer` renders optional source badges (`container`, `app`, `agent`) via `LogEntry.source` field (DS v0.1.49+).
- Environment slugs are auto-computed from display name (read-only in UI).
- Brand assets: `@cameleer/design-system/assets/` provides `camel-logo.svg` (currentColor), `cameleer-{16,32,48,192,512}.png`, and `cameleer-logo.png`. Copied to `ui/public/` for use as favicon (`favicon-16.png`, `favicon-32.png`) and logo (`camel-logo.svg` — login dialog 36px, sidebar 28x24px).
- Sidebar generates `/exchanges/` paths directly (no legacy `/apps/` redirects). basePath is centralized in `ui/src/config.ts`; router.tsx imports it instead of re-reading `<base>` tag.
- Global user preferences (environment selection) use Zustand stores with localStorage persistence — never URL search params. URL params are for page-specific state only (e.g. `?text=` search query). Switching environment resets all filters and remounts pages.

11
.gitea/sanitize-branch.sh Normal file
View File

@@ -0,0 +1,11 @@
#!/bin/sh
# Shared branch slug sanitization for CI jobs.
# Strips prefix (feature/, fix/, etc.), lowercases, replaces non-alphanum, truncates to 20 chars.
sanitize_branch() {
echo "$1" | sed -E 's#^(feature|fix|feat|hotfix)/##' \
| tr '[:upper:]' '[:lower:]' \
| sed 's/[^a-z0-9-]/-/g' \
| sed 's/--*/-/g; s/^-//; s/-$//' \
| cut -c1-20 \
| sed 's/-$//'
}

View File

@@ -14,16 +14,11 @@ jobs:
runs-on: ubuntu-latest
if: github.event_name != 'delete'
container:
image: maven:3.9-eclipse-temurin-17
image: gitea.siegeln.net/cameleer/cameleer-build:1
credentials:
username: cameleer
password: ${{ secrets.REGISTRY_TOKEN }}
steps:
- name: Install Node.js 22
run: |
apt-get update && apt-get install -y ca-certificates curl gnupg
mkdir -p /etc/apt/keyrings
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_22.x nodistro main" > /etc/apt/sources.list.d/nodesource.list
apt-get update && apt-get install -y nodejs
- uses: actions/checkout@v4
- name: Configure Gitea Maven Registry
@@ -53,22 +48,28 @@ jobs:
- name: Build UI
working-directory: ui
run: |
echo '//gitea.siegeln.net/api/packages/cameleer/npm/:_authToken=${REGISTRY_TOKEN}' >> .npmrc
npm ci
npm run build
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
VITE_APP_VERSION: ${{ github.sha }}
- name: Build and Test
run: mvn clean verify -DskipITs --batch-mode
run: mvn clean verify -DskipITs -U --batch-mode
docker:
needs: build
runs-on: ubuntu-latest
if: github.event_name == 'push'
container:
image: docker:27
image: gitea.siegeln.net/cameleer/cameleer-docker-builder:1
credentials:
username: cameleer
password: ${{ secrets.REGISTRY_TOKEN }}
steps:
- name: Checkout
run: |
apk add --no-cache git
git clone --depth=1 --branch=${GITHUB_REF_NAME} https://cameleer:${REGISTRY_TOKEN}@gitea.siegeln.net/${GITHUB_REPOSITORY}.git .
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
@@ -78,14 +79,7 @@ jobs:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
- name: Compute branch slug
run: |
sanitize_branch() {
echo "$1" | sed -E 's#^(feature|fix|feat|hotfix)/##' \
| tr '[:upper:]' '[:lower:]' \
| sed 's/[^a-z0-9-]/-/g' \
| sed 's/--*/-/g; s/^-//; s/-$//' \
| cut -c1-20 \
| sed 's/-$//'
}
. .gitea/sanitize-branch.sh
if [ "$GITHUB_REF_NAME" = "main" ]; then
echo "BRANCH_SLUG=main" >> "$GITHUB_ENV"
echo "IMAGE_TAGS=latest" >> "$GITHUB_ENV"
@@ -95,35 +89,37 @@ jobs:
echo "IMAGE_TAGS=branch-$SLUG" >> "$GITHUB_ENV"
fi
- name: Set up QEMU for cross-platform builds
run: docker run --rm --privileged tonistiigi/binfmt --install all
run: docker run --rm --privileged gitea.siegeln.net/cameleer/binfmt:1 --install all
- name: Build and push server
run: |
docker buildx create --use --name cibuilder
TAGS="-t gitea.siegeln.net/cameleer/cameleer3-server:${{ github.sha }}"
TAGS="-t gitea.siegeln.net/cameleer/cameleer-server:${{ github.sha }}"
for TAG in $IMAGE_TAGS; do
TAGS="$TAGS -t gitea.siegeln.net/cameleer/cameleer3-server:$TAG"
TAGS="$TAGS -t gitea.siegeln.net/cameleer/cameleer-server:$TAG"
done
docker buildx build --platform linux/amd64 \
--build-arg REGISTRY_TOKEN="$REGISTRY_TOKEN" \
$TAGS \
--cache-from type=registry,ref=gitea.siegeln.net/cameleer/cameleer3-server:buildcache \
--cache-to type=registry,ref=gitea.siegeln.net/cameleer/cameleer3-server:buildcache,mode=max \
--cache-from type=registry,ref=gitea.siegeln.net/cameleer/cameleer-server:buildcache \
--cache-to type=registry,ref=gitea.siegeln.net/cameleer/cameleer-server:buildcache,mode=max \
--provenance=false \
--push .
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
- name: Build and push UI
run: |
TAGS="-t gitea.siegeln.net/cameleer/cameleer3-server-ui:${{ github.sha }}"
TAGS="-t gitea.siegeln.net/cameleer/cameleer-server-ui:${{ github.sha }}"
for TAG in $IMAGE_TAGS; do
TAGS="$TAGS -t gitea.siegeln.net/cameleer/cameleer3-server-ui:$TAG"
TAGS="$TAGS -t gitea.siegeln.net/cameleer/cameleer-server-ui:$TAG"
done
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)
docker buildx build --platform linux/amd64 \
-f ui/Dockerfile \
--build-arg REGISTRY_TOKEN="$REGISTRY_TOKEN" \
--build-arg VITE_APP_VERSION="$SHORT_SHA" \
$TAGS \
--cache-from type=registry,ref=gitea.siegeln.net/cameleer/cameleer3-server-ui:buildcache \
--cache-to type=registry,ref=gitea.siegeln.net/cameleer/cameleer3-server-ui:buildcache,mode=max \
--cache-from type=registry,ref=gitea.siegeln.net/cameleer/cameleer-server-ui:buildcache \
--cache-to type=registry,ref=gitea.siegeln.net/cameleer/cameleer-server-ui:buildcache,mode=max \
--provenance=false \
--push ui/
env:
@@ -133,7 +129,6 @@ jobs:
if: always()
- name: Cleanup old container images
run: |
apk add --no-cache curl jq
API="https://gitea.siegeln.net/api/v1"
AUTH="Authorization: token ${REGISTRY_TOKEN}"
CURRENT_SHA="${{ github.sha }}"
@@ -142,7 +137,7 @@ jobs:
if [ "$BRANCH_SLUG" != "main" ]; then
KEEP_TAGS="$KEEP_TAGS branch-$BRANCH_SLUG"
fi
for PKG in cameleer3-server cameleer3-server-ui; do
for PKG in cameleer-server cameleer-server-ui; do
curl -sf -H "$AUTH" "$API/packages/cameleer/container/$PKG" | \
jq -r '.[] | "\(.id) \(.version)"' | \
while read id version; do
@@ -197,49 +192,50 @@ jobs:
kubectl create secret generic cameleer-auth \
--namespace=cameleer \
--from-literal=CAMELEER_AUTH_TOKEN="$CAMELEER_AUTH_TOKEN" \
--from-literal=CAMELEER_UI_USER="${CAMELEER_UI_USER:-admin}" \
--from-literal=CAMELEER_UI_PASSWORD="${CAMELEER_UI_PASSWORD:-admin}" \
--from-literal=CAMELEER_JWT_SECRET="${CAMELEER_JWT_SECRET}" \
--from-literal=CAMELEER_SERVER_SECURITY_BOOTSTRAPTOKEN="$CAMELEER_AUTH_TOKEN" \
--from-literal=CAMELEER_SERVER_SECURITY_UIUSER="${CAMELEER_UI_USER:-admin}" \
--from-literal=CAMELEER_SERVER_SECURITY_UIPASSWORD="${CAMELEER_UI_PASSWORD:-admin}" \
--from-literal=CAMELEER_SERVER_SECURITY_JWTSECRET="${CAMELEER_JWT_SECRET}" \
--dry-run=client -o yaml | kubectl apply -f -
kubectl create secret generic postgres-credentials \
kubectl create secret generic cameleer-postgres-credentials \
--namespace=cameleer \
--from-literal=POSTGRES_USER="$POSTGRES_USER" \
--from-literal=POSTGRES_PASSWORD="$POSTGRES_PASSWORD" \
--from-literal=POSTGRES_DB="${POSTGRES_DB:-cameleer}" \
--dry-run=client -o yaml | kubectl apply -f -
kubectl create secret generic opensearch-credentials \
kubectl create secret generic cameleer-logto-credentials \
--namespace=cameleer \
--from-literal=OPENSEARCH_USER="${OPENSEARCH_USER:-admin}" \
--from-literal=OPENSEARCH_PASSWORD="$OPENSEARCH_PASSWORD" \
--from-literal=PG_USER="${LOGTO_PG_USER:-logto}" \
--from-literal=PG_PASSWORD="${LOGTO_PG_PASSWORD}" \
--from-literal=ENDPOINT="${LOGTO_ENDPOINT}" \
--from-literal=ADMIN_ENDPOINT="${LOGTO_ADMIN_ENDPOINT}" \
--dry-run=client -o yaml | kubectl apply -f -
kubectl create secret generic authentik-credentials \
kubectl create secret generic cameleer-clickhouse-credentials \
--namespace=cameleer \
--from-literal=PG_USER="${AUTHENTIK_PG_USER:-authentik}" \
--from-literal=PG_PASSWORD="${AUTHENTIK_PG_PASSWORD}" \
--from-literal=AUTHENTIK_SECRET_KEY="${AUTHENTIK_SECRET_KEY}" \
--from-literal=CLICKHOUSE_USER="${CLICKHOUSE_USER:-default}" \
--from-literal=CLICKHOUSE_PASSWORD="$CLICKHOUSE_PASSWORD" \
--dry-run=client -o yaml | kubectl apply -f -
kubectl apply -f deploy/postgres.yaml
kubectl -n cameleer rollout status statefulset/postgres --timeout=120s
kubectl apply -f deploy/cameleer-postgres.yaml
kubectl -n cameleer rollout status statefulset/cameleer-postgres --timeout=120s
kubectl apply -f deploy/opensearch.yaml
kubectl -n cameleer rollout status statefulset/opensearch --timeout=180s
kubectl apply -f deploy/cameleer-clickhouse.yaml
kubectl -n cameleer rollout status statefulset/cameleer-clickhouse --timeout=180s
kubectl apply -f deploy/authentik.yaml
kubectl -n cameleer rollout status deployment/authentik-server --timeout=180s
kubectl apply -f deploy/cameleer-logto.yaml
kubectl -n cameleer rollout status deployment/cameleer-logto --timeout=180s
kubectl apply -k deploy/overlays/main
kubectl -n cameleer set image deployment/cameleer3-server \
server=gitea.siegeln.net/cameleer/cameleer3-server:${{ github.sha }}
kubectl -n cameleer rollout status deployment/cameleer3-server --timeout=120s
kubectl -n cameleer set image deployment/cameleer-server \
server=gitea.siegeln.net/cameleer/cameleer-server:${{ github.sha }}
kubectl -n cameleer rollout status deployment/cameleer-server --timeout=120s
kubectl -n cameleer set image deployment/cameleer3-ui \
ui=gitea.siegeln.net/cameleer/cameleer3-server-ui:${{ github.sha }}
kubectl -n cameleer rollout status deployment/cameleer3-ui --timeout=120s
kubectl -n cameleer set image deployment/cameleer-ui \
ui=gitea.siegeln.net/cameleer/cameleer-server-ui:${{ github.sha }}
kubectl -n cameleer rollout status deployment/cameleer-ui --timeout=120s
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
CAMELEER_AUTH_TOKEN: ${{ secrets.CAMELEER_AUTH_TOKEN }}
@@ -249,11 +245,12 @@ jobs:
POSTGRES_USER: ${{ secrets.POSTGRES_USER }}
POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
POSTGRES_DB: ${{ secrets.POSTGRES_DB }}
OPENSEARCH_USER: ${{ secrets.OPENSEARCH_USER }}
OPENSEARCH_PASSWORD: ${{ secrets.OPENSEARCH_PASSWORD }}
AUTHENTIK_PG_USER: ${{ secrets.AUTHENTIK_PG_USER }}
AUTHENTIK_PG_PASSWORD: ${{ secrets.AUTHENTIK_PG_PASSWORD }}
AUTHENTIK_SECRET_KEY: ${{ secrets.AUTHENTIK_SECRET_KEY }}
LOGTO_PG_USER: ${{ secrets.LOGTO_PG_USER }}
LOGTO_PG_PASSWORD: ${{ secrets.LOGTO_PG_PASSWORD }}
LOGTO_ENDPOINT: ${{ secrets.LOGTO_ENDPOINT }}
LOGTO_ADMIN_ENDPOINT: ${{ secrets.LOGTO_ADMIN_ENDPOINT }}
CLICKHOUSE_USER: ${{ secrets.CLICKHOUSE_USER }}
CLICKHOUSE_PASSWORD: ${{ secrets.CLICKHOUSE_PASSWORD }}
deploy-feature:
needs: docker
@@ -275,14 +272,7 @@ jobs:
KUBECONFIG_B64: ${{ secrets.KUBECONFIG_BASE64 }}
- name: Compute branch variables
run: |
sanitize_branch() {
echo "$1" | sed -E 's#^(feature|fix|feat|hotfix)/##' \
| tr '[:upper:]' '[:lower:]' \
| sed 's/[^a-z0-9-]/-/g' \
| sed 's/--*/-/g; s/^-//; s/-$//' \
| cut -c1-20 \
| sed 's/-$//'
}
. .gitea/sanitize-branch.sh
SLUG=$(sanitize_branch "$GITHUB_REF_NAME")
NS="cam-${SLUG}"
SCHEMA="cam_$(echo $SLUG | tr '-' '_')"
@@ -293,7 +283,7 @@ jobs:
run: kubectl create namespace "$BRANCH_NS" --dry-run=client -o yaml | kubectl apply -f -
- name: Copy secrets from cameleer namespace
run: |
for SECRET in gitea-registry postgres-credentials opensearch-credentials cameleer-auth; do
for SECRET in gitea-registry cameleer-postgres-credentials cameleer-clickhouse-credentials cameleer-auth; do
kubectl get secret "$SECRET" -n cameleer -o json \
| jq 'del(.metadata.namespace, .metadata.resourceVersion, .metadata.uid, .metadata.creationTimestamp, .metadata.managedFields)' \
| kubectl apply -n "$BRANCH_NS" -f -
@@ -319,9 +309,9 @@ jobs:
kubectl -n "$BRANCH_NS" wait --for=condition=complete job/init-schema --timeout=60s || \
echo "Warning: init-schema job did not complete in time"
- name: Wait for server rollout
run: kubectl -n "$BRANCH_NS" rollout status deployment/cameleer3-server --timeout=120s
run: kubectl -n "$BRANCH_NS" rollout status deployment/cameleer-server --timeout=120s
- name: Wait for UI rollout
run: kubectl -n "$BRANCH_NS" rollout status deployment/cameleer3-ui --timeout=60s
run: kubectl -n "$BRANCH_NS" rollout status deployment/cameleer-ui --timeout=60s
- name: Print deployment URLs
run: |
echo "===================================="
@@ -368,25 +358,16 @@ jobs:
--namespace=cameleer \
--image=postgres:16 \
--restart=Never \
--env="PGPASSWORD=$(kubectl get secret postgres-credentials -n cameleer -o jsonpath='{.data.POSTGRES_PASSWORD}' | base64 -d)" \
--command -- sh -c "psql -h postgres -U $(kubectl get secret postgres-credentials -n cameleer -o jsonpath='{.data.POSTGRES_USER}' | base64 -d) -d cameleer3 -c 'DROP SCHEMA IF EXISTS ${BRANCH_SCHEMA} CASCADE'"
--env="PGPASSWORD=$(kubectl get secret cameleer-postgres-credentials -n cameleer -o jsonpath='{.data.POSTGRES_PASSWORD}' | base64 -d)" \
--command -- sh -c "psql -h cameleer-postgres -U $(kubectl get secret cameleer-postgres-credentials -n cameleer -o jsonpath='{.data.POSTGRES_USER}' | base64 -d) -d cameleer -c 'DROP SCHEMA IF EXISTS ${BRANCH_SCHEMA} CASCADE'"
kubectl wait --for=condition=Ready pod/cleanup-schema-${BRANCH_SLUG} -n cameleer --timeout=30s || true
kubectl wait --for=jsonpath='{.status.phase}'=Succeeded pod/cleanup-schema-${BRANCH_SLUG} -n cameleer --timeout=60s || true
kubectl delete pod cleanup-schema-${BRANCH_SLUG} -n cameleer --ignore-not-found
- name: Delete OpenSearch indices
run: |
kubectl run cleanup-indices-${BRANCH_SLUG} \
--namespace=cameleer \
--image=curlimages/curl:latest \
--restart=Never \
--command -- curl -sf -X DELETE "http://opensearch:9200/cam-${BRANCH_SLUG}-*"
kubectl wait --for=jsonpath='{.status.phase}'=Succeeded pod/cleanup-indices-${BRANCH_SLUG} -n cameleer --timeout=60s || true
kubectl delete pod cleanup-indices-${BRANCH_SLUG} -n cameleer --ignore-not-found
- name: Cleanup Docker images
run: |
API="https://gitea.siegeln.net/api/v1"
AUTH="Authorization: token ${REGISTRY_TOKEN}"
for PKG in cameleer3-server cameleer3-server-ui; do
for PKG in cameleer-server cameleer-server-ui; do
# Delete branch-specific tag
curl -sf -X DELETE -H "$AUTH" "$API/packages/cameleer/container/$PKG/branch-${BRANCH_SLUG}" || true
done

View File

@@ -0,0 +1,63 @@
name: SonarQube
on:
schedule:
- cron: '0 2 * * *'
workflow_dispatch:
jobs:
sonarqube:
runs-on: ubuntu-latest
container:
image: gitea.siegeln.net/cameleer/cameleer-build:1
credentials:
username: cameleer
password: ${{ secrets.REGISTRY_TOKEN }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Configure Gitea Maven Registry
run: |
mkdir -p ~/.m2
cat > ~/.m2/settings.xml << 'SETTINGS'
<settings>
<servers>
<server>
<id>gitea</id>
<username>cameleer</username>
<password>${env.REGISTRY_TOKEN}</password>
</server>
</servers>
</settings>
SETTINGS
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
- name: Cache Maven dependencies
uses: actions/cache@v4
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-maven-
- name: Install UI dependencies
working-directory: ui
run: |
echo '//gitea.siegeln.net/api/packages/cameleer/npm/:_authToken=${REGISTRY_TOKEN}' >> .npmrc
npm ci
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
- name: Lint UI
working-directory: ui
run: npm run lint -- --format json --output-file eslint-report.json || true
- name: Build, Test and Analyze
run: |
mvn clean verify sonar:sonar -DskipITs -U --batch-mode \
-Dsonar.host.url=${{ secrets.SONAR_HOST_URL }} \
-Dsonar.token=${{ secrets.SONAR_TOKEN }} \
-Dsonar.projectKey=cameleer-server \
-Dsonar.projectName="Cameleer Server"

7
.gitignore vendored
View File

@@ -38,4 +38,9 @@ Thumbs.db
logs/
# Claude
.claude/
.claude/*
!.claude/rules/
.superpowers/
.playwright-mcp/
.worktrees/
.gitnexus

View File

@@ -1,8 +1,8 @@
# Cameleer3 Server
# Cameleer Server
## What This Is
An observability server that receives, stores, and serves Apache Camel route execution data from distributed Cameleer3 agents. Think njams Server (by Integration Matters) — but built incrementally, API-first, with a modern stack. Users can search through millions of recorded transactions by state, time, duration, full text, and correlate executions across multiple Camel instances. The server also pushes configuration, tracing controls, and ad-hoc commands to agents via SSE.
An observability server that receives, stores, and serves Apache Camel route execution data from distributed Cameleer agents. Think njams Server (by Integration Matters) — but built incrementally, API-first, with a modern stack. Users can search through millions of recorded transactions by state, time, duration, full text, and correlate executions across multiple Camel instances. The server also pushes configuration, tracing controls, and ad-hoc commands to agents via SSE.
## Core Value
@@ -16,7 +16,7 @@ Users can reliably search and find any transaction across all connected Camel in
### Active
- [ ] Receive and ingest transaction/activity data from Cameleer3 agents via HTTP POST
- [ ] Receive and ingest transaction/activity data from Cameleer agents via HTTP POST
- [ ] Store transactions in a high-volume, horizontally scalable data store with 30-day retention
- [ ] Search transactions by state, execution date/time, duration, and full-text content
- [ ] Correlate activities across multiple routes and Camel instances within a single transaction
@@ -38,8 +38,8 @@ Users can reliably search and find any transaction across all connected Camel in
## Context
- **Agent side**: cameleer3 agent (`https://gitea.siegeln.net/cameleer/cameleer3`) is under active development; already supports creating diagrams and capturing executions
- **Shared library**: `com.cameleer3:cameleer3-common` contains shared models and the graph API; protocol defined in `cameleer3-common/PROTOCOL.md`
- **Agent side**: cameleer agent (`https://gitea.siegeln.net/cameleer/cameleer`) is under active development; already supports creating diagrams and capturing executions
- **Shared library**: `com.cameleer:cameleer-common` contains shared models and the graph API; protocol defined in `cameleer-common/PROTOCOL.md`
- **Data model**: Hierarchical — a **transaction** represents a message's full journey, containing **activities** per route execution. Transactions can span multiple Camel instances (e.g., route A calls route B on another instance via endpoint)
- **Scale target**: Millions of transactions per day, 50+ connected agents, 30-day data retention
- **Query pattern**: Incident-driven — mostly recent data queries, with deep historical dives during incidents
@@ -49,7 +49,7 @@ Users can reliably search and find any transaction across all connected Camel in
## Constraints
- **Tech stack**: Java 17+, Spring Boot 3.4.3, Maven multi-module — already established
- **Dependency**: Must consume `com.cameleer3:cameleer3-common` from Gitea Maven registry
- **Dependency**: Must consume `com.cameleer:cameleer-common` from Gitea Maven registry
- **Protocol**: Agent protocol is still evolving — server must adapt as it stabilizes
- **Incremental delivery**: Build step by step; storage and search first, then layer features on top

View File

@@ -1,4 +1,4 @@
# Requirements: Cameleer3 Server
# Requirements: Cameleer Server
**Defined:** 2026-03-11
**Core Value:** Users can reliably search and find any transaction across all connected Camel instances — by any combination of state, time, duration, or content — even at millions of transactions per day with 30-day retention.

View File

@@ -1,4 +1,4 @@
# Roadmap: Cameleer3 Server
# Roadmap: Cameleer Server
## Overview

View File

@@ -75,7 +75,7 @@ Recent decisions affecting current work:
- [Roadmap]: Phases 2 and 3 can execute in parallel (both depend only on Phase 1)
- [Roadmap]: Web UI deferred to v2
- [Phase 01]: Used spring-boot-starter-jdbc for JdbcTemplate + HikariCP auto-config
- [Phase 01]: Created MetricsSnapshot record in core module (cameleer3-common has no metrics model)
- [Phase 01]: Created MetricsSnapshot record in core module (cameleer-common has no metrics model)
- [Phase 01]: Upgraded testcontainers to 2.0.3 for Docker Desktop 29.x compatibility
- [Phase 01]: Changed error_message/error_stacktrace to non-nullable String for tokenbf_v1 index compat
- [Phase 01]: TTL expressions require toDateTime() cast for DateTime64 columns in ClickHouse 25.3
@@ -121,7 +121,7 @@ None yet.
### Blockers/Concerns
- [Phase 1]: ClickHouse Java client API needs phase-specific research (library has undergone changes)
- [Phase 1]: Must read cameleer3-common PROTOCOL.md before designing ClickHouse schema
- [Phase 1]: Must read cameleer-common PROTOCOL.md before designing ClickHouse schema
- [Phase 2]: Diagram rendering library selection is an open question (Batik, jsvg, JGraphX, or client-side)
- [Phase 2]: ClickHouse skip indexes may not suffice for full-text; decision point during Phase 2

View File

@@ -6,18 +6,18 @@ wave: 1
depends_on: []
files_modified:
- pom.xml
- cameleer3-server-core/pom.xml
- cameleer3-server-app/pom.xml
- cameleer-server-core/pom.xml
- cameleer-server-app/pom.xml
- docker-compose.yml
- clickhouse/init/01-schema.sql
- cameleer3-server-app/src/main/resources/application.yml
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/ClickHouseConfig.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/IngestionConfig.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/ingestion/WriteBuffer.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/storage/ExecutionRepository.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/storage/DiagramRepository.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/storage/MetricsRepository.java
- cameleer3-server-core/src/test/java/com/cameleer3/server/core/ingestion/WriteBufferTest.java
- cameleer-server-app/src/main/resources/application.yml
- cameleer-server-app/src/main/java/com/cameleer/server/app/config/ClickHouseConfig.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/config/IngestionConfig.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/ingestion/WriteBuffer.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/storage/ExecutionRepository.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/storage/DiagramRepository.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/storage/MetricsRepository.java
- cameleer-server-core/src/test/java/com/cameleer/server/core/ingestion/WriteBufferTest.java
autonomous: true
requirements:
- INGST-04
@@ -32,7 +32,7 @@ must_haves:
- "TTL clause on tables removes data older than configured days"
- "Docker Compose starts ClickHouse and initializes the schema"
artifacts:
- path: "cameleer3-server-core/src/main/java/com/cameleer3/server/core/ingestion/WriteBuffer.java"
- path: "cameleer-server-core/src/main/java/com/cameleer/server/core/ingestion/WriteBuffer.java"
provides: "Generic bounded write buffer with offer/drain/isFull"
min_lines: 30
- path: "clickhouse/init/01-schema.sql"
@@ -41,15 +41,15 @@ must_haves:
- path: "docker-compose.yml"
provides: "Local ClickHouse service"
contains: "clickhouse-server"
- path: "cameleer3-server-core/src/main/java/com/cameleer3/server/core/storage/ExecutionRepository.java"
- path: "cameleer-server-core/src/main/java/com/cameleer/server/core/storage/ExecutionRepository.java"
provides: "Repository interface for execution batch inserts"
exports: ["insertBatch"]
key_links:
- from: "cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/ClickHouseConfig.java"
- from: "cameleer-server-app/src/main/java/com/cameleer/server/app/config/ClickHouseConfig.java"
to: "application.yml"
via: "spring.datasource properties"
pattern: "spring\\.datasource"
- from: "cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/IngestionConfig.java"
- from: "cameleer-server-app/src/main/java/com/cameleer/server/app/config/IngestionConfig.java"
to: "application.yml"
via: "ingestion.* properties"
pattern: "ingestion\\."
@@ -74,8 +74,8 @@ Output: Working ClickHouse via Docker Compose, DDL with TTL, WriteBuffer with un
@.planning/phases/01-ingestion-pipeline-api-foundation/01-RESEARCH.md
@pom.xml
@cameleer3-server-core/pom.xml
@cameleer3-server-app/pom.xml
@cameleer-server-core/pom.xml
@cameleer-server-app/pom.xml
</context>
<tasks>
@@ -84,11 +84,11 @@ Output: Working ClickHouse via Docker Compose, DDL with TTL, WriteBuffer with un
<name>Task 1: Dependencies, Docker Compose, ClickHouse schema, and application config</name>
<files>
pom.xml,
cameleer3-server-core/pom.xml,
cameleer3-server-app/pom.xml,
cameleer-server-core/pom.xml,
cameleer-server-app/pom.xml,
docker-compose.yml,
clickhouse/init/01-schema.sql,
cameleer3-server-app/src/main/resources/application.yml
cameleer-server-app/src/main/resources/application.yml
</files>
<behavior>
- docker compose up -d starts ClickHouse on ports 8123/9000
@@ -101,7 +101,7 @@ Output: Working ClickHouse via Docker Compose, DDL with TTL, WriteBuffer with un
- Maven compile succeeds with new dependencies
</behavior>
<action>
1. Add dependencies to cameleer3-server-app/pom.xml per research:
1. Add dependencies to cameleer-server-app/pom.xml per research:
- clickhouse-jdbc 0.9.7 (classifier: all)
- spring-boot-starter-actuator
- springdoc-openapi-starter-webmvc-ui 2.8.6
@@ -109,13 +109,13 @@ Output: Working ClickHouse via Docker Compose, DDL with TTL, WriteBuffer with un
- junit-jupiter from testcontainers 2.0.2 (test scope)
- awaitility (test scope)
2. Add slf4j-api dependency to cameleer3-server-core/pom.xml.
2. Add slf4j-api dependency to cameleer-server-core/pom.xml.
3. Create docker-compose.yml at project root with ClickHouse service:
- Image: clickhouse/clickhouse-server:25.3
- Ports: 8123:8123, 9000:9000
- Volume mount ./clickhouse/init to /docker-entrypoint-initdb.d
- Environment: CLICKHOUSE_USER=cameleer, CLICKHOUSE_PASSWORD=cameleer_dev, CLICKHOUSE_DB=cameleer3
- Environment: CLICKHOUSE_USER=cameleer, CLICKHOUSE_PASSWORD=cameleer_dev, CLICKHOUSE_DB=cameleer
- ulimits nofile 262144
4. Create clickhouse/init/01-schema.sql with the three tables from research:
@@ -124,9 +124,9 @@ Output: Working ClickHouse via Docker Compose, DDL with TTL, WriteBuffer with un
- agent_metrics: MergeTree, daily partitioning on collected_at, ORDER BY (agent_id, metric_name, collected_at), TTL collected_at + INTERVAL 30 DAY, SETTINGS ttl_only_drop_parts=1.
- All DateTime fields use DateTime64(3, 'UTC').
5. Create cameleer3-server-app/src/main/resources/application.yml with config from research:
5. Create cameleer-server-app/src/main/resources/application.yml with config from research:
- server.port: 8081
- spring.datasource: url=jdbc:ch://localhost:8123/cameleer3, username/password, driver-class-name
- spring.datasource: url=jdbc:ch://localhost:8123/cameleer, username/password, driver-class-name
- spring.jackson: write-dates-as-timestamps=false, fail-on-unknown-properties=false
- ingestion: buffer-capacity=50000, batch-size=5000, flush-interval-ms=1000
- clickhouse.ttl-days: 30
@@ -144,13 +144,13 @@ Output: Working ClickHouse via Docker Compose, DDL with TTL, WriteBuffer with un
<task type="auto" tdd="true">
<name>Task 2: WriteBuffer, repository interfaces, IngestionConfig, and ClickHouseConfig</name>
<files>
cameleer3-server-core/src/main/java/com/cameleer3/server/core/ingestion/WriteBuffer.java,
cameleer3-server-core/src/main/java/com/cameleer3/server/core/storage/ExecutionRepository.java,
cameleer3-server-core/src/main/java/com/cameleer3/server/core/storage/DiagramRepository.java,
cameleer3-server-core/src/main/java/com/cameleer3/server/core/storage/MetricsRepository.java,
cameleer3-server-core/src/test/java/com/cameleer3/server/core/ingestion/WriteBufferTest.java,
cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/ClickHouseConfig.java,
cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/IngestionConfig.java
cameleer-server-core/src/main/java/com/cameleer/server/core/ingestion/WriteBuffer.java,
cameleer-server-core/src/main/java/com/cameleer/server/core/storage/ExecutionRepository.java,
cameleer-server-core/src/main/java/com/cameleer/server/core/storage/DiagramRepository.java,
cameleer-server-core/src/main/java/com/cameleer/server/core/storage/MetricsRepository.java,
cameleer-server-core/src/test/java/com/cameleer/server/core/ingestion/WriteBufferTest.java,
cameleer-server-app/src/main/java/com/cameleer/server/app/config/ClickHouseConfig.java,
cameleer-server-app/src/main/java/com/cameleer/server/app/config/IngestionConfig.java
</files>
<behavior>
- WriteBuffer(capacity=10): offer() returns true for first 10 items, false on 11th
@@ -181,7 +181,7 @@ Output: Working ClickHouse via Docker Compose, DDL with TTL, WriteBuffer with un
3. Create repository interfaces in core module:
- ExecutionRepository: void insertBatch(List<RouteExecution> executions)
- DiagramRepository: void store(RouteGraph graph), Optional<RouteGraph> findByContentHash(String hash), Optional<String> findContentHashForRoute(String routeId, String agentId)
- MetricsRepository: void insertBatch(List<MetricsSnapshot> metrics) -- use a generic type or the cameleer3-common metrics model if available; if not, create a simple MetricsData record in core module
- MetricsRepository: void insertBatch(List<MetricsSnapshot> metrics) -- use a generic type or the cameleer-common metrics model if available; if not, create a simple MetricsData record in core module
4. Create IngestionConfig as @ConfigurationProperties("ingestion"):
- bufferCapacity (int, default 50000)
@@ -193,7 +193,7 @@ Output: Working ClickHouse via Docker Compose, DDL with TTL, WriteBuffer with un
- No custom bean needed if relying on auto-config; only create if explicit JdbcTemplate customization required
</action>
<verify>
<automated>mvn test -pl cameleer3-server-core -Dtest=WriteBufferTest -q 2>&1 | tail -10</automated>
<automated>mvn test -pl cameleer-server-core -Dtest=WriteBufferTest -q 2>&1 | tail -10</automated>
</verify>
<done>WriteBuffer passes all unit tests. Repository interfaces exist with correct method signatures. IngestionConfig reads from application.yml.</done>
</task>
@@ -201,7 +201,7 @@ Output: Working ClickHouse via Docker Compose, DDL with TTL, WriteBuffer with un
</tasks>
<verification>
- `mvn test -pl cameleer3-server-core -q` -- all WriteBuffer unit tests pass
- `mvn test -pl cameleer-server-core -q` -- all WriteBuffer unit tests pass
- `mvn clean compile -q` -- full project compiles with new dependencies
- `docker compose config` -- validates Docker Compose file
- clickhouse/init/01-schema.sql contains CREATE TABLE for all three tables with correct ENGINE, ORDER BY, PARTITION BY, and TTL

View File

@@ -26,22 +26,22 @@ key-files:
created:
- docker-compose.yml
- clickhouse/init/01-schema.sql
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/ingestion/WriteBuffer.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/storage/ExecutionRepository.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/storage/DiagramRepository.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/storage/MetricsRepository.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/storage/model/MetricsSnapshot.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/IngestionConfig.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/ClickHouseConfig.java
- cameleer3-server-core/src/test/java/com/cameleer3/server/core/ingestion/WriteBufferTest.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/ingestion/WriteBuffer.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/storage/ExecutionRepository.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/storage/DiagramRepository.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/storage/MetricsRepository.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/storage/model/MetricsSnapshot.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/config/IngestionConfig.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/config/ClickHouseConfig.java
- cameleer-server-core/src/test/java/com/cameleer/server/core/ingestion/WriteBufferTest.java
modified:
- cameleer3-server-core/pom.xml
- cameleer3-server-app/pom.xml
- cameleer3-server-app/src/main/resources/application.yml
- cameleer-server-core/pom.xml
- cameleer-server-app/pom.xml
- cameleer-server-app/src/main/resources/application.yml
key-decisions:
- "Used spring-boot-starter-jdbc for JdbcTemplate + HikariCP auto-config rather than manual DataSource"
- "Created MetricsSnapshot record in core module since cameleer3-common has no metrics model"
- "Created MetricsSnapshot record in core module since cameleer-common has no metrics model"
- "ClickHouseConfig exposes JdbcTemplate bean; relies on Spring Boot DataSource auto-config"
patterns-established:
@@ -84,21 +84,21 @@ Each task was committed atomically:
## Files Created/Modified
- `docker-compose.yml` - ClickHouse service with ports 8123/9000, init volume mount
- `clickhouse/init/01-schema.sql` - DDL for route_executions, route_diagrams, agent_metrics
- `cameleer3-server-core/src/main/java/.../ingestion/WriteBuffer.java` - Bounded queue with offer/offerBatch/drain
- `cameleer3-server-core/src/main/java/.../storage/ExecutionRepository.java` - Batch insert interface for RouteExecution
- `cameleer3-server-core/src/main/java/.../storage/DiagramRepository.java` - Store/find interface for RouteGraph
- `cameleer3-server-core/src/main/java/.../storage/MetricsRepository.java` - Batch insert interface for MetricsSnapshot
- `cameleer3-server-core/src/main/java/.../storage/model/MetricsSnapshot.java` - Metrics data record
- `cameleer3-server-app/src/main/java/.../config/IngestionConfig.java` - Buffer capacity, batch size, flush interval
- `cameleer3-server-app/src/main/java/.../config/ClickHouseConfig.java` - JdbcTemplate bean
- `cameleer3-server-core/src/test/java/.../ingestion/WriteBufferTest.java` - 10 unit tests for WriteBuffer
- `cameleer3-server-core/pom.xml` - Added slf4j-api
- `cameleer3-server-app/pom.xml` - Added clickhouse-jdbc, springdoc, actuator, testcontainers, awaitility
- `cameleer3-server-app/src/main/resources/application.yml` - Full config with datasource, ingestion, springdoc, actuator
- `cameleer-server-core/src/main/java/.../ingestion/WriteBuffer.java` - Bounded queue with offer/offerBatch/drain
- `cameleer-server-core/src/main/java/.../storage/ExecutionRepository.java` - Batch insert interface for RouteExecution
- `cameleer-server-core/src/main/java/.../storage/DiagramRepository.java` - Store/find interface for RouteGraph
- `cameleer-server-core/src/main/java/.../storage/MetricsRepository.java` - Batch insert interface for MetricsSnapshot
- `cameleer-server-core/src/main/java/.../storage/model/MetricsSnapshot.java` - Metrics data record
- `cameleer-server-app/src/main/java/.../config/IngestionConfig.java` - Buffer capacity, batch size, flush interval
- `cameleer-server-app/src/main/java/.../config/ClickHouseConfig.java` - JdbcTemplate bean
- `cameleer-server-core/src/test/java/.../ingestion/WriteBufferTest.java` - 10 unit tests for WriteBuffer
- `cameleer-server-core/pom.xml` - Added slf4j-api
- `cameleer-server-app/pom.xml` - Added clickhouse-jdbc, springdoc, actuator, testcontainers, awaitility
- `cameleer-server-app/src/main/resources/application.yml` - Full config with datasource, ingestion, springdoc, actuator
## Decisions Made
- Used spring-boot-starter-jdbc to get JdbcTemplate and HikariCP auto-configuration rather than manually wiring a DataSource
- Created MetricsSnapshot record in core module since cameleer3-common does not include a metrics model
- Created MetricsSnapshot record in core module since cameleer-common does not include a metrics model
- ClickHouseConfig is minimal -- relies on Spring Boot auto-configuring DataSource from spring.datasource properties
## Deviations from Plan

View File

@@ -5,18 +5,18 @@ type: execute
wave: 3
depends_on: ["01-01", "01-03"]
files_modified:
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/ExecutionController.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/DiagramController.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/MetricsController.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/ClickHouseExecutionRepository.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/ClickHouseDiagramRepository.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/ClickHouseMetricsRepository.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/ingestion/ClickHouseFlushScheduler.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/ingestion/IngestionService.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/ExecutionControllerIT.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/DiagramControllerIT.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/MetricsControllerIT.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/BackpressureIT.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/controller/ExecutionController.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/controller/DiagramController.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/controller/MetricsController.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/storage/ClickHouseExecutionRepository.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/storage/ClickHouseDiagramRepository.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/storage/ClickHouseMetricsRepository.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/ingestion/ClickHouseFlushScheduler.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/ingestion/IngestionService.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/controller/ExecutionControllerIT.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/controller/DiagramControllerIT.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/controller/MetricsControllerIT.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/controller/BackpressureIT.java
autonomous: true
requirements:
- INGST-01
@@ -32,16 +32,16 @@ must_haves:
- "Data posted to endpoints appears in ClickHouse after flush interval"
- "When buffer is full, endpoints return 503 with Retry-After header"
artifacts:
- path: "cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/ExecutionController.java"
- path: "cameleer-server-app/src/main/java/com/cameleer/server/app/controller/ExecutionController.java"
provides: "POST /api/v1/data/executions endpoint"
min_lines: 20
- path: "cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/ClickHouseExecutionRepository.java"
- path: "cameleer-server-app/src/main/java/com/cameleer/server/app/storage/ClickHouseExecutionRepository.java"
provides: "Batch insert to route_executions table via JdbcTemplate"
min_lines: 30
- path: "cameleer3-server-app/src/main/java/com/cameleer3/server/app/ingestion/ClickHouseFlushScheduler.java"
- path: "cameleer-server-app/src/main/java/com/cameleer/server/app/ingestion/ClickHouseFlushScheduler.java"
provides: "Scheduled drain of WriteBuffer into ClickHouse"
min_lines: 20
- path: "cameleer3-server-core/src/main/java/com/cameleer3/server/core/ingestion/IngestionService.java"
- path: "cameleer-server-core/src/main/java/com/cameleer/server/core/ingestion/IngestionService.java"
provides: "Routes data to appropriate WriteBuffer instances"
min_lines: 20
key_links:
@@ -92,7 +92,7 @@ Output: Working ingestion flow verified by integration tests against Testcontain
<!-- Interfaces from Plan 01 that this plan depends on -->
<interfaces>
From cameleer3-server-core WriteBuffer.java:
From cameleer-server-core WriteBuffer.java:
```java
public class WriteBuffer<T> {
public WriteBuffer(int capacity);
@@ -106,7 +106,7 @@ public class WriteBuffer<T> {
}
```
From cameleer3-server-core repository interfaces:
From cameleer-server-core repository interfaces:
```java
public interface ExecutionRepository {
void insertBatch(List<RouteExecution> executions);
@@ -138,11 +138,11 @@ public class IngestionConfig {
<task type="auto" tdd="false">
<name>Task 1: IngestionService, ClickHouse repositories, and flush scheduler</name>
<files>
cameleer3-server-core/src/main/java/com/cameleer3/server/core/ingestion/IngestionService.java,
cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/ClickHouseExecutionRepository.java,
cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/ClickHouseDiagramRepository.java,
cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/ClickHouseMetricsRepository.java,
cameleer3-server-app/src/main/java/com/cameleer3/server/app/ingestion/ClickHouseFlushScheduler.java
cameleer-server-core/src/main/java/com/cameleer/server/core/ingestion/IngestionService.java,
cameleer-server-app/src/main/java/com/cameleer/server/app/storage/ClickHouseExecutionRepository.java,
cameleer-server-app/src/main/java/com/cameleer/server/app/storage/ClickHouseDiagramRepository.java,
cameleer-server-app/src/main/java/com/cameleer/server/app/storage/ClickHouseMetricsRepository.java,
cameleer-server-app/src/main/java/com/cameleer/server/app/ingestion/ClickHouseFlushScheduler.java
</files>
<action>
1. Create IngestionService in core module (no Spring annotations -- it's a plain class):
@@ -188,13 +188,13 @@ public class IngestionConfig {
<task type="auto" tdd="true">
<name>Task 2: Ingestion REST controllers and integration tests</name>
<files>
cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/ExecutionController.java,
cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/DiagramController.java,
cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/MetricsController.java,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/ExecutionControllerIT.java,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/DiagramControllerIT.java,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/MetricsControllerIT.java,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/BackpressureIT.java
cameleer-server-app/src/main/java/com/cameleer/server/app/controller/ExecutionController.java,
cameleer-server-app/src/main/java/com/cameleer/server/app/controller/DiagramController.java,
cameleer-server-app/src/main/java/com/cameleer/server/app/controller/MetricsController.java,
cameleer-server-app/src/test/java/com/cameleer/server/app/controller/ExecutionControllerIT.java,
cameleer-server-app/src/test/java/com/cameleer/server/app/controller/DiagramControllerIT.java,
cameleer-server-app/src/test/java/com/cameleer/server/app/controller/MetricsControllerIT.java,
cameleer-server-app/src/test/java/com/cameleer/server/app/controller/BackpressureIT.java
</files>
<behavior>
- POST /api/v1/data/executions with single RouteExecution JSON returns 202
@@ -245,7 +245,7 @@ public class IngestionConfig {
Note: All integration tests must include X-Cameleer-Protocol-Version:1 header (API-04 will be enforced by Plan 03's interceptor, but include the header now for forward compatibility).
</action>
<verify>
<automated>mvn test -pl cameleer3-server-app -Dtest="ExecutionControllerIT,DiagramControllerIT,MetricsControllerIT,BackpressureIT" -q 2>&1 | tail -15</automated>
<automated>mvn test -pl cameleer-server-app -Dtest="ExecutionControllerIT,DiagramControllerIT,MetricsControllerIT,BackpressureIT" -q 2>&1 | tail -15</automated>
</verify>
<done>All three ingestion endpoints return 202 on valid data. Data arrives in ClickHouse after flush. Buffer-full returns 503 with Retry-After. Unknown JSON fields accepted. Integration tests green.</done>
</task>
@@ -253,7 +253,7 @@ public class IngestionConfig {
</tasks>
<verification>
- `mvn test -pl cameleer3-server-app -Dtest="ExecutionControllerIT,DiagramControllerIT,MetricsControllerIT,BackpressureIT" -q` -- all integration tests pass
- `mvn test -pl cameleer-server-app -Dtest="ExecutionControllerIT,DiagramControllerIT,MetricsControllerIT,BackpressureIT" -q` -- all integration tests pass
- POST to /api/v1/data/executions returns 202
- POST to /api/v1/data/diagrams returns 202
- POST to /api/v1/data/metrics returns 202

View File

@@ -30,21 +30,21 @@ tech-stack:
key-files:
created:
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/ingestion/IngestionService.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/ClickHouseExecutionRepository.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/ClickHouseDiagramRepository.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/ClickHouseMetricsRepository.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/ingestion/ClickHouseFlushScheduler.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/IngestionBeanConfig.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/ExecutionController.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/DiagramController.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/MetricsController.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/ExecutionControllerIT.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/DiagramControllerIT.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/MetricsControllerIT.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/BackpressureIT.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/ingestion/IngestionService.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/storage/ClickHouseExecutionRepository.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/storage/ClickHouseDiagramRepository.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/storage/ClickHouseMetricsRepository.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/ingestion/ClickHouseFlushScheduler.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/config/IngestionBeanConfig.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/controller/ExecutionController.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/controller/DiagramController.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/controller/MetricsController.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/controller/ExecutionControllerIT.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/controller/DiagramControllerIT.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/controller/MetricsControllerIT.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/controller/BackpressureIT.java
modified:
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/IngestionConfig.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/config/IngestionConfig.java
key-decisions:
- "Controllers accept raw String body and detect single vs array JSON to support both payload formats"
@@ -91,20 +91,20 @@ Each task was committed atomically:
3. **Task 2 GREEN: Ingestion REST controllers with backpressure** - `8fe65f0` (feat)
## Files Created/Modified
- `cameleer3-server-core/.../ingestion/IngestionService.java` - Routes data to WriteBuffer instances
- `cameleer3-server-app/.../storage/ClickHouseExecutionRepository.java` - Batch insert with parallel processor arrays
- `cameleer3-server-app/.../storage/ClickHouseDiagramRepository.java` - JSON storage with SHA-256 content-hash dedup
- `cameleer3-server-app/.../storage/ClickHouseMetricsRepository.java` - Batch insert for agent_metrics
- `cameleer3-server-app/.../ingestion/ClickHouseFlushScheduler.java` - Scheduled drain + SmartLifecycle shutdown
- `cameleer3-server-app/.../config/IngestionBeanConfig.java` - WriteBuffer and IngestionService bean wiring
- `cameleer3-server-app/.../controller/ExecutionController.java` - POST /api/v1/data/executions
- `cameleer3-server-app/.../controller/DiagramController.java` - POST /api/v1/data/diagrams
- `cameleer3-server-app/.../controller/MetricsController.java` - POST /api/v1/data/metrics
- `cameleer3-server-app/.../config/IngestionConfig.java` - Removed @Configuration (fix duplicate bean)
- `cameleer3-server-app/.../controller/ExecutionControllerIT.java` - 4 tests: single, array, flush, unknown fields
- `cameleer3-server-app/.../controller/DiagramControllerIT.java` - 3 tests: single, array, flush
- `cameleer3-server-app/.../controller/MetricsControllerIT.java` - 2 tests: POST, flush
- `cameleer3-server-app/.../controller/BackpressureIT.java` - 2 tests: 503 response, data not lost
- `cameleer-server-core/.../ingestion/IngestionService.java` - Routes data to WriteBuffer instances
- `cameleer-server-app/.../storage/ClickHouseExecutionRepository.java` - Batch insert with parallel processor arrays
- `cameleer-server-app/.../storage/ClickHouseDiagramRepository.java` - JSON storage with SHA-256 content-hash dedup
- `cameleer-server-app/.../storage/ClickHouseMetricsRepository.java` - Batch insert for agent_metrics
- `cameleer-server-app/.../ingestion/ClickHouseFlushScheduler.java` - Scheduled drain + SmartLifecycle shutdown
- `cameleer-server-app/.../config/IngestionBeanConfig.java` - WriteBuffer and IngestionService bean wiring
- `cameleer-server-app/.../controller/ExecutionController.java` - POST /api/v1/data/executions
- `cameleer-server-app/.../controller/DiagramController.java` - POST /api/v1/data/diagrams
- `cameleer-server-app/.../controller/MetricsController.java` - POST /api/v1/data/metrics
- `cameleer-server-app/.../config/IngestionConfig.java` - Removed @Configuration (fix duplicate bean)
- `cameleer-server-app/.../controller/ExecutionControllerIT.java` - 4 tests: single, array, flush, unknown fields
- `cameleer-server-app/.../controller/DiagramControllerIT.java` - 3 tests: single, array, flush
- `cameleer-server-app/.../controller/MetricsControllerIT.java` - 2 tests: POST, flush
- `cameleer-server-app/.../controller/BackpressureIT.java` - 2 tests: 503 response, data not lost
## Decisions Made
- Controllers accept raw String body and detect single vs array JSON (starts with `[`), supporting both payload formats per protocol spec
@@ -119,7 +119,7 @@ Each task was committed atomically:
- **Found during:** Task 2 (integration test context startup)
- **Issue:** IngestionConfig had both `@Configuration` and `@ConfigurationProperties`, while `@EnableConfigurationProperties(IngestionConfig.class)` on the app class created a second bean, causing "expected single matching bean but found 2"
- **Fix:** Removed `@Configuration` from IngestionConfig, relying solely on `@EnableConfigurationProperties`
- **Files modified:** cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/IngestionConfig.java
- **Files modified:** cameleer-server-app/src/main/java/com/cameleer/server/app/config/IngestionConfig.java
- **Verification:** Application context starts successfully, all tests pass
- **Committed in:** 8fe65f0

View File

@@ -5,15 +5,15 @@ type: execute
wave: 2
depends_on: ["01-01"]
files_modified:
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/interceptor/ProtocolVersionInterceptor.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/WebConfig.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/Cameleer3ServerApplication.java
- cameleer3-server-app/src/test/resources/application-test.yml
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/AbstractClickHouseIT.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/interceptor/ProtocolVersionIT.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/HealthControllerIT.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/OpenApiIT.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/ForwardCompatIT.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/interceptor/ProtocolVersionInterceptor.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/config/WebConfig.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/CameleerServerApplication.java
- cameleer-server-app/src/test/resources/application-test.yml
- cameleer-server-app/src/test/java/com/cameleer/server/app/AbstractClickHouseIT.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/interceptor/ProtocolVersionIT.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/controller/HealthControllerIT.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/controller/OpenApiIT.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/controller/ForwardCompatIT.java
autonomous: true
requirements:
- API-01
@@ -34,13 +34,13 @@ must_haves:
- "Unknown JSON fields in request body do not cause deserialization errors"
- "ClickHouse tables have TTL clause for 30-day retention"
artifacts:
- path: "cameleer3-server-app/src/main/java/com/cameleer3/server/app/interceptor/ProtocolVersionInterceptor.java"
- path: "cameleer-server-app/src/main/java/com/cameleer/server/app/interceptor/ProtocolVersionInterceptor.java"
provides: "Validates X-Cameleer-Protocol-Version:1 header on data endpoints"
min_lines: 20
- path: "cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/WebConfig.java"
- path: "cameleer-server-app/src/main/java/com/cameleer/server/app/config/WebConfig.java"
provides: "Registers interceptor with path patterns"
min_lines: 10
- path: "cameleer3-server-app/src/test/java/com/cameleer3/server/app/AbstractClickHouseIT.java"
- path: "cameleer-server-app/src/test/java/com/cameleer/server/app/AbstractClickHouseIT.java"
provides: "Shared Testcontainers base class for integration tests"
min_lines: 20
key_links:
@@ -83,11 +83,11 @@ Output: AbstractClickHouseIT base class, working health, Swagger UI, protocol he
<task type="auto">
<name>Task 1: Test infrastructure, protocol version interceptor, WebConfig, and Spring Boot application class</name>
<files>
cameleer3-server-app/src/test/resources/application-test.yml,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/AbstractClickHouseIT.java,
cameleer3-server-app/src/main/java/com/cameleer3/server/app/interceptor/ProtocolVersionInterceptor.java,
cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/WebConfig.java,
cameleer3-server-app/src/main/java/com/cameleer3/server/app/Cameleer3ServerApplication.java
cameleer-server-app/src/test/resources/application-test.yml,
cameleer-server-app/src/test/java/com/cameleer/server/app/AbstractClickHouseIT.java,
cameleer-server-app/src/main/java/com/cameleer/server/app/interceptor/ProtocolVersionInterceptor.java,
cameleer-server-app/src/main/java/com/cameleer/server/app/config/WebConfig.java,
cameleer-server-app/src/main/java/com/cameleer/server/app/CameleerServerApplication.java
</files>
<action>
1. Create application-test.yml for test profile:
@@ -113,15 +113,15 @@ Output: AbstractClickHouseIT base class, working health, Swagger UI, protocol he
- Override addInterceptors: register interceptor with pathPatterns "/api/v1/data/**" and "/api/v1/agents/**"
- Explicitly EXCLUDE: "/api/v1/health", "/api/v1/api-docs/**", "/api/v1/swagger-ui/**", "/api/v1/swagger-ui.html"
5. Create or update Cameleer3ServerApplication:
- @SpringBootApplication in package com.cameleer3.server.app
5. Create or update CameleerServerApplication:
- @SpringBootApplication in package com.cameleer.server.app
- @EnableScheduling (needed for ClickHouseFlushScheduler from Plan 02)
- @EnableConfigurationProperties(IngestionConfig.class)
- Main method with SpringApplication.run()
- Ensure package scanning covers com.cameleer3.server.app and com.cameleer3.server.core
- Ensure package scanning covers com.cameleer.server.app and com.cameleer.server.core
</action>
<verify>
<automated>mvn clean compile -pl cameleer3-server-app -q 2>&1 | tail -5</automated>
<automated>mvn clean compile -pl cameleer-server-app -q 2>&1 | tail -5</automated>
</verify>
<done>AbstractClickHouseIT base class ready for integration tests. ProtocolVersionInterceptor validates header on data/agent paths. Health, swagger, and api-docs paths excluded. Application class enables scheduling and config properties.</done>
</task>
@@ -129,10 +129,10 @@ Output: AbstractClickHouseIT base class, working health, Swagger UI, protocol he
<task type="auto" tdd="true">
<name>Task 2: Health, OpenAPI, protocol version, forward compat, and TTL integration tests</name>
<files>
cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/HealthControllerIT.java,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/OpenApiIT.java,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/interceptor/ProtocolVersionIT.java,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/ForwardCompatIT.java
cameleer-server-app/src/test/java/com/cameleer/server/app/controller/HealthControllerIT.java,
cameleer-server-app/src/test/java/com/cameleer/server/app/controller/OpenApiIT.java,
cameleer-server-app/src/test/java/com/cameleer/server/app/interceptor/ProtocolVersionIT.java,
cameleer-server-app/src/test/java/com/cameleer/server/app/controller/ForwardCompatIT.java
</files>
<behavior>
- GET /api/v1/health returns 200 with JSON containing status field
@@ -178,7 +178,7 @@ Output: AbstractClickHouseIT base class, working health, Swagger UI, protocol he
Note: All tests that POST to data endpoints must include X-Cameleer-Protocol-Version:1 header.
</action>
<verify>
<automated>mvn test -pl cameleer3-server-app -Dtest="HealthControllerIT,OpenApiIT,ProtocolVersionIT,ForwardCompatIT" -q 2>&1 | tail -15</automated>
<automated>mvn test -pl cameleer-server-app -Dtest="HealthControllerIT,OpenApiIT,ProtocolVersionIT,ForwardCompatIT" -q 2>&1 | tail -15</automated>
</verify>
<done>Health returns 200. OpenAPI docs are available and list endpoints. Protocol version header enforced on data paths, not on health/docs. Unknown JSON fields accepted. TTL confirmed in ClickHouse DDL via HealthControllerIT test methods.</done>
</task>
@@ -186,7 +186,7 @@ Output: AbstractClickHouseIT base class, working health, Swagger UI, protocol he
</tasks>
<verification>
- `mvn test -pl cameleer3-server-app -Dtest="HealthControllerIT,OpenApiIT,ProtocolVersionIT,ForwardCompatIT" -q` -- all tests pass
- `mvn test -pl cameleer-server-app -Dtest="HealthControllerIT,OpenApiIT,ProtocolVersionIT,ForwardCompatIT" -q` -- all tests pass
- GET /api/v1/health returns 200
- GET /api/v1/api-docs returns OpenAPI spec
- Missing protocol header returns 400 on data endpoints

View File

@@ -12,7 +12,7 @@ provides:
- AbstractClickHouseIT base class for all integration tests
- ProtocolVersionInterceptor enforcing X-Cameleer-Protocol-Version:1 on data/agent paths
- WebConfig with interceptor registration and path exclusions
- Cameleer3ServerApplication with @EnableScheduling and component scanning
- CameleerServerApplication with @EnableScheduling and component scanning
- 12 passing integration tests (health, OpenAPI, protocol version, forward compat, TTL)
affects: [01-02, 02-search, 03-agent-registry]
@@ -23,17 +23,17 @@ tech-stack:
key-files:
created:
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/Cameleer3ServerApplication.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/interceptor/ProtocolVersionInterceptor.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/WebConfig.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/AbstractClickHouseIT.java
- cameleer3-server-app/src/test/resources/application-test.yml
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/HealthControllerIT.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/OpenApiIT.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/interceptor/ProtocolVersionIT.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/ForwardCompatIT.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/CameleerServerApplication.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/interceptor/ProtocolVersionInterceptor.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/config/WebConfig.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/AbstractClickHouseIT.java
- cameleer-server-app/src/test/resources/application-test.yml
- cameleer-server-app/src/test/java/com/cameleer/server/app/controller/HealthControllerIT.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/controller/OpenApiIT.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/interceptor/ProtocolVersionIT.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/controller/ForwardCompatIT.java
modified:
- cameleer3-server-app/pom.xml
- cameleer-server-app/pom.xml
- pom.xml
- clickhouse/init/01-schema.sql
@@ -70,7 +70,7 @@ completed: 2026-03-11
- ProtocolVersionInterceptor validates X-Cameleer-Protocol-Version:1 on /api/v1/data/** and /api/v1/agents/** paths, returning 400 JSON error for missing or wrong version
- AbstractClickHouseIT base class with Testcontainers ClickHouse 25.3, shared static container, schema init from 01-schema.sql
- 12 integration tests: health endpoint (2), OpenAPI docs (2), protocol version enforcement (5), forward compatibility (1), TTL verification (2)
- Cameleer3ServerApplication with @EnableScheduling, @EnableConfigurationProperties, and dual package scanning
- CameleerServerApplication with @EnableScheduling, @EnableConfigurationProperties, and dual package scanning
## Task Commits
@@ -80,17 +80,17 @@ Each task was committed atomically:
2. **Task 2: Integration tests for health, OpenAPI, protocol version, forward compat, and TTL** - `2d3fde3` (test)
## Files Created/Modified
- `cameleer3-server-app/src/main/java/.../Cameleer3ServerApplication.java` - Spring Boot entry point with scheduling and config properties
- `cameleer3-server-app/src/main/java/.../interceptor/ProtocolVersionInterceptor.java` - Validates protocol version header on data/agent paths
- `cameleer3-server-app/src/main/java/.../config/WebConfig.java` - Registers interceptor with path patterns and exclusions
- `cameleer3-server-app/src/test/java/.../AbstractClickHouseIT.java` - Shared Testcontainers base class for ITs
- `cameleer3-server-app/src/test/resources/application-test.yml` - Test profile with small buffer config
- `cameleer3-server-app/src/test/java/.../controller/HealthControllerIT.java` - Health endpoint and TTL tests
- `cameleer3-server-app/src/test/java/.../controller/OpenApiIT.java` - OpenAPI and Swagger UI tests
- `cameleer3-server-app/src/test/java/.../interceptor/ProtocolVersionIT.java` - Protocol header enforcement tests
- `cameleer3-server-app/src/test/java/.../controller/ForwardCompatIT.java` - Unknown JSON fields test
- `cameleer-server-app/src/main/java/.../CameleerServerApplication.java` - Spring Boot entry point with scheduling and config properties
- `cameleer-server-app/src/main/java/.../interceptor/ProtocolVersionInterceptor.java` - Validates protocol version header on data/agent paths
- `cameleer-server-app/src/main/java/.../config/WebConfig.java` - Registers interceptor with path patterns and exclusions
- `cameleer-server-app/src/test/java/.../AbstractClickHouseIT.java` - Shared Testcontainers base class for ITs
- `cameleer-server-app/src/test/resources/application-test.yml` - Test profile with small buffer config
- `cameleer-server-app/src/test/java/.../controller/HealthControllerIT.java` - Health endpoint and TTL tests
- `cameleer-server-app/src/test/java/.../controller/OpenApiIT.java` - OpenAPI and Swagger UI tests
- `cameleer-server-app/src/test/java/.../interceptor/ProtocolVersionIT.java` - Protocol header enforcement tests
- `cameleer-server-app/src/test/java/.../controller/ForwardCompatIT.java` - Unknown JSON fields test
- `pom.xml` - Override testcontainers.version to 2.0.3
- `cameleer3-server-app/pom.xml` - Remove junit-jupiter, upgrade testcontainers-clickhouse to 2.0.3
- `cameleer-server-app/pom.xml` - Remove junit-jupiter, upgrade testcontainers-clickhouse to 2.0.3
- `clickhouse/init/01-schema.sql` - Fix TTL expressions and error column types
## Decisions Made
@@ -107,7 +107,7 @@ Each task was committed atomically:
- **Found during:** Task 2 (compilation)
- **Issue:** org.testcontainers:junit-jupiter:2.0.2 does not exist in Maven Central
- **Fix:** Removed junit-jupiter dependency, upgraded to TC 2.0.3, managed container lifecycle manually via static initializer
- **Files modified:** cameleer3-server-app/pom.xml, pom.xml, AbstractClickHouseIT.java
- **Files modified:** cameleer-server-app/pom.xml, pom.xml, AbstractClickHouseIT.java
- **Verification:** All tests compile and pass
- **Committed in:** 2d3fde3

View File

@@ -6,7 +6,7 @@
## Summary
Phase 1 establishes the data pipeline and API skeleton for Cameleer3 Server. Agents POST execution data, diagrams, and metrics to REST endpoints; the server buffers these in memory and batch-flushes to ClickHouse. The ClickHouse schema design is the most critical and least reversible decision in this phase -- ORDER BY and partitioning cannot be changed without table recreation.
Phase 1 establishes the data pipeline and API skeleton for Cameleer Server. Agents POST execution data, diagrams, and metrics to REST endpoints; the server buffers these in memory and batch-flushes to ClickHouse. The ClickHouse schema design is the most critical and least reversible decision in this phase -- ORDER BY and partitioning cannot be changed without table recreation.
The ClickHouse Java ecosystem has undergone significant changes. The recommended approach is **clickhouse-jdbc v0.9.7** (JDBC V2 driver) with Spring Boot's JdbcTemplate for batch inserts. An alternative is the standalone **client-v2** artifact which offers a POJO-based insert API, but JDBC integration with Spring Boot is more conventional and better documented. ClickHouse now has a native full-text index (TYPE text, GA as of March 2026) that supersedes the older tokenbf_v1 bloom filter approach -- this is relevant for Phase 2 but should be accounted for in schema design now.
@@ -17,7 +17,7 @@ The ClickHouse Java ecosystem has undergone significant changes. The recommended
| ID | Description | Research Support |
|----|-------------|-----------------|
| INGST-01 (#1) | Accept RouteExecution via POST /api/v1/data/executions, return 202 | REST controller + async write buffer pattern; Jackson deserialization of cameleer3-common models |
| INGST-01 (#1) | Accept RouteExecution via POST /api/v1/data/executions, return 202 | REST controller + async write buffer pattern; Jackson deserialization of cameleer-common models |
| INGST-02 (#2) | Accept RouteGraph via POST /api/v1/data/diagrams, return 202 | Same pattern; separate ClickHouse table for diagrams with content-hash dedup |
| INGST-03 (#3) | Accept metrics via POST /api/v1/data/metrics, return 202 | Same pattern; separate ClickHouse table for metrics |
| INGST-04 (#4) | In-memory batch buffer with configurable flush interval/size | ArrayBlockingQueue + @Scheduled flush; configurable via application.yml |
@@ -60,7 +60,7 @@ The ClickHouse Java ecosystem has undergone significant changes. The recommended
| ArrayBlockingQueue | LMAX Disruptor | Disruptor is faster under extreme contention but adds complexity; ABQ is sufficient for this throughput |
| Spring JdbcTemplate | Raw JDBC PreparedStatement | JdbcTemplate provides cleaner error handling and resource management; no meaningful overhead |
**Installation (add to cameleer3-server-app/pom.xml):**
**Installation (add to cameleer-server-app/pom.xml):**
```xml
<!-- ClickHouse JDBC V2 -->
<dependency>
@@ -103,7 +103,7 @@ The ClickHouse Java ecosystem has undergone significant changes. The recommended
</dependency>
```
**Add to cameleer3-server-core/pom.xml:**
**Add to cameleer-server-core/pom.xml:**
```xml
<!-- SLF4J for logging (no Spring dependency) -->
<dependency>
@@ -117,7 +117,7 @@ The ClickHouse Java ecosystem has undergone significant changes. The recommended
### Recommended Project Structure
```
cameleer3-server-core/src/main/java/com/cameleer3/server/core/
cameleer-server-core/src/main/java/com/cameleer/server/core/
ingestion/
WriteBuffer.java # Bounded queue + flush logic
IngestionService.java # Accepts data, routes to buffer
@@ -126,9 +126,9 @@ cameleer3-server-core/src/main/java/com/cameleer3/server/core/
DiagramRepository.java # Interface: store/retrieve diagrams
MetricsRepository.java # Interface: store metrics
model/
(extend/complement cameleer3-common models as needed)
(extend/complement cameleer-common models as needed)
cameleer3-server-app/src/main/java/com/cameleer3/server/app/
cameleer-server-app/src/main/java/com/cameleer/server/app/
config/
ClickHouseConfig.java # DataSource + JdbcTemplate bean
IngestionConfig.java # Buffer size, flush interval from YAML
@@ -424,7 +424,7 @@ services:
environment:
CLICKHOUSE_USER: cameleer
CLICKHOUSE_PASSWORD: cameleer_dev
CLICKHOUSE_DB: cameleer3
CLICKHOUSE_DB: cameleer
ulimits:
nofile:
soft: 262144
@@ -442,7 +442,7 @@ server:
spring:
datasource:
url: jdbc:ch://localhost:8123/cameleer3
url: jdbc:ch://localhost:8123/cameleer
username: cameleer
password: cameleer_dev
driver-class-name: com.clickhouse.jdbc.ClickHouseDriver
@@ -493,10 +493,10 @@ management:
## Open Questions
1. **Exact cameleer3-common model structure**
1. **Exact cameleer-common model structure**
- What we know: Models include RouteExecution, ProcessorExecution, ExchangeSnapshot, RouteGraph, RouteNode, RouteEdge
- What's unclear: Exact field names, types, nesting structure -- needed to design ClickHouse schema precisely
- Recommendation: Read cameleer3-common source code before implementing schema. Schema must match the wire format.
- Recommendation: Read cameleer-common source code before implementing schema. Schema must match the wire format.
2. **ClickHouse JDBC V2 + HikariCP compatibility**
- What we know: clickhouse-jdbc 0.9.7 implements JDBC spec; HikariCP is Spring Boot default
@@ -515,36 +515,36 @@ management:
| Property | Value |
|----------|-------|
| Framework | JUnit 5 (Spring Boot managed) + Testcontainers 2.0.2 |
| Config file | cameleer3-server-app/src/test/resources/application-test.yml (Wave 0) |
| Quick run command | `mvn test -pl cameleer3-server-core -Dtest=WriteBufferTest -q` |
| Config file | cameleer-server-app/src/test/resources/application-test.yml (Wave 0) |
| Quick run command | `mvn test -pl cameleer-server-core -Dtest=WriteBufferTest -q` |
| Full suite command | `mvn verify` |
### Phase Requirements -> Test Map
| Req ID | Behavior | Test Type | Automated Command | File Exists? |
|--------|----------|-----------|-------------------|-------------|
| INGST-01 | POST /api/v1/data/executions returns 202, data in ClickHouse | integration | `mvn test -pl cameleer3-server-app -Dtest=ExecutionControllerIT -q` | Wave 0 |
| INGST-02 | POST /api/v1/data/diagrams returns 202 | integration | `mvn test -pl cameleer3-server-app -Dtest=DiagramControllerIT -q` | Wave 0 |
| INGST-03 | POST /api/v1/data/metrics returns 202 | integration | `mvn test -pl cameleer3-server-app -Dtest=MetricsControllerIT -q` | Wave 0 |
| INGST-04 | Buffer flushes at interval/size | unit | `mvn test -pl cameleer3-server-core -Dtest=WriteBufferTest -q` | Wave 0 |
| INGST-05 | 503 when buffer full | unit+integration | `mvn test -pl cameleer3-server-app -Dtest=BackpressureIT -q` | Wave 0 |
| INGST-06 | TTL removes old data | integration | `mvn test -pl cameleer3-server-app -Dtest=ClickHouseTtlIT -q` | Wave 0 |
| INGST-01 | POST /api/v1/data/executions returns 202, data in ClickHouse | integration | `mvn test -pl cameleer-server-app -Dtest=ExecutionControllerIT -q` | Wave 0 |
| INGST-02 | POST /api/v1/data/diagrams returns 202 | integration | `mvn test -pl cameleer-server-app -Dtest=DiagramControllerIT -q` | Wave 0 |
| INGST-03 | POST /api/v1/data/metrics returns 202 | integration | `mvn test -pl cameleer-server-app -Dtest=MetricsControllerIT -q` | Wave 0 |
| INGST-04 | Buffer flushes at interval/size | unit | `mvn test -pl cameleer-server-core -Dtest=WriteBufferTest -q` | Wave 0 |
| INGST-05 | 503 when buffer full | unit+integration | `mvn test -pl cameleer-server-app -Dtest=BackpressureIT -q` | Wave 0 |
| INGST-06 | TTL removes old data | integration | `mvn test -pl cameleer-server-app -Dtest=ClickHouseTtlIT -q` | Wave 0 |
| API-01 | Endpoints under /api/v1/ | integration | Covered by controller ITs | Wave 0 |
| API-02 | OpenAPI docs available | integration | `mvn test -pl cameleer3-server-app -Dtest=OpenApiIT -q` | Wave 0 |
| API-03 | GET /api/v1/health responds | integration | `mvn test -pl cameleer3-server-app -Dtest=HealthControllerIT -q` | Wave 0 |
| API-04 | Protocol version header validated | integration | `mvn test -pl cameleer3-server-app -Dtest=ProtocolVersionIT -q` | Wave 0 |
| API-05 | Unknown JSON fields accepted | unit | `mvn test -pl cameleer3-server-app -Dtest=ForwardCompatIT -q` | Wave 0 |
| API-02 | OpenAPI docs available | integration | `mvn test -pl cameleer-server-app -Dtest=OpenApiIT -q` | Wave 0 |
| API-03 | GET /api/v1/health responds | integration | `mvn test -pl cameleer-server-app -Dtest=HealthControllerIT -q` | Wave 0 |
| API-04 | Protocol version header validated | integration | `mvn test -pl cameleer-server-app -Dtest=ProtocolVersionIT -q` | Wave 0 |
| API-05 | Unknown JSON fields accepted | unit | `mvn test -pl cameleer-server-app -Dtest=ForwardCompatIT -q` | Wave 0 |
### Sampling Rate
- **Per task commit:** `mvn test -pl cameleer3-server-core -q` (unit tests, fast)
- **Per task commit:** `mvn test -pl cameleer-server-core -q` (unit tests, fast)
- **Per wave merge:** `mvn verify` (full suite with Testcontainers integration tests)
- **Phase gate:** Full suite green before verification
### Wave 0 Gaps
- [ ] `cameleer3-server-app/src/test/resources/application-test.yml` -- test ClickHouse config
- [ ] `cameleer3-server-core/src/test/java/.../WriteBufferTest.java` -- buffer unit tests
- [ ] `cameleer3-server-app/src/test/java/.../AbstractClickHouseIT.java` -- shared Testcontainers base class
- [ ] `cameleer3-server-app/src/test/java/.../ExecutionControllerIT.java` -- ingestion integration test
- [ ] `cameleer-server-app/src/test/resources/application-test.yml` -- test ClickHouse config
- [ ] `cameleer-server-core/src/test/java/.../WriteBufferTest.java` -- buffer unit tests
- [ ] `cameleer-server-app/src/test/java/.../AbstractClickHouseIT.java` -- shared Testcontainers base class
- [ ] `cameleer-server-app/src/test/java/.../ExecutionControllerIT.java` -- ingestion integration test
- [ ] Docker available on test machine for Testcontainers
## Sources

View File

@@ -18,8 +18,8 @@ created: 2026-03-11
| Property | Value |
|----------|-------|
| **Framework** | JUnit 5 (Spring Boot managed) + Testcontainers 2.0.2 |
| **Config file** | cameleer3-server-app/src/test/resources/application-test.yml (Wave 0) |
| **Quick run command** | `mvn test -pl cameleer3-server-core -Dtest=WriteBufferTest -q` |
| **Config file** | cameleer-server-app/src/test/resources/application-test.yml (Wave 0) |
| **Quick run command** | `mvn test -pl cameleer-server-core -Dtest=WriteBufferTest -q` |
| **Full suite command** | `mvn verify` |
| **Estimated runtime** | ~30 seconds |
@@ -27,7 +27,7 @@ created: 2026-03-11
## Sampling Rate
- **After every task commit:** Run `mvn test -pl cameleer3-server-core -q`
- **After every task commit:** Run `mvn test -pl cameleer-server-core -q`
- **After every plan wave:** Run `mvn verify`
- **Before `/gsd:verify-work`:** Full suite must be green
- **Max feedback latency:** 30 seconds
@@ -38,17 +38,17 @@ created: 2026-03-11
| Task ID | Plan | Wave | Requirement | Test Type | Automated Command | File Exists | Status |
|---------|------|------|-------------|-----------|-------------------|-------------|--------|
| 1-01-01 | 01 | 1 | INGST-04 | unit | `mvn test -pl cameleer3-server-core -Dtest=WriteBufferTest -q` | no W0 | pending |
| 1-01-02 | 01 | 1 | INGST-01 | integration | `mvn test -pl cameleer3-server-app -Dtest=ExecutionControllerIT -q` | no W0 | pending |
| 1-01-03 | 01 | 1 | INGST-05 | integration | `mvn test -pl cameleer3-server-app -Dtest=BackpressureIT -q` | no W0 | pending |
| 1-01-04 | 01 | 1 | INGST-06 | integration | `mvn test -pl cameleer3-server-app -Dtest=HealthControllerIT#ttlConfigured* -q` | no W0 | pending |
| 1-02-01 | 02 | 1 | INGST-01 | integration | `mvn test -pl cameleer3-server-app -Dtest=ExecutionControllerIT -q` | no W0 | pending |
| 1-02-02 | 02 | 1 | INGST-02 | integration | `mvn test -pl cameleer3-server-app -Dtest=DiagramControllerIT -q` | no W0 | pending |
| 1-02-03 | 02 | 1 | INGST-03 | integration | `mvn test -pl cameleer3-server-app -Dtest=MetricsControllerIT -q` | no W0 | pending |
| 1-02-04 | 02 | 1 | API-02 | integration | `mvn test -pl cameleer3-server-app -Dtest=OpenApiIT -q` | no W0 | pending |
| 1-02-05 | 02 | 1 | API-03 | integration | `mvn test -pl cameleer3-server-app -Dtest=HealthControllerIT -q` | no W0 | pending |
| 1-02-06 | 02 | 1 | API-04 | integration | `mvn test -pl cameleer3-server-app -Dtest=ProtocolVersionIT -q` | no W0 | pending |
| 1-02-07 | 02 | 1 | API-05 | unit | `mvn test -pl cameleer3-server-app -Dtest=ForwardCompatIT -q` | no W0 | pending |
| 1-01-01 | 01 | 1 | INGST-04 | unit | `mvn test -pl cameleer-server-core -Dtest=WriteBufferTest -q` | no W0 | pending |
| 1-01-02 | 01 | 1 | INGST-01 | integration | `mvn test -pl cameleer-server-app -Dtest=ExecutionControllerIT -q` | no W0 | pending |
| 1-01-03 | 01 | 1 | INGST-05 | integration | `mvn test -pl cameleer-server-app -Dtest=BackpressureIT -q` | no W0 | pending |
| 1-01-04 | 01 | 1 | INGST-06 | integration | `mvn test -pl cameleer-server-app -Dtest=HealthControllerIT#ttlConfigured* -q` | no W0 | pending |
| 1-02-01 | 02 | 1 | INGST-01 | integration | `mvn test -pl cameleer-server-app -Dtest=ExecutionControllerIT -q` | no W0 | pending |
| 1-02-02 | 02 | 1 | INGST-02 | integration | `mvn test -pl cameleer-server-app -Dtest=DiagramControllerIT -q` | no W0 | pending |
| 1-02-03 | 02 | 1 | INGST-03 | integration | `mvn test -pl cameleer-server-app -Dtest=MetricsControllerIT -q` | no W0 | pending |
| 1-02-04 | 02 | 1 | API-02 | integration | `mvn test -pl cameleer-server-app -Dtest=OpenApiIT -q` | no W0 | pending |
| 1-02-05 | 02 | 1 | API-03 | integration | `mvn test -pl cameleer-server-app -Dtest=HealthControllerIT -q` | no W0 | pending |
| 1-02-06 | 02 | 1 | API-04 | integration | `mvn test -pl cameleer-server-app -Dtest=ProtocolVersionIT -q` | no W0 | pending |
| 1-02-07 | 02 | 1 | API-05 | unit | `mvn test -pl cameleer-server-app -Dtest=ForwardCompatIT -q` | no W0 | pending |
*Status: pending / green / red / flaky*
@@ -56,10 +56,10 @@ created: 2026-03-11
## Wave 0 Requirements
- [ ] `cameleer3-server-app/src/test/resources/application-test.yml` — test ClickHouse config
- [ ] `cameleer3-server-core/src/test/java/.../WriteBufferTest.java` — buffer unit tests
- [ ] `cameleer3-server-app/src/test/java/.../AbstractClickHouseIT.java` — shared Testcontainers base class
- [ ] `cameleer3-server-app/src/test/java/.../ExecutionControllerIT.java` — ingestion integration test
- [ ] `cameleer-server-app/src/test/resources/application-test.yml` — test ClickHouse config
- [ ] `cameleer-server-core/src/test/java/.../WriteBufferTest.java` — buffer unit tests
- [ ] `cameleer-server-app/src/test/java/.../AbstractClickHouseIT.java` — shared Testcontainers base class
- [ ] `cameleer-server-app/src/test/java/.../ExecutionControllerIT.java` — ingestion integration test
- [ ] Docker available on test machine for Testcontainers
*If none: "Existing infrastructure covers all phase requirements."*

View File

@@ -35,27 +35,27 @@ re_verification: false
| Artifact | Expected | Status | Details |
|---|---|---|---|
| `cameleer3-server-core/src/main/java/com/cameleer3/server/core/ingestion/WriteBuffer.java` | Generic bounded write buffer with offer/drain/isFull | VERIFIED | 80 lines; `ArrayBlockingQueue`-backed; implements `offer`, `offerBatch` (all-or-nothing), `drain`, `isFull`, `size`, `capacity`, `remainingCapacity` |
| `cameleer-server-core/src/main/java/com/cameleer/server/core/ingestion/WriteBuffer.java` | Generic bounded write buffer with offer/drain/isFull | VERIFIED | 80 lines; `ArrayBlockingQueue`-backed; implements `offer`, `offerBatch` (all-or-nothing), `drain`, `isFull`, `size`, `capacity`, `remainingCapacity` |
| `clickhouse/init/01-schema.sql` | ClickHouse DDL for all three tables | VERIFIED | Contains `CREATE TABLE route_executions`, `route_diagrams`, `agent_metrics`; correct ENGINE, ORDER BY, PARTITION BY, TTL with `toDateTime()` cast |
| `docker-compose.yml` | Local ClickHouse service | VERIFIED | `clickhouse/clickhouse-server:25.3`; ports 8123/9000; init volume mount; credentials configured |
| `cameleer3-server-core/src/main/java/com/cameleer3/server/core/storage/ExecutionRepository.java` | Repository interface for execution batch inserts | VERIFIED | Declares `void insertBatch(List<RouteExecution> executions)` |
| `cameleer-server-core/src/main/java/com/cameleer/server/core/storage/ExecutionRepository.java` | Repository interface for execution batch inserts | VERIFIED | Declares `void insertBatch(List<RouteExecution> executions)` |
#### Plan 01-02 Artifacts
| Artifact | Expected | Status | Details |
|---|---|---|---|
| `cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/ExecutionController.java` | POST /api/v1/data/executions endpoint | VERIFIED | 79 lines; `@PostMapping("/executions")`; handles single/array via raw String parsing; returns 202 or 503 + Retry-After |
| `cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/ClickHouseExecutionRepository.java` | Batch insert to route_executions via JdbcTemplate | VERIFIED | 118 lines; `@Repository`; `BatchPreparedStatementSetter`; flattens processor tree to parallel arrays |
| `cameleer3-server-app/src/main/java/com/cameleer3/server/app/ingestion/ClickHouseFlushScheduler.java` | Scheduled drain of WriteBuffer into ClickHouse | VERIFIED | 160 lines; `@Scheduled(fixedDelayString="${ingestion.flush-interval-ms:1000}")`; implements `SmartLifecycle` for shutdown drain |
| `cameleer3-server-core/src/main/java/com/cameleer3/server/core/ingestion/IngestionService.java` | Routes data to appropriate WriteBuffer instances | VERIFIED | 115 lines; plain class; `acceptExecution`, `acceptExecutions`, `acceptDiagram`, `acceptDiagrams`, `acceptMetrics`; delegates to typed `WriteBuffer` instances |
| `cameleer-server-app/src/main/java/com/cameleer/server/app/controller/ExecutionController.java` | POST /api/v1/data/executions endpoint | VERIFIED | 79 lines; `@PostMapping("/executions")`; handles single/array via raw String parsing; returns 202 or 503 + Retry-After |
| `cameleer-server-app/src/main/java/com/cameleer/server/app/storage/ClickHouseExecutionRepository.java` | Batch insert to route_executions via JdbcTemplate | VERIFIED | 118 lines; `@Repository`; `BatchPreparedStatementSetter`; flattens processor tree to parallel arrays |
| `cameleer-server-app/src/main/java/com/cameleer/server/app/ingestion/ClickHouseFlushScheduler.java` | Scheduled drain of WriteBuffer into ClickHouse | VERIFIED | 160 lines; `@Scheduled(fixedDelayString="${ingestion.flush-interval-ms:1000}")`; implements `SmartLifecycle` for shutdown drain |
| `cameleer-server-core/src/main/java/com/cameleer/server/core/ingestion/IngestionService.java` | Routes data to appropriate WriteBuffer instances | VERIFIED | 115 lines; plain class; `acceptExecution`, `acceptExecutions`, `acceptDiagram`, `acceptDiagrams`, `acceptMetrics`; delegates to typed `WriteBuffer` instances |
#### Plan 01-03 Artifacts
| Artifact | Expected | Status | Details |
|---|---|---|---|
| `cameleer3-server-app/src/main/java/com/cameleer3/server/app/interceptor/ProtocolVersionInterceptor.java` | Validates X-Cameleer-Protocol-Version:1 header on data endpoints | VERIFIED | 47 lines; implements `HandlerInterceptor.preHandle`; returns 400 JSON on missing/wrong version |
| `cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/WebConfig.java` | Registers interceptor with path patterns | VERIFIED | 35 lines; `addInterceptors` registers interceptor on `/api/v1/data/**` and `/api/v1/agents/**`; excludes health, api-docs, swagger-ui |
| `cameleer3-server-app/src/test/java/com/cameleer3/server/app/AbstractClickHouseIT.java` | Shared Testcontainers base class for integration tests | VERIFIED | 73 lines; static `ClickHouseContainer`; `@DynamicPropertySource`; `@BeforeAll` schema init from SQL file; `JdbcTemplate` exposed to subclasses |
| `cameleer-server-app/src/main/java/com/cameleer/server/app/interceptor/ProtocolVersionInterceptor.java` | Validates X-Cameleer-Protocol-Version:1 header on data endpoints | VERIFIED | 47 lines; implements `HandlerInterceptor.preHandle`; returns 400 JSON on missing/wrong version |
| `cameleer-server-app/src/main/java/com/cameleer/server/app/config/WebConfig.java` | Registers interceptor with path patterns | VERIFIED | 35 lines; `addInterceptors` registers interceptor on `/api/v1/data/**` and `/api/v1/agents/**`; excludes health, api-docs, swagger-ui |
| `cameleer-server-app/src/test/java/com/cameleer/server/app/AbstractClickHouseIT.java` | Shared Testcontainers base class for integration tests | VERIFIED | 73 lines; static `ClickHouseContainer`; `@DynamicPropertySource`; `@BeforeAll` schema init from SQL file; `JdbcTemplate` exposed to subclasses |
---
@@ -113,7 +113,7 @@ No orphaned requirements — all 11 IDs declared in plan frontmatter match the R
### Anti-Patterns Found
No anti-patterns detected. Scanned all source files in `cameleer3-server-app/src/main` and `cameleer3-server-core/src/main` for TODO/FIXME/PLACEHOLDER/stub return patterns. None found.
No anti-patterns detected. Scanned all source files in `cameleer-server-app/src/main` and `cameleer-server-core/src/main` for TODO/FIXME/PLACEHOLDER/stub return patterns. None found.
One minor observation (not a blocker):

View File

@@ -6,17 +6,17 @@ wave: 1
depends_on: []
files_modified:
- clickhouse/init/02-search-columns.sql
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/search/SearchRequest.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/search/SearchResult.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/search/SearchEngine.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/search/SearchService.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/search/ExecutionSummary.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/detail/DetailService.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/detail/ExecutionDetail.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/detail/ProcessorNode.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/storage/ExecutionRepository.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/ClickHouseExecutionRepository.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/AbstractClickHouseIT.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/search/SearchRequest.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/search/SearchResult.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/search/SearchEngine.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/search/SearchService.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/search/ExecutionSummary.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/detail/DetailService.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/detail/ExecutionDetail.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/detail/ProcessorNode.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/storage/ExecutionRepository.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/storage/ClickHouseExecutionRepository.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/AbstractClickHouseIT.java
autonomous: true
requirements:
- SRCH-01
@@ -38,21 +38,21 @@ must_haves:
- path: "clickhouse/init/02-search-columns.sql"
provides: "Schema extension DDL for Phase 2 columns and skip indexes"
contains: "exchange_bodies"
- path: "cameleer3-server-core/src/main/java/com/cameleer3/server/core/search/SearchEngine.java"
- path: "cameleer-server-core/src/main/java/com/cameleer/server/core/search/SearchEngine.java"
provides: "Search backend abstraction interface"
exports: ["SearchEngine"]
- path: "cameleer3-server-core/src/main/java/com/cameleer3/server/core/search/SearchRequest.java"
- path: "cameleer-server-core/src/main/java/com/cameleer/server/core/search/SearchRequest.java"
provides: "Immutable search criteria record"
exports: ["SearchRequest"]
- path: "cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/ClickHouseExecutionRepository.java"
- path: "cameleer-server-app/src/main/java/com/cameleer/server/app/storage/ClickHouseExecutionRepository.java"
provides: "Extended with new columns in INSERT, plus query methods"
min_lines: 100
key_links:
- from: "cameleer3-server-core/src/main/java/com/cameleer3/server/core/search/SearchService.java"
- from: "cameleer-server-core/src/main/java/com/cameleer/server/core/search/SearchService.java"
to: "SearchEngine"
via: "constructor injection"
pattern: "SearchEngine"
- from: "cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/ClickHouseExecutionRepository.java"
- from: "cameleer-server-app/src/main/java/com/cameleer/server/app/storage/ClickHouseExecutionRepository.java"
to: "clickhouse/init/02-search-columns.sql"
via: "INSERT and SELECT SQL matching schema"
pattern: "exchange_bodies|processor_depths|diagram_content_hash"
@@ -79,22 +79,22 @@ Output: Schema migration SQL, updated ingestion INSERT with new columns, core se
@.planning/phases/02-transaction-search-diagrams/02-RESEARCH.md
@clickhouse/init/01-schema.sql
@cameleer3-server-core/src/main/java/com/cameleer3/server/core/storage/ExecutionRepository.java
@cameleer3-server-core/src/main/java/com/cameleer3/server/core/storage/DiagramRepository.java
@cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/ClickHouseExecutionRepository.java
@cameleer3-server-app/src/test/java/com/cameleer3/server/app/AbstractClickHouseIT.java
@cameleer-server-core/src/main/java/com/cameleer/server/core/storage/ExecutionRepository.java
@cameleer-server-core/src/main/java/com/cameleer/server/core/storage/DiagramRepository.java
@cameleer-server-app/src/main/java/com/cameleer/server/app/storage/ClickHouseExecutionRepository.java
@cameleer-server-app/src/test/java/com/cameleer/server/app/AbstractClickHouseIT.java
<interfaces>
<!-- Existing interfaces the executor needs -->
From cameleer3-server-core/.../storage/ExecutionRepository.java:
From cameleer-server-core/.../storage/ExecutionRepository.java:
```java
public interface ExecutionRepository {
void insertBatch(List<RouteExecution> executions);
}
```
From cameleer3-server-core/.../storage/DiagramRepository.java:
From cameleer-server-core/.../storage/DiagramRepository.java:
```java
public interface DiagramRepository {
void store(RouteGraph graph);
@@ -103,7 +103,7 @@ public interface DiagramRepository {
}
```
From cameleer3-common (decompiled — key fields):
From cameleer-common (decompiled — key fields):
```java
// RouteExecution: routeId, status (ExecutionStatus enum: COMPLETED/FAILED/RUNNING),
// startTime (Instant), endTime (Instant), durationMs (long), correlationId, exchangeId,
@@ -145,15 +145,15 @@ Existing ClickHouse schema (01-schema.sql):
<name>Task 1: Schema extension and core domain types</name>
<files>
clickhouse/init/02-search-columns.sql,
cameleer3-server-core/src/main/java/com/cameleer3/server/core/search/SearchRequest.java,
cameleer3-server-core/src/main/java/com/cameleer3/server/core/search/SearchResult.java,
cameleer3-server-core/src/main/java/com/cameleer3/server/core/search/SearchEngine.java,
cameleer3-server-core/src/main/java/com/cameleer3/server/core/search/SearchService.java,
cameleer3-server-core/src/main/java/com/cameleer3/server/core/search/ExecutionSummary.java,
cameleer3-server-core/src/main/java/com/cameleer3/server/core/detail/DetailService.java,
cameleer3-server-core/src/main/java/com/cameleer3/server/core/detail/ExecutionDetail.java,
cameleer3-server-core/src/main/java/com/cameleer3/server/core/detail/ProcessorNode.java,
cameleer3-server-core/src/main/java/com/cameleer3/server/core/storage/ExecutionRepository.java
cameleer-server-core/src/main/java/com/cameleer/server/core/search/SearchRequest.java,
cameleer-server-core/src/main/java/com/cameleer/server/core/search/SearchResult.java,
cameleer-server-core/src/main/java/com/cameleer/server/core/search/SearchEngine.java,
cameleer-server-core/src/main/java/com/cameleer/server/core/search/SearchService.java,
cameleer-server-core/src/main/java/com/cameleer/server/core/search/ExecutionSummary.java,
cameleer-server-core/src/main/java/com/cameleer/server/core/detail/DetailService.java,
cameleer-server-core/src/main/java/com/cameleer/server/core/detail/ExecutionDetail.java,
cameleer-server-core/src/main/java/com/cameleer/server/core/detail/ProcessorNode.java,
cameleer-server-core/src/main/java/com/cameleer/server/core/storage/ExecutionRepository.java
</files>
<action>
1. Create `clickhouse/init/02-search-columns.sql` with ALTER TABLE statements to add Phase 2 columns to route_executions:
@@ -172,14 +172,14 @@ Existing ClickHouse schema (01-schema.sql):
- Add tokenbf_v1 skip indexes on exchange_bodies and exchange_headers (GRANULARITY 4, same as idx_error)
- Add tokenbf_v1 skip index on error_stacktrace (it has no index yet, needed for SRCH-05 full-text search across stack traces)
2. Create core search domain types in `com.cameleer3.server.core.search`:
2. Create core search domain types in `com.cameleer.server.core.search`:
- `SearchRequest` record: status (String, nullable), timeFrom (Instant), timeTo (Instant), durationMin (Long), durationMax (Long), correlationId (String), text (String — global full-text), textInBody (String), textInHeaders (String), textInErrors (String), offset (int), limit (int). Compact constructor validates: limit defaults to 50 if <= 0, capped at 500; offset defaults to 0 if < 0.
- `SearchResult<T>` record: data (List<T>), total (long), offset (int), limit (int). Include static factory `empty(int offset, int limit)`.
- `ExecutionSummary` record: executionId (String), routeId (String), agentId (String), status (String), startTime (Instant), endTime (Instant), durationMs (long), correlationId (String), errorMessage (String), diagramContentHash (String). This is the lightweight list-view DTO — NOT the full processor arrays.
- `SearchEngine` interface with methods: `SearchResult<ExecutionSummary> search(SearchRequest request)` and `long count(SearchRequest request)`. This is the swappable backend (ClickHouse now, OpenSearch later per user decision).
- `SearchService` class: plain class (no Spring annotations, same pattern as IngestionService). Constructor takes SearchEngine. `search(SearchRequest)` delegates to engine.search(). This thin orchestration layer allows adding cross-cutting concerns later.
3. Create core detail domain types in `com.cameleer3.server.core.detail`:
3. Create core detail domain types in `com.cameleer.server.core.detail`:
- `ProcessorNode` record: processorId (String), processorType (String), status (String), startTime (Instant), endTime (Instant), durationMs (long), diagramNodeId (String), errorMessage (String), errorStackTrace (String), children (List<ProcessorNode>). This is the nested tree node.
- `ExecutionDetail` record: executionId (String), routeId (String), agentId (String), status (String), startTime (Instant), endTime (Instant), durationMs (long), correlationId (String), exchangeId (String), errorMessage (String), errorStackTrace (String), diagramContentHash (String), processors (List<ProcessorNode>). This is the full detail response.
- `DetailService` class: plain class (no Spring annotations). Constructor takes ExecutionRepository. Method `getDetail(String executionId)` returns `Optional<ExecutionDetail>`. Calls repository's new `findDetailById` method, then calls `reconstructTree()` to convert flat arrays into nested ProcessorNode tree. The `reconstructTree` method: takes parallel arrays (ids, types, statuses, starts, ends, durations, diagramNodeIds, errorMessages, errorStackTraces, depths, parentIndexes), creates ProcessorNode[] array, then wires children using parentIndexes (parentIndex == -1 means root).
@@ -190,7 +190,7 @@ Existing ClickHouse schema (01-schema.sql):
Actually, use a different approach per the layering: add a `findRawById(String executionId)` method that returns `Optional<RawExecutionRow>` — a new record containing all parallel arrays. DetailService takes this and reconstructs. Create `RawExecutionRow` as a record in the detail package with all fields needed for reconstruction.
</action>
<verify>
<automated>cd C:/Users/Hendrik/Documents/projects/cameleer3-server && mvn compile -pl cameleer3-server-core</automated>
<automated>cd C:/Users/Hendrik/Documents/projects/cameleer-server && mvn compile -pl cameleer-server-core</automated>
</verify>
<done>Schema migration SQL exists, all core domain types compile, SearchEngine interface and SearchService defined, ExecutionRepository extended with query method, DetailService has tree reconstruction logic</done>
</task>
@@ -198,9 +198,9 @@ Existing ClickHouse schema (01-schema.sql):
<task type="auto" tdd="true">
<name>Task 2: Update ingestion to populate new columns and verify with integration test</name>
<files>
cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/ClickHouseExecutionRepository.java,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/AbstractClickHouseIT.java,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/storage/IngestionSchemaIT.java
cameleer-server-app/src/main/java/com/cameleer/server/app/storage/ClickHouseExecutionRepository.java,
cameleer-server-app/src/test/java/com/cameleer/server/app/AbstractClickHouseIT.java,
cameleer-server-app/src/test/java/com/cameleer/server/app/storage/IngestionSchemaIT.java
</files>
<behavior>
- Test: After inserting a RouteExecution with processors that have exchange snapshots and nested children, the route_executions row has non-empty exchange_bodies, exchange_headers, processor_depths (correct depth values), processor_parent_indexes (correct parent wiring), processor_input_bodies, processor_output_bodies, processor_input_headers, processor_output_headers, processor_diagram_node_ids, and diagram_content_hash columns
@@ -231,7 +231,7 @@ Existing ClickHouse schema (01-schema.sql):
- Verifies a second insertion with null snapshots succeeds with empty defaults
</action>
<verify>
<automated>cd C:/Users/Hendrik/Documents/projects/cameleer3-server && mvn test -pl cameleer3-server-app -Dtest=IngestionSchemaIT</automated>
<automated>cd C:/Users/Hendrik/Documents/projects/cameleer-server && mvn test -pl cameleer-server-app -Dtest=IngestionSchemaIT</automated>
</verify>
<done>All new columns populated correctly during ingestion, tree metadata (depth/parent) correct for nested processors, exchange data concatenated for search, existing ingestion tests still pass</done>
</task>
@@ -239,9 +239,9 @@ Existing ClickHouse schema (01-schema.sql):
</tasks>
<verification>
- `mvn compile -pl cameleer3-server-core` succeeds (core domain types compile)
- `mvn test -pl cameleer3-server-app -Dtest=IngestionSchemaIT` passes (new columns populated correctly)
- `mvn test -pl cameleer3-server-app` passes (all existing tests still green with schema extension)
- `mvn compile -pl cameleer-server-core` succeeds (core domain types compile)
- `mvn test -pl cameleer-server-app -Dtest=IngestionSchemaIT` passes (new columns populated correctly)
- `mvn test -pl cameleer-server-app` passes (all existing tests still green with schema extension)
</verification>
<success_criteria>

View File

@@ -22,22 +22,22 @@ tech-stack:
key-files:
created:
- clickhouse/init/02-search-columns.sql
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/search/SearchRequest.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/search/SearchResult.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/search/ExecutionSummary.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/search/SearchEngine.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/search/SearchService.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/detail/DetailService.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/detail/ExecutionDetail.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/detail/ProcessorNode.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/detail/RawExecutionRow.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/diagram/DiagramRenderer.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/diagram/DiagramLayout.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/storage/IngestionSchemaIT.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/search/SearchRequest.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/search/SearchResult.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/search/ExecutionSummary.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/search/SearchEngine.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/search/SearchService.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/detail/DetailService.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/detail/ExecutionDetail.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/detail/ProcessorNode.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/detail/RawExecutionRow.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/diagram/DiagramRenderer.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/diagram/DiagramLayout.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/storage/IngestionSchemaIT.java
modified:
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/storage/ExecutionRepository.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/ClickHouseExecutionRepository.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/AbstractClickHouseIT.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/storage/ExecutionRepository.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/storage/ClickHouseExecutionRepository.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/AbstractClickHouseIT.java
key-decisions:
- "FlatProcessor record captures depth and parentIndex during DFS traversal"
@@ -86,21 +86,21 @@ Each task was committed atomically:
## Files Created/Modified
- `clickhouse/init/02-search-columns.sql` - ALTER TABLE adding 12 columns + 3 skip indexes
- `cameleer3-server-core/.../search/SearchRequest.java` - Immutable search criteria record with validation
- `cameleer3-server-core/.../search/SearchResult.java` - Paginated result envelope
- `cameleer3-server-core/.../search/ExecutionSummary.java` - Lightweight list-view DTO
- `cameleer3-server-core/.../search/SearchEngine.java` - Swappable search backend interface
- `cameleer3-server-core/.../search/SearchService.java` - Search orchestration layer
- `cameleer3-server-core/.../detail/DetailService.java` - Tree reconstruction from flat arrays
- `cameleer3-server-core/.../detail/ExecutionDetail.java` - Full execution detail record
- `cameleer3-server-core/.../detail/ProcessorNode.java` - Nested tree node (mutable children)
- `cameleer3-server-core/.../detail/RawExecutionRow.java` - DB-to-domain intermediate record
- `cameleer3-server-core/.../diagram/DiagramRenderer.java` - Diagram rendering interface (stub)
- `cameleer3-server-core/.../diagram/DiagramLayout.java` - JSON layout record (stub)
- `cameleer3-server-core/.../storage/ExecutionRepository.java` - Extended with findRawById
- `cameleer3-server-app/.../storage/ClickHouseExecutionRepository.java` - INSERT extended with 12 new columns
- `cameleer3-server-app/src/test/.../AbstractClickHouseIT.java` - Loads 02-search-columns.sql
- `cameleer3-server-app/src/test/.../storage/IngestionSchemaIT.java` - 3 integration tests
- `cameleer-server-core/.../search/SearchRequest.java` - Immutable search criteria record with validation
- `cameleer-server-core/.../search/SearchResult.java` - Paginated result envelope
- `cameleer-server-core/.../search/ExecutionSummary.java` - Lightweight list-view DTO
- `cameleer-server-core/.../search/SearchEngine.java` - Swappable search backend interface
- `cameleer-server-core/.../search/SearchService.java` - Search orchestration layer
- `cameleer-server-core/.../detail/DetailService.java` - Tree reconstruction from flat arrays
- `cameleer-server-core/.../detail/ExecutionDetail.java` - Full execution detail record
- `cameleer-server-core/.../detail/ProcessorNode.java` - Nested tree node (mutable children)
- `cameleer-server-core/.../detail/RawExecutionRow.java` - DB-to-domain intermediate record
- `cameleer-server-core/.../diagram/DiagramRenderer.java` - Diagram rendering interface (stub)
- `cameleer-server-core/.../diagram/DiagramLayout.java` - JSON layout record (stub)
- `cameleer-server-core/.../storage/ExecutionRepository.java` - Extended with findRawById
- `cameleer-server-app/.../storage/ClickHouseExecutionRepository.java` - INSERT extended with 12 new columns
- `cameleer-server-app/src/test/.../AbstractClickHouseIT.java` - Loads 02-search-columns.sql
- `cameleer-server-app/src/test/.../storage/IngestionSchemaIT.java` - 3 integration tests
## Decisions Made
- Used FlatProcessor record to carry depth and parentIndex alongside the ProcessorExecution during DFS flattening -- single pass, no separate traversal
@@ -116,9 +116,9 @@ Each task was committed atomically:
**1. [Rule 3 - Blocking] Created DiagramRenderer and DiagramLayout stub interfaces**
- **Found during:** Task 2 (compilation step)
- **Issue:** Pre-existing `ElkDiagramRenderer` in app module referenced `DiagramRenderer` and `DiagramLayout` interfaces that did not exist in core module, causing compilation failure
- **Fix:** Created minimal stub interfaces in `com.cameleer3.server.core.diagram` package
- **Fix:** Created minimal stub interfaces in `com.cameleer.server.core.diagram` package
- **Files created:** DiagramRenderer.java, DiagramLayout.java
- **Verification:** `mvn compile -pl cameleer3-server-core` and `mvn compile -pl cameleer3-server-app` succeed
- **Verification:** `mvn compile -pl cameleer-server-core` and `mvn compile -pl cameleer-server-app` succeed
- **Committed in:** f6ff279 (Task 2 GREEN commit)
**2. [Rule 1 - Bug] Fixed ClickHouse Array type handling in IngestionSchemaIT**

View File

@@ -5,16 +5,16 @@ type: execute
wave: 1
depends_on: []
files_modified:
- cameleer3-server-app/pom.xml
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/diagram/DiagramRenderer.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/diagram/DiagramLayout.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/diagram/PositionedNode.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/diagram/PositionedEdge.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/diagram/ElkDiagramRenderer.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/DiagramRenderController.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/DiagramBeanConfig.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/DiagramRenderControllerIT.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/diagram/ElkDiagramRendererTest.java
- cameleer-server-app/pom.xml
- cameleer-server-core/src/main/java/com/cameleer/server/core/diagram/DiagramRenderer.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/diagram/DiagramLayout.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/diagram/PositionedNode.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/diagram/PositionedEdge.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/diagram/ElkDiagramRenderer.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/controller/DiagramRenderController.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/config/DiagramBeanConfig.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/controller/DiagramRenderControllerIT.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/diagram/ElkDiagramRendererTest.java
autonomous: true
requirements:
- DIAG-03
@@ -27,13 +27,13 @@ must_haves:
- "Node colors match the route-diagram-example.html style: blue endpoints, green processors, red error handlers, purple EIPs"
- "Nested processors (inside split, choice, try-catch) are rendered in compound/swimlane groups"
artifacts:
- path: "cameleer3-server-core/src/main/java/com/cameleer3/server/core/diagram/DiagramRenderer.java"
- path: "cameleer-server-core/src/main/java/com/cameleer/server/core/diagram/DiagramRenderer.java"
provides: "Renderer interface for SVG and JSON layout output"
exports: ["DiagramRenderer"]
- path: "cameleer3-server-app/src/main/java/com/cameleer3/server/app/diagram/ElkDiagramRenderer.java"
- path: "cameleer-server-app/src/main/java/com/cameleer/server/app/diagram/ElkDiagramRenderer.java"
provides: "ELK + JFreeSVG implementation of DiagramRenderer"
min_lines: 100
- path: "cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/DiagramRenderController.java"
- path: "cameleer-server-app/src/main/java/com/cameleer/server/app/controller/DiagramRenderController.java"
provides: "GET /api/v1/diagrams/{hash} with content negotiation"
exports: ["DiagramRenderController"]
key_links:
@@ -70,14 +70,14 @@ Output: DiagramRenderer interface in core, ElkDiagramRenderer implementation in
@.planning/phases/02-transaction-search-diagrams/02-CONTEXT.md
@.planning/phases/02-transaction-search-diagrams/02-RESEARCH.md
@cameleer3-server-core/src/main/java/com/cameleer3/server/core/storage/DiagramRepository.java
@cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/ClickHouseDiagramRepository.java
@cameleer3-server-app/pom.xml
@cameleer-server-core/src/main/java/com/cameleer/server/core/storage/DiagramRepository.java
@cameleer-server-app/src/main/java/com/cameleer/server/app/storage/ClickHouseDiagramRepository.java
@cameleer-server-app/pom.xml
<interfaces>
<!-- Existing interfaces needed -->
From cameleer3-server-core/.../storage/DiagramRepository.java:
From cameleer-server-core/.../storage/DiagramRepository.java:
```java
public interface DiagramRepository {
void store(RouteGraph graph);
@@ -86,7 +86,7 @@ public interface DiagramRepository {
}
```
From cameleer3-common (decompiled — diagram models):
From cameleer-common (decompiled — diagram models):
```java
// RouteGraph: routeId (String), nodes (List<RouteNode>), edges (List<RouteEdge>),
// processorNodeMapping (Map<String,String>)
@@ -114,14 +114,14 @@ NodeType color mapping (from CONTEXT.md, matching route-diagram-example.html):
<task type="auto">
<name>Task 1: Add ELK/JFreeSVG dependencies and create core diagram rendering interfaces</name>
<files>
cameleer3-server-app/pom.xml,
cameleer3-server-core/src/main/java/com/cameleer3/server/core/diagram/DiagramRenderer.java,
cameleer3-server-core/src/main/java/com/cameleer3/server/core/diagram/DiagramLayout.java,
cameleer3-server-core/src/main/java/com/cameleer3/server/core/diagram/PositionedNode.java,
cameleer3-server-core/src/main/java/com/cameleer3/server/core/diagram/PositionedEdge.java
cameleer-server-app/pom.xml,
cameleer-server-core/src/main/java/com/cameleer/server/core/diagram/DiagramRenderer.java,
cameleer-server-core/src/main/java/com/cameleer/server/core/diagram/DiagramLayout.java,
cameleer-server-core/src/main/java/com/cameleer/server/core/diagram/PositionedNode.java,
cameleer-server-core/src/main/java/com/cameleer/server/core/diagram/PositionedEdge.java
</files>
<action>
1. Add Maven dependencies to `cameleer3-server-app/pom.xml`:
1. Add Maven dependencies to `cameleer-server-app/pom.xml`:
```xml
<dependency>
<groupId>org.eclipse.elk</groupId>
@@ -140,7 +140,7 @@ NodeType color mapping (from CONTEXT.md, matching route-diagram-example.html):
</dependency>
```
2. Create core diagram rendering interfaces in `com.cameleer3.server.core.diagram`:
2. Create core diagram rendering interfaces in `com.cameleer.server.core.diagram`:
- `PositionedNode` record: id (String), label (String), type (String — NodeType name), x (double), y (double), width (double), height (double), children (List<PositionedNode> — for compound/swimlane groups). JSON-serializable for the JSON layout response.
@@ -154,7 +154,7 @@ NodeType color mapping (from CONTEXT.md, matching route-diagram-example.html):
Both methods take a RouteGraph and produce output. The interface lives in core so it can be swapped (e.g., for a different renderer).
</action>
<verify>
<automated>cd C:/Users/Hendrik/Documents/projects/cameleer3-server && mvn compile -pl cameleer3-server-core && mvn dependency:resolve -pl cameleer3-server-app -q</automated>
<automated>cd C:/Users/Hendrik/Documents/projects/cameleer-server && mvn compile -pl cameleer-server-core && mvn dependency:resolve -pl cameleer-server-app -q</automated>
</verify>
<done>ELK and JFreeSVG dependencies resolve, DiagramRenderer interface and layout DTOs compile in core module</done>
</task>
@@ -162,11 +162,11 @@ NodeType color mapping (from CONTEXT.md, matching route-diagram-example.html):
<task type="auto" tdd="true">
<name>Task 2: Implement ElkDiagramRenderer, DiagramRenderController, and integration tests</name>
<files>
cameleer3-server-app/src/main/java/com/cameleer3/server/app/diagram/ElkDiagramRenderer.java,
cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/DiagramRenderController.java,
cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/DiagramBeanConfig.java,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/diagram/ElkDiagramRendererTest.java,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/DiagramRenderControllerIT.java
cameleer-server-app/src/main/java/com/cameleer/server/app/diagram/ElkDiagramRenderer.java,
cameleer-server-app/src/main/java/com/cameleer/server/app/controller/DiagramRenderController.java,
cameleer-server-app/src/main/java/com/cameleer/server/app/config/DiagramBeanConfig.java,
cameleer-server-app/src/test/java/com/cameleer/server/app/diagram/ElkDiagramRendererTest.java,
cameleer-server-app/src/test/java/com/cameleer/server/app/controller/DiagramRenderControllerIT.java
</files>
<behavior>
- Unit test: ElkDiagramRenderer.renderSvg with a simple 3-node graph (from->process->to) produces valid SVG containing svg element, rect elements for nodes, line/path elements for edges
@@ -179,7 +179,7 @@ NodeType color mapping (from CONTEXT.md, matching route-diagram-example.html):
- Integration test: GET /api/v1/diagrams/{hash} with no Accept preference defaults to SVG
</behavior>
<action>
1. Create `ElkDiagramRenderer` implementing `DiagramRenderer` in `com.cameleer3.server.app.diagram`:
1. Create `ElkDiagramRenderer` implementing `DiagramRenderer` in `com.cameleer.server.app.diagram`:
**Layout phase (shared by both SVG and JSON):**
- Convert RouteGraph to ELK graph: create ElkNode root, set properties for LayeredOptions.ALGORITHM_ID, Direction.DOWN (top-to-bottom per user decision), spacing 40px node-node, 20px edge-node.
@@ -206,10 +206,10 @@ NodeType color mapping (from CONTEXT.md, matching route-diagram-example.html):
**JSON layout (layoutJson):**
- Run layout phase, return DiagramLayout directly. Jackson will serialize it to JSON.
2. Create `DiagramBeanConfig` in `com.cameleer3.server.app.config`:
2. Create `DiagramBeanConfig` in `com.cameleer.server.app.config`:
- @Configuration class that creates DiagramRenderer bean (ElkDiagramRenderer) and SearchService bean wiring (prepare for Plan 03).
3. Create `DiagramRenderController` in `com.cameleer3.server.app.controller`:
3. Create `DiagramRenderController` in `com.cameleer.server.app.controller`:
- `GET /api/v1/diagrams/{contentHash}/render` — renders the diagram
- Inject DiagramRepository and DiagramRenderer.
- Look up RouteGraph via `diagramRepository.findByContentHash(contentHash)`. If empty, return 404.
@@ -233,7 +233,7 @@ NodeType color mapping (from CONTEXT.md, matching route-diagram-example.html):
- GET /api/v1/diagrams/{hash}/render with no Accept header -> assert SVG response (default).
</action>
<verify>
<automated>cd C:/Users/Hendrik/Documents/projects/cameleer3-server && mvn test -pl cameleer3-server-app -Dtest="ElkDiagramRendererTest,DiagramRenderControllerIT"</automated>
<automated>cd C:/Users/Hendrik/Documents/projects/cameleer-server && mvn test -pl cameleer-server-app -Dtest="ElkDiagramRendererTest,DiagramRenderControllerIT"</automated>
</verify>
<done>Diagram rendering produces color-coded top-to-bottom SVG and JSON layout, content negotiation works via Accept header, compound nodes group nested processors, all tests pass</done>
</task>
@@ -241,8 +241,8 @@ NodeType color mapping (from CONTEXT.md, matching route-diagram-example.html):
</tasks>
<verification>
- `mvn test -pl cameleer3-server-app -Dtest=ElkDiagramRendererTest` passes (unit tests for layout and SVG)
- `mvn test -pl cameleer3-server-app -Dtest=DiagramRenderControllerIT` passes (integration tests for REST endpoint)
- `mvn test -pl cameleer-server-app -Dtest=ElkDiagramRendererTest` passes (unit tests for layout and SVG)
- `mvn test -pl cameleer-server-app -Dtest=DiagramRenderControllerIT` passes (integration tests for REST endpoint)
- `mvn clean verify` passes (all existing tests still green)
- SVG output contains color-coded nodes matching the NodeType color scheme
</verification>

View File

@@ -21,17 +21,17 @@ tech-stack:
key-files:
created:
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/diagram/DiagramRenderer.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/diagram/DiagramLayout.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/diagram/PositionedNode.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/diagram/PositionedEdge.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/diagram/ElkDiagramRenderer.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/DiagramRenderController.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/DiagramBeanConfig.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/diagram/ElkDiagramRendererTest.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/DiagramRenderControllerIT.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/diagram/DiagramRenderer.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/diagram/DiagramLayout.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/diagram/PositionedNode.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/diagram/PositionedEdge.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/diagram/ElkDiagramRenderer.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/controller/DiagramRenderController.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/config/DiagramBeanConfig.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/diagram/ElkDiagramRendererTest.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/controller/DiagramRenderControllerIT.java
modified:
- cameleer3-server-app/pom.xml
- cameleer-server-app/pom.xml
key-decisions:
- "Used ELK layered algorithm with top-to-bottom direction for route diagram layout"
@@ -78,16 +78,16 @@ Each task was committed atomically:
2. **Task 2: Implement ElkDiagramRenderer, DiagramRenderController, and integration tests** - `c1bc32d` (feat, TDD)
## Files Created/Modified
- `cameleer3-server-core/.../diagram/DiagramRenderer.java` - Renderer interface with renderSvg and layoutJson
- `cameleer3-server-core/.../diagram/DiagramLayout.java` - Layout record (width, height, nodes, edges)
- `cameleer3-server-core/.../diagram/PositionedNode.java` - Node record with position, dimensions, children
- `cameleer3-server-core/.../diagram/PositionedEdge.java` - Edge record with waypoints
- `cameleer3-server-app/.../diagram/ElkDiagramRenderer.java` - ELK + JFreeSVG implementation (~400 lines)
- `cameleer3-server-app/.../controller/DiagramRenderController.java` - GET /api/v1/diagrams/{hash}/render
- `cameleer3-server-app/.../config/DiagramBeanConfig.java` - Spring bean wiring for DiagramRenderer
- `cameleer3-server-app/pom.xml` - Added ELK, JFreeSVG, xtext dependencies
- `cameleer3-server-app/.../diagram/ElkDiagramRendererTest.java` - 11 unit tests
- `cameleer3-server-app/.../controller/DiagramRenderControllerIT.java` - 4 integration tests
- `cameleer-server-core/.../diagram/DiagramRenderer.java` - Renderer interface with renderSvg and layoutJson
- `cameleer-server-core/.../diagram/DiagramLayout.java` - Layout record (width, height, nodes, edges)
- `cameleer-server-core/.../diagram/PositionedNode.java` - Node record with position, dimensions, children
- `cameleer-server-core/.../diagram/PositionedEdge.java` - Edge record with waypoints
- `cameleer-server-app/.../diagram/ElkDiagramRenderer.java` - ELK + JFreeSVG implementation (~400 lines)
- `cameleer-server-app/.../controller/DiagramRenderController.java` - GET /api/v1/diagrams/{hash}/render
- `cameleer-server-app/.../config/DiagramBeanConfig.java` - Spring bean wiring for DiagramRenderer
- `cameleer-server-app/pom.xml` - Added ELK, JFreeSVG, xtext dependencies
- `cameleer-server-app/.../diagram/ElkDiagramRendererTest.java` - 11 unit tests
- `cameleer-server-app/.../controller/DiagramRenderControllerIT.java` - 4 integration tests
## Decisions Made
- Used ELK layered algorithm (org.eclipse.elk.alg.layered) -- well-maintained, supports compound nodes natively
@@ -104,7 +104,7 @@ Each task was committed atomically:
- **Found during:** Task 2 (ElkDiagramRenderer implementation)
- **Issue:** ELK 0.11.0 LayeredMetaDataProvider references org.eclipse.xtext.xbase.lib.CollectionLiterals at class initialization, causing NoClassDefFoundError
- **Fix:** Added org.eclipse.xtext:org.eclipse.xtext.xbase.lib:2.37.0 dependency to app pom.xml
- **Files modified:** cameleer3-server-app/pom.xml
- **Files modified:** cameleer-server-app/pom.xml
- **Verification:** All unit tests pass after adding dependency
- **Committed in:** c1bc32d (Task 2 commit)
@@ -119,7 +119,7 @@ Each task was committed atomically:
**3. [Rule 1 - Bug] Adapted to actual NodeType enum naming (EIP_ prefix)**
- **Found during:** Task 2 (ElkDiagramRenderer implementation)
- **Issue:** Plan referenced CHOICE, SPLIT etc. but actual enum values are EIP_CHOICE, EIP_SPLIT etc.
- **Fix:** Used correct enum names from decompiled cameleer3-common jar in all color mapping sets
- **Fix:** Used correct enum names from decompiled cameleer-common jar in all color mapping sets
- **Files modified:** ElkDiagramRenderer.java
- **Verification:** Unit tests verify correct colors for endpoint and processor nodes
- **Committed in:** c1bc32d (Task 2 commit)

View File

@@ -6,14 +6,14 @@ wave: 2
depends_on:
- "02-01"
files_modified:
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/search/ClickHouseSearchEngine.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/SearchController.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/DetailController.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/SearchBeanConfig.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/ClickHouseExecutionRepository.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/SearchControllerIT.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/DetailControllerIT.java
- cameleer3-server-core/src/test/java/com/cameleer3/server/core/detail/TreeReconstructionTest.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/search/ClickHouseSearchEngine.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/controller/SearchController.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/controller/DetailController.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/config/SearchBeanConfig.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/storage/ClickHouseExecutionRepository.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/controller/SearchControllerIT.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/controller/DetailControllerIT.java
- cameleer-server-core/src/test/java/com/cameleer/server/core/detail/TreeReconstructionTest.java
autonomous: true
requirements:
- SRCH-01
@@ -35,16 +35,16 @@ must_haves:
- "Detail response includes diagramContentHash for linking to diagram endpoint"
- "Search results are paginated with total count, offset, and limit"
artifacts:
- path: "cameleer3-server-app/src/main/java/com/cameleer3/server/app/search/ClickHouseSearchEngine.java"
- path: "cameleer-server-app/src/main/java/com/cameleer/server/app/search/ClickHouseSearchEngine.java"
provides: "ClickHouse implementation of SearchEngine with dynamic WHERE building"
min_lines: 80
- path: "cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/SearchController.java"
- path: "cameleer-server-app/src/main/java/com/cameleer/server/app/controller/SearchController.java"
provides: "GET + POST /api/v1/search/executions endpoints"
exports: ["SearchController"]
- path: "cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/DetailController.java"
- path: "cameleer-server-app/src/main/java/com/cameleer/server/app/controller/DetailController.java"
provides: "GET /api/v1/executions/{id} endpoint returning nested tree"
exports: ["DetailController"]
- path: "cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/SearchControllerIT.java"
- path: "cameleer-server-app/src/test/java/com/cameleer/server/app/controller/SearchControllerIT.java"
provides: "Integration tests for all search filter combinations"
min_lines: 100
key_links:
@@ -92,13 +92,13 @@ Output: SearchController (GET + POST), DetailController, ClickHouseSearchEngine,
@clickhouse/init/01-schema.sql
@clickhouse/init/02-search-columns.sql
@cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/ClickHouseExecutionRepository.java
@cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/ExecutionController.java
@cameleer-server-app/src/main/java/com/cameleer/server/app/storage/ClickHouseExecutionRepository.java
@cameleer-server-app/src/main/java/com/cameleer/server/app/controller/ExecutionController.java
<interfaces>
<!-- Core types created by Plan 01 — executor reads these from plan 01 SUMMARY -->
From cameleer3-server-core/.../search/SearchEngine.java:
From cameleer-server-core/.../search/SearchEngine.java:
```java
public interface SearchEngine {
SearchResult<ExecutionSummary> search(SearchRequest request);
@@ -106,7 +106,7 @@ public interface SearchEngine {
}
```
From cameleer3-server-core/.../search/SearchRequest.java:
From cameleer-server-core/.../search/SearchRequest.java:
```java
public record SearchRequest(
String status, // nullable, filter by ExecutionStatus name
@@ -124,14 +124,14 @@ public record SearchRequest(
) { /* compact constructor with validation */ }
```
From cameleer3-server-core/.../search/SearchResult.java:
From cameleer-server-core/.../search/SearchResult.java:
```java
public record SearchResult<T>(List<T> data, long total, int offset, int limit) {
public static <T> SearchResult<T> empty(int offset, int limit);
}
```
From cameleer3-server-core/.../search/ExecutionSummary.java:
From cameleer-server-core/.../search/ExecutionSummary.java:
```java
public record ExecutionSummary(
String executionId, String routeId, String agentId, String status,
@@ -140,7 +140,7 @@ public record ExecutionSummary(
) {}
```
From cameleer3-server-core/.../detail/DetailService.java:
From cameleer-server-core/.../detail/DetailService.java:
```java
public class DetailService {
// Constructor takes ExecutionRepository (or a query interface)
@@ -149,7 +149,7 @@ public class DetailService {
}
```
From cameleer3-server-core/.../detail/ExecutionDetail.java:
From cameleer-server-core/.../detail/ExecutionDetail.java:
```java
public record ExecutionDetail(
String executionId, String routeId, String agentId, String status,
@@ -160,7 +160,7 @@ public record ExecutionDetail(
) {}
```
From cameleer3-server-core/.../detail/ProcessorNode.java:
From cameleer-server-core/.../detail/ProcessorNode.java:
```java
public record ProcessorNode(
String processorId, String processorType, String status,
@@ -201,10 +201,10 @@ Established controller pattern (from Phase 1):
<task type="auto" tdd="true">
<name>Task 1: ClickHouseSearchEngine, SearchController, and search integration tests</name>
<files>
cameleer3-server-app/src/main/java/com/cameleer3/server/app/search/ClickHouseSearchEngine.java,
cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/SearchController.java,
cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/SearchBeanConfig.java,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/SearchControllerIT.java
cameleer-server-app/src/main/java/com/cameleer/server/app/search/ClickHouseSearchEngine.java,
cameleer-server-app/src/main/java/com/cameleer/server/app/controller/SearchController.java,
cameleer-server-app/src/main/java/com/cameleer/server/app/config/SearchBeanConfig.java,
cameleer-server-app/src/test/java/com/cameleer/server/app/controller/SearchControllerIT.java
</files>
<behavior>
- Test searchByStatus: Insert 3 executions (COMPLETED, FAILED, RUNNING). GET /api/v1/search/executions?status=FAILED returns only the FAILED execution. Response has envelope: {"data":[...],"total":1,"offset":0,"limit":50}
@@ -221,7 +221,7 @@ Established controller pattern (from Phase 1):
- Test emptyResults: Search with no matches returns {"data":[],"total":0,"offset":0,"limit":50}
</behavior>
<action>
1. Create `ClickHouseSearchEngine` in `com.cameleer3.server.app.search`:
1. Create `ClickHouseSearchEngine` in `com.cameleer.server.app.search`:
- Implements SearchEngine interface from core module.
- Constructor takes JdbcTemplate.
- `search(SearchRequest)` method:
@@ -244,13 +244,13 @@ Established controller pattern (from Phase 1):
- `escapeLike(String)` utility: escape `%`, `_`, `\` characters in user input to prevent LIKE injection. Replace `\` with `\\`, `%` with `\%`, `_` with `\_`.
- `count(SearchRequest)` method: same WHERE building, just count query.
2. Create `SearchBeanConfig` in `com.cameleer3.server.app.config`:
2. Create `SearchBeanConfig` in `com.cameleer.server.app.config`:
- @Configuration class that creates:
- `ClickHouseSearchEngine` bean (takes JdbcTemplate)
- `SearchService` bean (takes SearchEngine)
- `DetailService` bean (takes the execution query interface from Plan 01)
3. Create `SearchController` in `com.cameleer3.server.app.controller`:
3. Create `SearchController` in `com.cameleer.server.app.controller`:
- Inject SearchService.
- `GET /api/v1/search/executions` with @RequestParam for basic filters:
- status (optional String)
@@ -274,7 +274,7 @@ Established controller pattern (from Phase 1):
- Assert response structure matches the envelope format.
</action>
<verify>
<automated>cd C:/Users/Hendrik/Documents/projects/cameleer3-server && mvn test -pl cameleer3-server-app -Dtest=SearchControllerIT</automated>
<automated>cd C:/Users/Hendrik/Documents/projects/cameleer-server && mvn test -pl cameleer-server-app -Dtest=SearchControllerIT</automated>
</verify>
<done>All search filter types work independently and in combination, response envelope has correct format, pagination works correctly, full-text search finds matches in all text fields, LIKE patterns are properly escaped</done>
</task>
@@ -282,10 +282,10 @@ Established controller pattern (from Phase 1):
<task type="auto" tdd="true">
<name>Task 2: DetailController, tree reconstruction, exchange snapshot endpoint, and integration tests</name>
<files>
cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/ClickHouseExecutionRepository.java,
cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/DetailController.java,
cameleer3-server-core/src/test/java/com/cameleer3/server/core/detail/TreeReconstructionTest.java,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/DetailControllerIT.java
cameleer-server-app/src/main/java/com/cameleer/server/app/storage/ClickHouseExecutionRepository.java,
cameleer-server-app/src/main/java/com/cameleer/server/app/controller/DetailController.java,
cameleer-server-core/src/test/java/com/cameleer/server/core/detail/TreeReconstructionTest.java,
cameleer-server-app/src/test/java/com/cameleer/server/app/controller/DetailControllerIT.java
</files>
<behavior>
- Unit test: reconstructTree with [root, child, grandchild], depths=[0,1,2], parents=[-1,0,1] produces single root with one child that has one grandchild
@@ -308,7 +308,7 @@ Established controller pattern (from Phase 1):
- Add `findRawById(String executionId)` method that queries all columns from route_executions WHERE execution_id = ?. Return Optional<RawExecutionRow> (use the record created in Plan 01 or create it here if needed). The RawExecutionRow should contain ALL columns including the parallel arrays for processors.
- Add `findProcessorSnapshot(String executionId, int processorIndex)` method: queries processor_input_bodies[index+1], processor_output_bodies[index+1], processor_input_headers[index+1], processor_output_headers[index+1] for the given execution. Returns a DTO with inputBody, outputBody, inputHeaders, outputHeaders. ClickHouse arrays are 1-indexed in SQL, so add 1 to the Java 0-based index.
3. Create `DetailController` in `com.cameleer3.server.app.controller`:
3. Create `DetailController` in `com.cameleer.server.app.controller`:
- Inject DetailService.
- `GET /api/v1/executions/{executionId}`: call detailService.getDetail(executionId). If empty, return 404. Otherwise return 200 with ExecutionDetail JSON. The processors field is a nested tree of ProcessorNode objects.
- `GET /api/v1/executions/{executionId}/processors/{index}/snapshot`: call repository's findProcessorSnapshot. If execution not found or index out of bounds, return 404. Return JSON with inputBody, outputBody, inputHeaders, outputHeaders. Per user decision: exchange snapshot data fetched separately per processor, not inlined in detail response.
@@ -323,7 +323,7 @@ Established controller pattern (from Phase 1):
- Test GET /api/v1/executions/{id}/processors/999/snapshot: returns 404 for out-of-bounds index.
</action>
<verify>
<automated>cd C:/Users/Hendrik/Documents/projects/cameleer3-server && mvn test -pl cameleer3-server-core -Dtest=TreeReconstructionTest && mvn test -pl cameleer3-server-app -Dtest=DetailControllerIT</automated>
<automated>cd C:/Users/Hendrik/Documents/projects/cameleer-server && mvn test -pl cameleer-server-core -Dtest=TreeReconstructionTest && mvn test -pl cameleer-server-app -Dtest=DetailControllerIT</automated>
</verify>
<done>Tree reconstruction correctly rebuilds nested processor trees from flat arrays, detail endpoint returns nested tree with all fields, snapshot endpoint returns per-processor exchange data, diagram hash included in detail response, all tests pass</done>
</task>
@@ -331,9 +331,9 @@ Established controller pattern (from Phase 1):
</tasks>
<verification>
- `mvn test -pl cameleer3-server-core -Dtest=TreeReconstructionTest` passes (unit test for tree rebuild)
- `mvn test -pl cameleer3-server-app -Dtest=SearchControllerIT` passes (all search filters)
- `mvn test -pl cameleer3-server-app -Dtest=DetailControllerIT` passes (detail + snapshot)
- `mvn test -pl cameleer-server-core -Dtest=TreeReconstructionTest` passes (unit test for tree rebuild)
- `mvn test -pl cameleer-server-app -Dtest=SearchControllerIT` passes (all search filters)
- `mvn test -pl cameleer-server-app -Dtest=DetailControllerIT` passes (detail + snapshot)
- `mvn clean verify` passes (full suite green)
</verification>

View File

@@ -21,16 +21,16 @@ tech-stack:
key-files:
created:
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/search/ClickHouseSearchEngine.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/SearchController.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/DetailController.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/SearchBeanConfig.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/SearchControllerIT.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/DetailControllerIT.java
- cameleer3-server-core/src/test/java/com/cameleer3/server/core/detail/TreeReconstructionTest.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/search/ClickHouseSearchEngine.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/controller/SearchController.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/controller/DetailController.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/config/SearchBeanConfig.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/controller/SearchControllerIT.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/controller/DetailControllerIT.java
- cameleer-server-core/src/test/java/com/cameleer/server/core/detail/TreeReconstructionTest.java
modified:
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/ClickHouseExecutionRepository.java
- cameleer3-server-core/pom.xml
- cameleer-server-app/src/main/java/com/cameleer/server/app/storage/ClickHouseExecutionRepository.java
- cameleer-server-core/pom.xml
key-decisions:
- "Search tests use correlationId scoping and >= assertions for shared ClickHouse isolation"
@@ -77,15 +77,15 @@ Each task was committed atomically:
3. **Task 2 fix: Test isolation for shared ClickHouse** - `079dce5` (fix)
## Files Created/Modified
- `cameleer3-server-app/.../search/ClickHouseSearchEngine.java` - Dynamic SQL search with LIKE escape, implements SearchEngine
- `cameleer3-server-app/.../controller/SearchController.java` - GET + POST /api/v1/search/executions endpoints
- `cameleer3-server-app/.../controller/DetailController.java` - GET /api/v1/executions/{id} and processor snapshot endpoints
- `cameleer3-server-app/.../config/SearchBeanConfig.java` - Wires SearchEngine, SearchService, DetailService beans
- `cameleer3-server-app/.../storage/ClickHouseExecutionRepository.java` - Added findRawById, findProcessorSnapshot, array extraction helpers
- `cameleer3-server-app/.../controller/SearchControllerIT.java` - 13 integration tests for search
- `cameleer3-server-app/.../controller/DetailControllerIT.java` - 6 integration tests for detail/snapshot
- `cameleer3-server-core/.../detail/TreeReconstructionTest.java` - 5 unit tests for tree reconstruction
- `cameleer3-server-core/pom.xml` - Added assertj and mockito test dependencies
- `cameleer-server-app/.../search/ClickHouseSearchEngine.java` - Dynamic SQL search with LIKE escape, implements SearchEngine
- `cameleer-server-app/.../controller/SearchController.java` - GET + POST /api/v1/search/executions endpoints
- `cameleer-server-app/.../controller/DetailController.java` - GET /api/v1/executions/{id} and processor snapshot endpoints
- `cameleer-server-app/.../config/SearchBeanConfig.java` - Wires SearchEngine, SearchService, DetailService beans
- `cameleer-server-app/.../storage/ClickHouseExecutionRepository.java` - Added findRawById, findProcessorSnapshot, array extraction helpers
- `cameleer-server-app/.../controller/SearchControllerIT.java` - 13 integration tests for search
- `cameleer-server-app/.../controller/DetailControllerIT.java` - 6 integration tests for detail/snapshot
- `cameleer-server-core/.../detail/TreeReconstructionTest.java` - 5 unit tests for tree reconstruction
- `cameleer-server-core/pom.xml` - Added assertj and mockito test dependencies
## Decisions Made
- Search tests use correlationId scoping and >= assertions to remain stable when other test classes seed data in the shared ClickHouse container
@@ -99,8 +99,8 @@ Each task was committed atomically:
**1. [Rule 3 - Blocking] Added assertj and mockito test dependencies to core module**
- **Found during:** Task 2 (TreeReconstructionTest compilation)
- **Issue:** Core module only had JUnit Jupiter as test dependency, TreeReconstructionTest needed assertj for assertions and mockito for mock(ExecutionRepository.class)
- **Fix:** Added assertj-core and mockito-core test-scoped dependencies to cameleer3-server-core/pom.xml
- **Files modified:** cameleer3-server-core/pom.xml
- **Fix:** Added assertj-core and mockito-core test-scoped dependencies to cameleer-server-core/pom.xml
- **Files modified:** cameleer-server-core/pom.xml
- **Committed in:** 0615a98 (Task 2 commit)
**2. [Rule 1 - Bug] Fixed search tests failing with shared ClickHouse data**

View File

@@ -5,9 +5,9 @@ type: execute
wave: 1
depends_on: ["02-01", "02-02", "02-03"]
files_modified:
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/ClickHouseExecutionRepository.java
- cameleer3-server-app/pom.xml
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/storage/DiagramLinkingIT.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/storage/ClickHouseExecutionRepository.java
- cameleer-server-app/pom.xml
- cameleer-server-app/src/test/java/com/cameleer/server/app/storage/DiagramLinkingIT.java
autonomous: true
gap_closure: true
requirements: ["DIAG-02"]
@@ -17,13 +17,13 @@ must_haves:
- "Each transaction links to the RouteGraph version that was active at execution time"
- "Full test suite passes with mvn clean verify (no classloader failures)"
artifacts:
- path: "cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/ClickHouseExecutionRepository.java"
- path: "cameleer-server-app/src/main/java/com/cameleer/server/app/storage/ClickHouseExecutionRepository.java"
provides: "Diagram hash lookup during batch insert"
contains: "findContentHashForRoute"
- path: "cameleer3-server-app/pom.xml"
- path: "cameleer-server-app/pom.xml"
provides: "Surefire fork configuration isolating ELK classloader"
contains: "reuseForks"
- path: "cameleer3-server-app/src/test/java/com/cameleer3/server/app/storage/DiagramLinkingIT.java"
- path: "cameleer-server-app/src/test/java/com/cameleer/server/app/storage/DiagramLinkingIT.java"
provides: "Integration test proving diagram hash is stored during ingestion"
key_links:
- from: "ClickHouseExecutionRepository"
@@ -57,18 +57,18 @@ Prior plan summaries (needed — touches same files):
<interfaces>
<!-- Key types and contracts the executor needs. -->
From cameleer3-server-core/.../storage/DiagramRepository.java:
From cameleer-server-core/.../storage/DiagramRepository.java:
```java
Optional<String> findContentHashForRoute(String routeId, String agentId);
```
From cameleer3-server-app/.../storage/ClickHouseExecutionRepository.java (line 141):
From cameleer-server-app/.../storage/ClickHouseExecutionRepository.java (line 141):
```java
ps.setString(col++, ""); // diagram_content_hash (wired later)
```
The class is @Repository annotated, constructor takes JdbcTemplate only. It needs DiagramRepository injected to perform the lookup.
From cameleer3-server-app/.../storage/ClickHouseDiagramRepository.java:
From cameleer-server-app/.../storage/ClickHouseDiagramRepository.java:
```java
@Repository
public class ClickHouseDiagramRepository implements DiagramRepository {
@@ -83,9 +83,9 @@ public class ClickHouseDiagramRepository implements DiagramRepository {
<task type="auto" tdd="true">
<name>Task 1: Populate diagram_content_hash during ingestion and fix Surefire forks</name>
<files>
cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/ClickHouseExecutionRepository.java,
cameleer3-server-app/pom.xml,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/storage/DiagramLinkingIT.java
cameleer-server-app/src/main/java/com/cameleer/server/app/storage/ClickHouseExecutionRepository.java,
cameleer-server-app/pom.xml,
cameleer-server-app/src/test/java/com/cameleer/server/app/storage/DiagramLinkingIT.java
</files>
<behavior>
- Test 1: When a RouteGraph is ingested before a RouteExecution for the same routeId+agentId, the execution's diagram_content_hash column contains the SHA-256 hash of the diagram (not empty string)
@@ -117,7 +117,7 @@ public class ClickHouseDiagramRepository implements DiagramRepository {
**Gap 2 — Surefire classloader isolation:**
5. In `cameleer3-server-app/pom.xml`, add a `<build><plugins>` section (after the existing `spring-boot-maven-plugin`) with `maven-surefire-plugin` configuration:
5. In `cameleer-server-app/pom.xml`, add a `<build><plugins>` section (after the existing `spring-boot-maven-plugin`) with `maven-surefire-plugin` configuration:
```xml
<plugin>
<groupId>org.apache.maven.plugins</groupId>
@@ -131,12 +131,12 @@ public class ClickHouseDiagramRepository implements DiagramRepository {
This forces Surefire to fork a fresh JVM for each test class, isolating ELK's static initializer (LayeredMetaDataProvider + xtext CollectionLiterals) from Spring Boot's classloader. Trade-off: slightly slower test execution, but correct results.
</action>
<verify>
<automated>cd C:/Users/Hendrik/Documents/projects/cameleer3-server && mvn clean verify -pl cameleer3-server-app -am 2>&1 | tail -30</automated>
<automated>cd C:/Users/Hendrik/Documents/projects/cameleer-server && mvn clean verify -pl cameleer-server-app -am 2>&1 | tail -30</automated>
</verify>
<done>
- diagram_content_hash is populated with the active diagram's SHA-256 hash during ingestion (not empty string)
- DiagramLinkingIT passes with both positive and negative cases
- `mvn clean verify` passes for cameleer3-server-app (no classloader failures from ElkDiagramRendererTest)
- `mvn clean verify` passes for cameleer-server-app (no classloader failures from ElkDiagramRendererTest)
</done>
</task>

View File

@@ -20,12 +20,12 @@ tech-stack:
key-files:
created:
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/storage/DiagramLinkingIT.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/storage/DiagramLinkingIT.java
modified:
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/ClickHouseExecutionRepository.java
- cameleer3-server-app/pom.xml
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/storage/IngestionSchemaIT.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/SearchControllerIT.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/storage/ClickHouseExecutionRepository.java
- cameleer-server-app/pom.xml
- cameleer-server-app/src/test/java/com/cameleer/server/app/storage/IngestionSchemaIT.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/controller/SearchControllerIT.java
key-decisions:
- "DiagramRepository injected via constructor into ClickHouseExecutionRepository for diagram hash lookup during batch insert"
@@ -69,11 +69,11 @@ Each task was committed atomically:
**Plan metadata:** (pending)
## Files Created/Modified
- `cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/ClickHouseExecutionRepository.java` - Added DiagramRepository injection, diagram hash lookup in insertBatch
- `cameleer3-server-app/pom.xml` - Added maven-surefire-plugin and maven-failsafe-plugin with reuseForks=false
- `cameleer3-server-app/src/test/java/com/cameleer3/server/app/storage/DiagramLinkingIT.java` - Integration test for diagram hash linking
- `cameleer3-server-app/src/test/java/com/cameleer3/server/app/storage/IngestionSchemaIT.java` - Added ignoreExceptions + increased timeouts
- `cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/SearchControllerIT.java` - Adjusted pagination assertion count
- `cameleer-server-app/src/main/java/com/cameleer/server/app/storage/ClickHouseExecutionRepository.java` - Added DiagramRepository injection, diagram hash lookup in insertBatch
- `cameleer-server-app/pom.xml` - Added maven-surefire-plugin and maven-failsafe-plugin with reuseForks=false
- `cameleer-server-app/src/test/java/com/cameleer/server/app/storage/DiagramLinkingIT.java` - Integration test for diagram hash linking
- `cameleer-server-app/src/test/java/com/cameleer/server/app/storage/IngestionSchemaIT.java` - Added ignoreExceptions + increased timeouts
- `cameleer-server-app/src/test/java/com/cameleer/server/app/controller/SearchControllerIT.java` - Adjusted pagination assertion count
## Decisions Made
- DiagramRepository injected via constructor into ClickHouseExecutionRepository -- both are @Repository Spring beans, so constructor injection autowires cleanly

View File

@@ -44,7 +44,7 @@ Users can find any transaction by status, time, duration, correlation ID, or con
- No execution overlay in server-rendered SVG — the UI handles overlay with theme support (dark/light)
- Top-to-bottom node layout flow
- Nested processors (inside for-each, split, try-catch) rendered in swimlanes to highlight nesting/scope
- Reference: cameleer3 agent repo `examples/route-diagram-example.html` for visual style inspiration (color-coded node types, EIP icons)
- Reference: cameleer agent repo `examples/route-diagram-example.html` for visual style inspiration (color-coded node types, EIP icons)
### Claude's Discretion
- Pagination implementation details (offset/limit vs cursor)
@@ -58,7 +58,7 @@ Users can find any transaction by status, time, duration, correlation ID, or con
<specifics>
## Specific Ideas
- "We want a cmd+k type of search in the UI" — see `cameleer3/examples/cmd-k-search-example.html` for the target UX. Key features:
- "We want a cmd+k type of search in the UI" — see `cameleer/examples/cmd-k-search-example.html` for the target UX. Key features:
- Cross-entity search: single query hits Executions, Routes, Exchanges, Agents with scope tabs and counts
- Filter chips in the input (e.g., `route:order` prefix filtering)
- Inline preview pane with JSON syntax highlighting for selected result

View File

@@ -112,7 +112,7 @@ The existing Phase 1 code provides a solid foundation: `ClickHouseExecutionRepos
### Recommended Project Structure (additions for Phase 2)
```
cameleer3-server-core/src/main/java/com/cameleer3/server/core/
cameleer-server-core/src/main/java/com/cameleer/server/core/
├── search/
│ ├── SearchService.java # Orchestrates search, delegates to SearchEngine
│ ├── SearchEngine.java # Interface for search backends (ClickHouse now, OpenSearch later)
@@ -127,7 +127,7 @@ cameleer3-server-core/src/main/java/com/cameleer3/server/core/
├── ExecutionRepository.java # Extended with query methods
└── DiagramRepository.java # Extended with lookup methods
cameleer3-server-app/src/main/java/com/cameleer3/server/app/
cameleer-server-app/src/main/java/com/cameleer/server/app/
├── controller/
│ ├── SearchController.java # GET + POST /api/v1/search/executions
│ ├── DetailController.java # GET /api/v1/executions/{id}
@@ -517,25 +517,25 @@ private void populateExchangeColumns(PreparedStatement ps, List<FlatProcessor> p
| Property | Value |
|----------|-------|
| Framework | JUnit 5 + Spring Boot Test + Testcontainers ClickHouse 25.3 |
| Config file | cameleer3-server-app/pom.xml (testcontainers dep), AbstractClickHouseIT base class |
| Quick run command | `mvn test -pl cameleer3-server-app -Dtest=SearchControllerIT -Dfailsafe.skip=true` |
| Config file | cameleer-server-app/pom.xml (testcontainers dep), AbstractClickHouseIT base class |
| Quick run command | `mvn test -pl cameleer-server-app -Dtest=SearchControllerIT -Dfailsafe.skip=true` |
| Full suite command | `mvn clean verify` |
### Phase Requirements -> Test Map
| Req ID | Behavior | Test Type | Automated Command | File Exists? |
|--------|----------|-----------|-------------------|-------------|
| SRCH-01 | Filter by status returns matching executions | integration | `mvn test -pl cameleer3-server-app -Dtest=SearchControllerIT#searchByStatus` | No -- Wave 0 |
| SRCH-02 | Filter by time range returns matching executions | integration | `mvn test -pl cameleer3-server-app -Dtest=SearchControllerIT#searchByTimeRange` | No -- Wave 0 |
| SRCH-03 | Filter by duration range returns matching | integration | `mvn test -pl cameleer3-server-app -Dtest=SearchControllerIT#searchByDuration` | No -- Wave 0 |
| SRCH-04 | Filter by correlationId returns correlated | integration | `mvn test -pl cameleer3-server-app -Dtest=SearchControllerIT#searchByCorrelationId` | No -- Wave 0 |
| SRCH-05 | Full-text search across bodies/headers/errors | integration | `mvn test -pl cameleer3-server-app -Dtest=SearchControllerIT#fullTextSearch` | No -- Wave 0 |
| SRCH-06 | Detail returns nested processor tree | integration | `mvn test -pl cameleer3-server-app -Dtest=DetailControllerIT#detailReturnsNestedTree` | No -- Wave 0 |
| DIAG-01 | Content-hash dedup stores identical defs once | integration | `mvn test -pl cameleer3-server-app -Dtest=DiagramControllerIT#contentHashDedup` | Partial (ingestion test exists) |
| DIAG-02 | Transaction links to active diagram version | integration | `mvn test -pl cameleer3-server-app -Dtest=DetailControllerIT#detailIncludesDiagramHash` | No -- Wave 0 |
| DIAG-03 | Diagram rendered as SVG or JSON layout | integration | `mvn test -pl cameleer3-server-app -Dtest=DiagramRenderControllerIT#renderSvg` | No -- Wave 0 |
| SRCH-01 | Filter by status returns matching executions | integration | `mvn test -pl cameleer-server-app -Dtest=SearchControllerIT#searchByStatus` | No -- Wave 0 |
| SRCH-02 | Filter by time range returns matching executions | integration | `mvn test -pl cameleer-server-app -Dtest=SearchControllerIT#searchByTimeRange` | No -- Wave 0 |
| SRCH-03 | Filter by duration range returns matching | integration | `mvn test -pl cameleer-server-app -Dtest=SearchControllerIT#searchByDuration` | No -- Wave 0 |
| SRCH-04 | Filter by correlationId returns correlated | integration | `mvn test -pl cameleer-server-app -Dtest=SearchControllerIT#searchByCorrelationId` | No -- Wave 0 |
| SRCH-05 | Full-text search across bodies/headers/errors | integration | `mvn test -pl cameleer-server-app -Dtest=SearchControllerIT#fullTextSearch` | No -- Wave 0 |
| SRCH-06 | Detail returns nested processor tree | integration | `mvn test -pl cameleer-server-app -Dtest=DetailControllerIT#detailReturnsNestedTree` | No -- Wave 0 |
| DIAG-01 | Content-hash dedup stores identical defs once | integration | `mvn test -pl cameleer-server-app -Dtest=DiagramControllerIT#contentHashDedup` | Partial (ingestion test exists) |
| DIAG-02 | Transaction links to active diagram version | integration | `mvn test -pl cameleer-server-app -Dtest=DetailControllerIT#detailIncludesDiagramHash` | No -- Wave 0 |
| DIAG-03 | Diagram rendered as SVG or JSON layout | integration | `mvn test -pl cameleer-server-app -Dtest=DiagramRenderControllerIT#renderSvg` | No -- Wave 0 |
### Sampling Rate
- **Per task commit:** `mvn test -pl cameleer3-server-app -Dtest=<relevant>IT`
- **Per task commit:** `mvn test -pl cameleer-server-app -Dtest=<relevant>IT`
- **Per wave merge:** `mvn clean verify`
- **Phase gate:** Full suite green before `/gsd:verify-work`
@@ -551,7 +551,7 @@ private void populateExchangeColumns(PreparedStatement ps, List<FlatProcessor> p
### Primary (HIGH confidence)
- ClickHouse JDBC 0.9.7, ClickHouse 25.3 -- verified from project pom.xml and AbstractClickHouseIT
- cameleer3-common 1.0-SNAPSHOT JAR -- decompiled to verify RouteGraph, RouteNode, RouteEdge, NodeType, ProcessorExecution, ExchangeSnapshot field structures
- cameleer-common 1.0-SNAPSHOT JAR -- decompiled to verify RouteGraph, RouteNode, RouteEdge, NodeType, ProcessorExecution, ExchangeSnapshot field structures
- Existing Phase 1 codebase -- ClickHouseExecutionRepository, ClickHouseDiagramRepository, schema, test patterns
### Secondary (MEDIUM confidence)

View File

@@ -18,8 +18,8 @@ created: 2026-03-11
| Property | Value |
|----------|-------|
| **Framework** | JUnit 5 + Spring Boot Test + Testcontainers ClickHouse 25.3 |
| **Config file** | cameleer3-server-app/pom.xml (testcontainers dep), AbstractClickHouseIT base class |
| **Quick run command** | `mvn test -pl cameleer3-server-app -Dtest=SearchControllerIT` |
| **Config file** | cameleer-server-app/pom.xml (testcontainers dep), AbstractClickHouseIT base class |
| **Quick run command** | `mvn test -pl cameleer-server-app -Dtest=SearchControllerIT` |
| **Full suite command** | `mvn clean verify` |
| **Estimated runtime** | ~45 seconds |
@@ -27,7 +27,7 @@ created: 2026-03-11
## Sampling Rate
- **After every task commit:** Run `mvn test -pl cameleer3-server-app -Dtest=<relevant>IT`
- **After every task commit:** Run `mvn test -pl cameleer-server-app -Dtest=<relevant>IT`
- **After every plan wave:** Run `mvn clean verify`
- **Before `/gsd:verify-work`:** Full suite must be green
- **Max feedback latency:** 45 seconds
@@ -38,15 +38,15 @@ created: 2026-03-11
| Task ID | Plan | Wave | Requirement | Test Type | Automated Command | File Exists | Status |
|---------|------|------|-------------|-----------|-------------------|-------------|--------|
| 02-01-01 | 01 | 1 | SRCH-01 | integration | `mvn test -pl cameleer3-server-app -Dtest=SearchControllerIT#searchByStatus` | ❌ W0 | ⬜ pending |
| 02-01-02 | 01 | 1 | SRCH-02 | integration | `mvn test -pl cameleer3-server-app -Dtest=SearchControllerIT#searchByTimeRange` | ❌ W0 | ⬜ pending |
| 02-01-03 | 01 | 1 | SRCH-03 | integration | `mvn test -pl cameleer3-server-app -Dtest=SearchControllerIT#searchByDuration` | ❌ W0 | ⬜ pending |
| 02-01-04 | 01 | 1 | SRCH-04 | integration | `mvn test -pl cameleer3-server-app -Dtest=SearchControllerIT#searchByCorrelationId` | ❌ W0 | ⬜ pending |
| 02-01-05 | 01 | 1 | SRCH-05 | integration | `mvn test -pl cameleer3-server-app -Dtest=SearchControllerIT#fullTextSearch` | ❌ W0 | ⬜ pending |
| 02-01-06 | 01 | 1 | SRCH-06 | integration | `mvn test -pl cameleer3-server-app -Dtest=DetailControllerIT#detailReturnsNestedTree` | ❌ W0 | ⬜ pending |
| 02-02-01 | 02 | 1 | DIAG-01 | integration | `mvn test -pl cameleer3-server-app -Dtest=DiagramControllerIT#contentHashDedup` | Partial | ⬜ pending |
| 02-02-02 | 02 | 1 | DIAG-02 | integration | `mvn test -pl cameleer3-server-app -Dtest=DetailControllerIT#detailIncludesDiagramHash` | ❌ W0 | ⬜ pending |
| 02-02-03 | 02 | 1 | DIAG-03 | integration | `mvn test -pl cameleer3-server-app -Dtest=DiagramRenderControllerIT#renderSvg` | ❌ W0 | ⬜ pending |
| 02-01-01 | 01 | 1 | SRCH-01 | integration | `mvn test -pl cameleer-server-app -Dtest=SearchControllerIT#searchByStatus` | ❌ W0 | ⬜ pending |
| 02-01-02 | 01 | 1 | SRCH-02 | integration | `mvn test -pl cameleer-server-app -Dtest=SearchControllerIT#searchByTimeRange` | ❌ W0 | ⬜ pending |
| 02-01-03 | 01 | 1 | SRCH-03 | integration | `mvn test -pl cameleer-server-app -Dtest=SearchControllerIT#searchByDuration` | ❌ W0 | ⬜ pending |
| 02-01-04 | 01 | 1 | SRCH-04 | integration | `mvn test -pl cameleer-server-app -Dtest=SearchControllerIT#searchByCorrelationId` | ❌ W0 | ⬜ pending |
| 02-01-05 | 01 | 1 | SRCH-05 | integration | `mvn test -pl cameleer-server-app -Dtest=SearchControllerIT#fullTextSearch` | ❌ W0 | ⬜ pending |
| 02-01-06 | 01 | 1 | SRCH-06 | integration | `mvn test -pl cameleer-server-app -Dtest=DetailControllerIT#detailReturnsNestedTree` | ❌ W0 | ⬜ pending |
| 02-02-01 | 02 | 1 | DIAG-01 | integration | `mvn test -pl cameleer-server-app -Dtest=DiagramControllerIT#contentHashDedup` | Partial | ⬜ pending |
| 02-02-02 | 02 | 1 | DIAG-02 | integration | `mvn test -pl cameleer-server-app -Dtest=DetailControllerIT#detailIncludesDiagramHash` | ❌ W0 | ⬜ pending |
| 02-02-03 | 02 | 1 | DIAG-03 | integration | `mvn test -pl cameleer-server-app -Dtest=DiagramRenderControllerIT#renderSvg` | ❌ W0 | ⬜ pending |
*Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky*

View File

@@ -50,9 +50,9 @@ human_verification:
| Artifact | Status | Notes |
|----------|--------|-------|
| `cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/ClickHouseExecutionRepository.java` | VERIFIED | DiagramRepository injected via constructor (line 59); `findContentHashForRoute` called in `setValues()` (lines 144147); former `""` placeholder removed |
| `cameleer3-server-app/pom.xml` | VERIFIED | `maven-surefire-plugin` with `forkCount=1` `reuseForks=false` at lines 95100; `maven-failsafe-plugin` same config at lines 103108 |
| `cameleer3-server-app/src/test/java/com/cameleer3/server/app/storage/DiagramLinkingIT.java` | VERIFIED | 152 lines; 2 integration tests; positive case asserts 64-char hex hash; negative case asserts empty string; uses `ignoreExceptions()` for ClickHouse eventual consistency |
| `cameleer-server-app/src/main/java/com/cameleer/server/app/storage/ClickHouseExecutionRepository.java` | VERIFIED | DiagramRepository injected via constructor (line 59); `findContentHashForRoute` called in `setValues()` (lines 144147); former `""` placeholder removed |
| `cameleer-server-app/pom.xml` | VERIFIED | `maven-surefire-plugin` with `forkCount=1` `reuseForks=false` at lines 95100; `maven-failsafe-plugin` same config at lines 103108 |
| `cameleer-server-app/src/test/java/com/cameleer/server/app/storage/DiagramLinkingIT.java` | VERIFIED | 152 lines; 2 integration tests; positive case asserts 64-char hex hash; negative case asserts empty string; uses `ignoreExceptions()` for ClickHouse eventual consistency |
### Key Link Verification
@@ -62,7 +62,7 @@ human_verification:
| `SearchController` | `SearchService` | constructor injection, `searchService.search()` | WIRED | Previously verified; no regression |
| `DetailController` | `DetailService` | constructor injection, `detailService.getDetail()` | WIRED | Previously verified; no regression |
| `DiagramRenderController` | `DiagramRepository` + `DiagramRenderer` | `findByContentHash()` + `renderSvg()`/`layoutJson()` | WIRED | Previously verified; no regression |
| `Surefire/Failsafe` | ELK classloader isolation | `reuseForks=false` forces fresh JVM per test class | WIRED | Lines 95116 in `cameleer3-server-app/pom.xml` |
| `Surefire/Failsafe` | ELK classloader isolation | `reuseForks=false` forces fresh JVM per test class | WIRED | Lines 95116 in `cameleer-server-app/pom.xml` |
### Requirements Coverage
@@ -108,7 +108,7 @@ Two blockers from the initial verification (2026-03-11T16:00:00Z) have been reso
**Gap 1 resolved — DIAG-02 diagram hash linking:** `ClickHouseExecutionRepository` now injects `DiagramRepository` via constructor and calls `findContentHashForRoute(exec.getRouteId(), "")` in `insertBatch()`. Both the diagram store path and the execution ingest path use `agent_id=""` consistently, so the lookup is correct. `DiagramLinkingIT` provides integration test coverage for both the positive case (hash populated when diagram exists) and negative case (empty string when no diagram exists for the route).
**Gap 2 resolved — Test suite stability:** Both `maven-surefire-plugin` and `maven-failsafe-plugin` in `cameleer3-server-app/pom.xml` are now configured with `forkCount=1` `reuseForks=false`. This forces a fresh JVM per test class, isolating ELK's `LayeredMetaDataProvider` static initializer from Spring Boot's classloader. The SUMMARY reports 51 tests, 0 failures. Test count across 16 test files totals 80 `@Test` methods; the difference from 51 reflects how Surefire/Failsafe counts parameterized and nested tests vs. raw annotation count.
**Gap 2 resolved — Test suite stability:** Both `maven-surefire-plugin` and `maven-failsafe-plugin` in `cameleer-server-app/pom.xml` are now configured with `forkCount=1` `reuseForks=false`. This forces a fresh JVM per test class, isolating ELK's `LayeredMetaDataProvider` static initializer from Spring Boot's classloader. The SUMMARY reports 51 tests, 0 failures. Test count across 16 test files totals 80 `@Test` methods; the difference from 51 reflects how Surefire/Failsafe counts parameterized and nested tests vs. raw annotation count.
No regressions were introduced. All 10 observable truths and all 9 phase requirements are now satisfied. Two items remain for human visual verification (SVG rendering correctness).

View File

@@ -5,21 +5,21 @@ type: execute
wave: 1
depends_on: []
files_modified:
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/agent/AgentInfo.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/agent/AgentState.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/agent/AgentCommand.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/agent/CommandStatus.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/agent/CommandType.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/agent/AgentRegistryService.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/agent/AgentEventListener.java
- cameleer3-server-core/src/test/java/com/cameleer3/server/core/agent/AgentRegistryServiceTest.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/AgentRegistryConfig.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/AgentRegistryBeanConfig.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/agent/AgentLifecycleMonitor.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/AgentRegistrationController.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/Cameleer3ServerApplication.java
- cameleer3-server-app/src/main/resources/application.yml
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/AgentRegistrationControllerIT.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/agent/AgentInfo.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/agent/AgentState.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/agent/AgentCommand.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/agent/CommandStatus.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/agent/CommandType.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/agent/AgentRegistryService.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/agent/AgentEventListener.java
- cameleer-server-core/src/test/java/com/cameleer/server/core/agent/AgentRegistryServiceTest.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/config/AgentRegistryConfig.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/config/AgentRegistryBeanConfig.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/agent/AgentLifecycleMonitor.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/controller/AgentRegistrationController.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/CameleerServerApplication.java
- cameleer-server-app/src/main/resources/application.yml
- cameleer-server-app/src/test/java/com/cameleer/server/app/controller/AgentRegistrationControllerIT.java
autonomous: true
requirements:
- AGNT-01
@@ -34,13 +34,13 @@ must_haves:
- "Server transitions agents LIVE->STALE after 90s without heartbeat, STALE->DEAD 5 minutes after staleTransitionTime"
- "Agent list endpoint GET /api/v1/agents returns all agents, filterable by ?status=LIVE|STALE|DEAD"
artifacts:
- path: "cameleer3-server-core/src/main/java/com/cameleer3/server/core/agent/AgentRegistryService.java"
- path: "cameleer-server-core/src/main/java/com/cameleer/server/core/agent/AgentRegistryService.java"
provides: "Agent registration, heartbeat, lifecycle transitions, find/filter"
- path: "cameleer3-server-core/src/main/java/com/cameleer3/server/core/agent/AgentInfo.java"
- path: "cameleer-server-core/src/main/java/com/cameleer/server/core/agent/AgentInfo.java"
provides: "Agent record with id, name, group, version, routeIds, capabilities, state, timestamps"
- path: "cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/AgentRegistrationController.java"
- path: "cameleer-server-app/src/main/java/com/cameleer/server/app/controller/AgentRegistrationController.java"
provides: "POST /register, POST /{id}/heartbeat, GET /agents endpoints"
- path: "cameleer3-server-app/src/main/java/com/cameleer3/server/app/agent/AgentLifecycleMonitor.java"
- path: "cameleer-server-app/src/main/java/com/cameleer/server/app/agent/AgentLifecycleMonitor.java"
provides: "@Scheduled lifecycle transitions LIVE->STALE->DEAD"
key_links:
- from: "AgentRegistrationController"
@@ -76,14 +76,14 @@ Output: Core domain types (AgentInfo, AgentState, AgentCommand, CommandStatus, C
@.planning/phases/03-agent-registry-sse-push/03-CONTEXT.md
@.planning/phases/03-agent-registry-sse-push/03-RESEARCH.md
@cameleer3-server-core/src/main/java/com/cameleer3/server/core/ingestion/IngestionService.java
@cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/IngestionBeanConfig.java
@cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/IngestionConfig.java
@cameleer3-server-app/src/main/java/com/cameleer3/server/app/ingestion/ClickHouseFlushScheduler.java
@cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/WebConfig.java
@cameleer3-server-app/src/main/java/com/cameleer3/server/app/Cameleer3ServerApplication.java
@cameleer3-server-app/src/main/resources/application.yml
@cameleer3-server-app/src/test/java/com/cameleer3/server/app/AbstractClickHouseIT.java
@cameleer-server-core/src/main/java/com/cameleer/server/core/ingestion/IngestionService.java
@cameleer-server-app/src/main/java/com/cameleer/server/app/config/IngestionBeanConfig.java
@cameleer-server-app/src/main/java/com/cameleer/server/app/config/IngestionConfig.java
@cameleer-server-app/src/main/java/com/cameleer/server/app/ingestion/ClickHouseFlushScheduler.java
@cameleer-server-app/src/main/java/com/cameleer/server/app/config/WebConfig.java
@cameleer-server-app/src/main/java/com/cameleer/server/app/CameleerServerApplication.java
@cameleer-server-app/src/main/resources/application.yml
@cameleer-server-app/src/test/java/com/cameleer/server/app/AbstractClickHouseIT.java
<interfaces>
<!-- Established codebase patterns the executor must follow -->
@@ -99,10 +99,10 @@ Pattern: Controller accepts raw String body:
Pattern: @Scheduled for periodic tasks:
- ClickHouseFlushScheduler uses @Scheduled(fixedDelayString = "${ingestion.flush-interval-ms:1000}")
- @EnableScheduling already on Cameleer3ServerApplication
- @EnableScheduling already on CameleerServerApplication
Pattern: @EnableConfigurationProperties registration:
- Cameleer3ServerApplication has @EnableConfigurationProperties(IngestionConfig.class)
- CameleerServerApplication has @EnableConfigurationProperties(IngestionConfig.class)
- New config classes must be added to this annotation
Pattern: ProtocolVersionInterceptor:
@@ -116,14 +116,14 @@ Pattern: ProtocolVersionInterceptor:
<task type="auto" tdd="true">
<name>Task 1: Core domain types and AgentRegistryService with unit tests</name>
<files>
cameleer3-server-core/src/main/java/com/cameleer3/server/core/agent/AgentInfo.java,
cameleer3-server-core/src/main/java/com/cameleer3/server/core/agent/AgentState.java,
cameleer3-server-core/src/main/java/com/cameleer3/server/core/agent/AgentCommand.java,
cameleer3-server-core/src/main/java/com/cameleer3/server/core/agent/CommandStatus.java,
cameleer3-server-core/src/main/java/com/cameleer3/server/core/agent/CommandType.java,
cameleer3-server-core/src/main/java/com/cameleer3/server/core/agent/AgentRegistryService.java,
cameleer3-server-core/src/main/java/com/cameleer3/server/core/agent/AgentEventListener.java,
cameleer3-server-core/src/test/java/com/cameleer3/server/core/agent/AgentRegistryServiceTest.java
cameleer-server-core/src/main/java/com/cameleer/server/core/agent/AgentInfo.java,
cameleer-server-core/src/main/java/com/cameleer/server/core/agent/AgentState.java,
cameleer-server-core/src/main/java/com/cameleer/server/core/agent/AgentCommand.java,
cameleer-server-core/src/main/java/com/cameleer/server/core/agent/CommandStatus.java,
cameleer-server-core/src/main/java/com/cameleer/server/core/agent/CommandType.java,
cameleer-server-core/src/main/java/com/cameleer/server/core/agent/AgentRegistryService.java,
cameleer-server-core/src/main/java/com/cameleer/server/core/agent/AgentEventListener.java,
cameleer-server-core/src/test/java/com/cameleer/server/core/agent/AgentRegistryServiceTest.java
</files>
<behavior>
- register: new agent ID creates AgentInfo with state LIVE, returns AgentInfo
@@ -142,7 +142,7 @@ Pattern: ProtocolVersionInterceptor:
- findPendingCommands: returns PENDING commands for given agentId
</behavior>
<action>
Create the agent domain model in the core module (package com.cameleer3.server.core.agent):
Create the agent domain model in the core module (package com.cameleer.server.core.agent):
1. **AgentState enum**: LIVE, STALE, DEAD
@@ -182,7 +182,7 @@ Pattern: ProtocolVersionInterceptor:
Write tests FIRST (RED), then implement (GREEN). Test class: AgentRegistryServiceTest.
</action>
<verify>
<automated>mvn test -pl cameleer3-server-core -Dtest=AgentRegistryServiceTest</automated>
<automated>mvn test -pl cameleer-server-core -Dtest=AgentRegistryServiceTest</automated>
</verify>
<done>All unit tests pass: registration (new + re-register), heartbeat (known + unknown), lifecycle transitions (LIVE->STALE->DEAD, heartbeat revives STALE), findAll/findByState/findById, command add/acknowledge/expire. AgentEventListener interface defined.</done>
</task>
@@ -190,13 +190,13 @@ Pattern: ProtocolVersionInterceptor:
<task type="auto">
<name>Task 2: Registration/heartbeat/list controllers, config, lifecycle monitor, integration tests</name>
<files>
cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/AgentRegistryConfig.java,
cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/AgentRegistryBeanConfig.java,
cameleer3-server-app/src/main/java/com/cameleer3/server/app/agent/AgentLifecycleMonitor.java,
cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/AgentRegistrationController.java,
cameleer3-server-app/src/main/java/com/cameleer3/server/app/Cameleer3ServerApplication.java,
cameleer3-server-app/src/main/resources/application.yml,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/AgentRegistrationControllerIT.java
cameleer-server-app/src/main/java/com/cameleer/server/app/config/AgentRegistryConfig.java,
cameleer-server-app/src/main/java/com/cameleer/server/app/config/AgentRegistryBeanConfig.java,
cameleer-server-app/src/main/java/com/cameleer/server/app/agent/AgentLifecycleMonitor.java,
cameleer-server-app/src/main/java/com/cameleer/server/app/controller/AgentRegistrationController.java,
cameleer-server-app/src/main/java/com/cameleer/server/app/CameleerServerApplication.java,
cameleer-server-app/src/main/resources/application.yml,
cameleer-server-app/src/test/java/com/cameleer/server/app/controller/AgentRegistrationControllerIT.java
</files>
<action>
Wire the agent registry into the Spring Boot app and create REST endpoints:
@@ -214,7 +214,7 @@ Pattern: ProtocolVersionInterceptor:
- @Bean AgentRegistryService: `new AgentRegistryService(config.getStaleThresholdMs(), config.getDeadThresholdMs(), config.getCommandExpiryMs())`
Follow IngestionBeanConfig pattern.
3. **Update Cameleer3ServerApplication**: Add AgentRegistryConfig.class to @EnableConfigurationProperties.
3. **Update CameleerServerApplication**: Add AgentRegistryConfig.class to @EnableConfigurationProperties.
4. **Update application.yml**: Add agent-registry section with all defaults (see RESEARCH.md code example). Also add `spring.mvc.async.request-timeout: -1` for SSE support (Plan 02 needs it, but set it now).
@@ -242,7 +242,7 @@ Pattern: ProtocolVersionInterceptor:
- Use TestRestTemplate (already available from AbstractClickHouseIT's @SpringBootTest)
</action>
<verify>
<automated>mvn test -pl cameleer3-server-core,cameleer3-server-app -Dtest="Agent*"</automated>
<automated>mvn test -pl cameleer-server-core,cameleer-server-app -Dtest="Agent*"</automated>
</verify>
<done>POST /register returns 200 with agentId + sseEndpoint + heartbeatIntervalMs. POST /{id}/heartbeat returns 200 for known agents, 404 for unknown. GET /agents returns all agents with optional ?status= filter. AgentLifecycleMonitor runs on schedule. All integration tests pass. mvn clean verify passes.</done>
</task>

View File

@@ -27,22 +27,22 @@ tech-stack:
key-files:
created:
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/agent/AgentInfo.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/agent/AgentState.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/agent/AgentCommand.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/agent/CommandStatus.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/agent/CommandType.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/agent/AgentRegistryService.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/agent/AgentEventListener.java
- cameleer3-server-core/src/test/java/com/cameleer3/server/core/agent/AgentRegistryServiceTest.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/AgentRegistryConfig.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/AgentRegistryBeanConfig.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/agent/AgentLifecycleMonitor.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/AgentRegistrationController.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/AgentRegistrationControllerIT.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/agent/AgentInfo.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/agent/AgentState.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/agent/AgentCommand.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/agent/CommandStatus.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/agent/CommandType.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/agent/AgentRegistryService.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/agent/AgentEventListener.java
- cameleer-server-core/src/test/java/com/cameleer/server/core/agent/AgentRegistryServiceTest.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/config/AgentRegistryConfig.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/config/AgentRegistryBeanConfig.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/agent/AgentLifecycleMonitor.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/controller/AgentRegistrationController.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/controller/AgentRegistrationControllerIT.java
modified:
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/Cameleer3ServerApplication.java
- cameleer3-server-app/src/main/resources/application.yml
- cameleer-server-app/src/main/java/com/cameleer/server/app/CameleerServerApplication.java
- cameleer-server-app/src/main/resources/application.yml
key-decisions:
- "AgentInfo as Java record with wither-style methods for immutable ConcurrentHashMap swapping"
@@ -103,7 +103,7 @@ _Note: Task 1 used TDD with separate RED/GREEN commits_
- `AgentRegistrationController.java` - REST endpoints for agents
- `AgentRegistryServiceTest.java` - 23 unit tests
- `AgentRegistrationControllerIT.java` - 7 integration tests
- `Cameleer3ServerApplication.java` - Added AgentRegistryConfig to @EnableConfigurationProperties
- `CameleerServerApplication.java` - Added AgentRegistryConfig to @EnableConfigurationProperties
- `application.yml` - Added agent-registry config section and spring.mvc.async.request-timeout
## Decisions Made

View File

@@ -5,12 +5,12 @@ type: execute
wave: 2
depends_on: ["03-01"]
files_modified:
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/agent/SseConnectionManager.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/AgentSseController.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/AgentCommandController.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/WebConfig.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/AgentSseControllerIT.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/AgentCommandControllerIT.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/agent/SseConnectionManager.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/controller/AgentSseController.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/controller/AgentCommandController.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/config/WebConfig.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/controller/AgentSseControllerIT.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/controller/AgentCommandControllerIT.java
autonomous: true
requirements:
- AGNT-04
@@ -30,11 +30,11 @@ must_haves:
- "SSE events include event ID for Last-Event-ID reconnection support (no replay of missed events)"
- "Agent can acknowledge command receipt via POST /api/v1/agents/{id}/commands/{commandId}/ack"
artifacts:
- path: "cameleer3-server-app/src/main/java/com/cameleer3/server/app/agent/SseConnectionManager.java"
- path: "cameleer-server-app/src/main/java/com/cameleer/server/app/agent/SseConnectionManager.java"
provides: "Per-agent SseEmitter management, event sending, ping keepalive"
- path: "cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/AgentSseController.java"
- path: "cameleer-server-app/src/main/java/com/cameleer/server/app/controller/AgentSseController.java"
provides: "GET /{id}/events SSE endpoint"
- path: "cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/AgentCommandController.java"
- path: "cameleer-server-app/src/main/java/com/cameleer/server/app/controller/AgentCommandController.java"
provides: "POST command endpoints (single, group, broadcast) + ack endpoint"
key_links:
- from: "AgentCommandController"
@@ -75,49 +75,49 @@ Output: SseConnectionManager, SSE endpoint, command controller (single/group/bro
@.planning/phases/03-agent-registry-sse-push/03-RESEARCH.md
@.planning/phases/03-agent-registry-sse-push/03-01-SUMMARY.md
@cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/WebConfig.java
@cameleer3-server-app/src/main/resources/application.yml
@cameleer3-server-app/src/test/java/com/cameleer3/server/app/AbstractClickHouseIT.java
@cameleer-server-app/src/main/java/com/cameleer/server/app/config/WebConfig.java
@cameleer-server-app/src/main/resources/application.yml
@cameleer-server-app/src/test/java/com/cameleer/server/app/AbstractClickHouseIT.java
<interfaces>
<!-- From Plan 01 (must exist before this plan executes) -->
From cameleer3-server-core/.../agent/AgentInfo.java:
From cameleer-server-core/.../agent/AgentInfo.java:
```java
// Record or class with fields:
// id, name, group, version, routeIds, capabilities, state, registeredAt, lastHeartbeat, staleTransitionTime
// Methods: withState(), withLastHeartbeat(), etc.
```
From cameleer3-server-core/.../agent/AgentState.java:
From cameleer-server-core/.../agent/AgentState.java:
```java
public enum AgentState { LIVE, STALE, DEAD }
```
From cameleer3-server-core/.../agent/CommandType.java:
From cameleer-server-core/.../agent/CommandType.java:
```java
public enum CommandType { CONFIG_UPDATE, DEEP_TRACE, REPLAY }
```
From cameleer3-server-core/.../agent/CommandStatus.java:
From cameleer-server-core/.../agent/CommandStatus.java:
```java
public enum CommandStatus { PENDING, DELIVERED, ACKNOWLEDGED, EXPIRED }
```
From cameleer3-server-core/.../agent/AgentCommand.java:
From cameleer-server-core/.../agent/AgentCommand.java:
```java
// Record: id (UUID string), type (CommandType), payload (String JSON), targetAgentId, createdAt, status
// Method: withStatus()
```
From cameleer3-server-core/.../agent/AgentEventListener.java:
From cameleer-server-core/.../agent/AgentEventListener.java:
```java
public interface AgentEventListener {
void onCommandReady(String agentId, AgentCommand command);
}
```
From cameleer3-server-core/.../agent/AgentRegistryService.java:
From cameleer-server-core/.../agent/AgentRegistryService.java:
```java
// Key methods:
// register(id, name, group, version, routeIds, capabilities) -> AgentInfo
@@ -131,7 +131,7 @@ From cameleer3-server-core/.../agent/AgentRegistryService.java:
// setEventListener(listener) -> void
```
From cameleer3-server-app/.../config/AgentRegistryConfig.java:
From cameleer-server-app/.../config/AgentRegistryConfig.java:
```java
// @ConfigurationProperties(prefix = "agent-registry")
// getPingIntervalMs(), getCommandExpiryMs(), etc.
@@ -144,11 +144,11 @@ From cameleer3-server-app/.../config/AgentRegistryConfig.java:
<task type="auto">
<name>Task 1: SseConnectionManager, SSE controller, and command controller</name>
<files>
cameleer3-server-app/src/main/java/com/cameleer3/server/app/agent/SseConnectionManager.java,
cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/AgentSseController.java,
cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/AgentCommandController.java,
cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/AgentRegistryBeanConfig.java,
cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/WebConfig.java
cameleer-server-app/src/main/java/com/cameleer/server/app/agent/SseConnectionManager.java,
cameleer-server-app/src/main/java/com/cameleer/server/app/controller/AgentSseController.java,
cameleer-server-app/src/main/java/com/cameleer/server/app/controller/AgentCommandController.java,
cameleer-server-app/src/main/java/com/cameleer/server/app/config/AgentRegistryBeanConfig.java,
cameleer-server-app/src/main/java/com/cameleer/server/app/config/WebConfig.java
</files>
<action>
Build the SSE infrastructure and command delivery system:
@@ -181,7 +181,7 @@ From cameleer3-server-app/.../config/AgentRegistryConfig.java:
5. **Update WebConfig**: The SSE endpoint GET /api/v1/agents/{id}/events is already covered by the interceptor pattern "/api/v1/agents/**". Agents send the protocol version header on all requests (per research recommendation), so no exclusion needed. However, if the SSE GET causes issues because browsers/clients may not easily add custom headers to EventSource, add the SSE events path to excludePathPatterns: `/api/v1/agents/*/events`. This is a practical consideration -- add the exclusion to be safe.
</action>
<verify>
<automated>mvn compile -pl cameleer3-server-core,cameleer3-server-app</automated>
<automated>mvn compile -pl cameleer-server-core,cameleer-server-app</automated>
</verify>
<done>SseConnectionManager, AgentSseController, and AgentCommandController compile. SSE endpoint returns SseEmitter. Command endpoints accept type/payload and deliver via SSE. Ping keepalive scheduled. WebConfig updated if needed.</done>
</task>
@@ -189,8 +189,8 @@ From cameleer3-server-app/.../config/AgentRegistryConfig.java:
<task type="auto">
<name>Task 2: Integration tests for SSE, commands, and full flow</name>
<files>
cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/AgentSseControllerIT.java,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/AgentCommandControllerIT.java
cameleer-server-app/src/test/java/com/cameleer/server/app/controller/AgentSseControllerIT.java,
cameleer-server-app/src/test/java/com/cameleer/server/app/controller/AgentCommandControllerIT.java
</files>
<action>
Write integration tests covering SSE connection, command delivery, ping, and acknowledgement:
@@ -224,7 +224,7 @@ From cameleer3-server-app/.../config/AgentRegistryConfig.java:
**Test configuration**: If ping interval needs to be shorter for tests, add to test application.yml or use @TestPropertySource with agent-registry.ping-interval-ms=1000.
</action>
<verify>
<automated>mvn test -pl cameleer3-server-core,cameleer3-server-app -Dtest="Agent*"</automated>
<automated>mvn test -pl cameleer-server-core,cameleer-server-app -Dtest="Agent*"</automated>
</verify>
<done>All SSE integration tests pass: connect/disconnect, config-update/deep-trace/replay delivery via SSE, ping keepalive received, Last-Event-ID accepted, command targeting (single/group/broadcast), command acknowledgement. mvn clean verify passes with all existing tests still green.</done>
</task>

View File

@@ -24,14 +24,14 @@ tech-stack:
key-files:
created:
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/agent/SseConnectionManager.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/AgentSseController.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/AgentCommandController.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/AgentSseControllerIT.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/AgentCommandControllerIT.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/agent/SseConnectionManager.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/controller/AgentSseController.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/controller/AgentCommandController.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/controller/AgentSseControllerIT.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/controller/AgentCommandControllerIT.java
modified:
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/WebConfig.java
- cameleer3-server-app/src/test/resources/application-test.yml
- cameleer-server-app/src/main/java/com/cameleer/server/app/config/WebConfig.java
- cameleer-server-app/src/test/resources/application-test.yml
key-decisions:
- "SSE events path excluded from ProtocolVersionInterceptor for EventSource client compatibility"

View File

@@ -6,7 +6,7 @@
## Summary
This phase adds agent registration, heartbeat-based lifecycle management (LIVE/STALE/DEAD), and real-time command push via SSE to the Cameleer3 server. The technology stack is straightforward: Spring MVC's `SseEmitter` for server-push, `ConcurrentHashMap` for the in-memory agent registry, and `@Scheduled` for periodic lifecycle checks (same pattern already used by `ClickHouseFlushScheduler`).
This phase adds agent registration, heartbeat-based lifecycle management (LIVE/STALE/DEAD), and real-time command push via SSE to the Cameleer server. The technology stack is straightforward: Spring MVC's `SseEmitter` for server-push, `ConcurrentHashMap` for the in-memory agent registry, and `@Scheduled` for periodic lifecycle checks (same pattern already used by `ClickHouseFlushScheduler`).
The main architectural challenge is managing per-agent SSE connections reliably -- handling disconnections, timeouts, and cleanup without leaking threads or emitters. The command delivery model (PENDING with 60s expiry, acknowledgement) adds a second concurrent data structure to manage alongside the registry itself.
@@ -93,7 +93,7 @@ No new dependencies required. Everything is already on the classpath.
### Recommended Project Structure
```
cameleer3-server-core/src/main/java/com/cameleer3/server/core/
cameleer-server-core/src/main/java/com/cameleer/server/core/
├── agent/
│ ├── AgentInfo.java # Record: id, name, group, version, routeIds, capabilities, state, timestamps
│ ├── AgentState.java # Enum: LIVE, STALE, DEAD
@@ -101,7 +101,7 @@ cameleer3-server-core/src/main/java/com/cameleer3/server/core/
│ ├── AgentCommand.java # Record: id, type, payload, targetAgentId, createdAt, status
│ └── CommandStatus.java # Enum: PENDING, DELIVERED, ACKNOWLEDGED, EXPIRED
cameleer3-server-app/src/main/java/com/cameleer3/server/app/
cameleer-server-app/src/main/java/com/cameleer/server/app/
├── config/
│ ├── AgentRegistryConfig.java # @ConfigurationProperties(prefix = "agent-registry")
│ └── AgentRegistryBeanConfig.java # @Configuration: wires AgentRegistryService as bean
@@ -452,30 +452,30 @@ spring:
|----------|-------|
| Framework | JUnit 5 + Spring Boot Test (via spring-boot-starter-test) |
| Config file | pom.xml (Surefire + Failsafe configured) |
| Quick run command | `mvn test -pl cameleer3-server-core -Dtest=AgentRegistryServiceTest` |
| Quick run command | `mvn test -pl cameleer-server-core -Dtest=AgentRegistryServiceTest` |
| Full suite command | `mvn clean verify` |
### Phase Requirements to Test Map
| Req ID | Behavior | Test Type | Automated Command | File Exists? |
|--------|----------|-----------|-------------------|-------------|
| AGNT-01 | Agent registers and gets response | integration | `mvn test -pl cameleer3-server-app -Dtest=AgentRegistrationControllerIT#registerAgent*` | No - Wave 0 |
| AGNT-02 | Lifecycle transitions LIVE/STALE/DEAD | unit | `mvn test -pl cameleer3-server-core -Dtest=AgentRegistryServiceTest#lifecycle*` | No - Wave 0 |
| AGNT-03 | Heartbeat updates timestamp, returns 200/404 | integration | `mvn test -pl cameleer3-server-app -Dtest=AgentRegistrationControllerIT#heartbeat*` | No - Wave 0 |
| AGNT-04 | Config-update pushed via SSE | integration | `mvn test -pl cameleer3-server-app -Dtest=AgentSseControllerIT#configUpdate*` | No - Wave 0 |
| AGNT-05 | Deep-trace command pushed via SSE | integration | `mvn test -pl cameleer3-server-app -Dtest=AgentSseControllerIT#deepTrace*` | No - Wave 0 |
| AGNT-06 | Replay command pushed via SSE | integration | `mvn test -pl cameleer3-server-app -Dtest=AgentSseControllerIT#replay*` | No - Wave 0 |
| AGNT-07 | SSE ping keepalive + Last-Event-ID | integration | `mvn test -pl cameleer3-server-app -Dtest=AgentSseControllerIT#pingKeepalive*` | No - Wave 0 |
| AGNT-01 | Agent registers and gets response | integration | `mvn test -pl cameleer-server-app -Dtest=AgentRegistrationControllerIT#registerAgent*` | No - Wave 0 |
| AGNT-02 | Lifecycle transitions LIVE/STALE/DEAD | unit | `mvn test -pl cameleer-server-core -Dtest=AgentRegistryServiceTest#lifecycle*` | No - Wave 0 |
| AGNT-03 | Heartbeat updates timestamp, returns 200/404 | integration | `mvn test -pl cameleer-server-app -Dtest=AgentRegistrationControllerIT#heartbeat*` | No - Wave 0 |
| AGNT-04 | Config-update pushed via SSE | integration | `mvn test -pl cameleer-server-app -Dtest=AgentSseControllerIT#configUpdate*` | No - Wave 0 |
| AGNT-05 | Deep-trace command pushed via SSE | integration | `mvn test -pl cameleer-server-app -Dtest=AgentSseControllerIT#deepTrace*` | No - Wave 0 |
| AGNT-06 | Replay command pushed via SSE | integration | `mvn test -pl cameleer-server-app -Dtest=AgentSseControllerIT#replay*` | No - Wave 0 |
| AGNT-07 | SSE ping keepalive + Last-Event-ID | integration | `mvn test -pl cameleer-server-app -Dtest=AgentSseControllerIT#pingKeepalive*` | No - Wave 0 |
### Sampling Rate
- **Per task commit:** `mvn test -pl cameleer3-server-core,cameleer3-server-app -Dtest="Agent*"` (agent-related tests only)
- **Per task commit:** `mvn test -pl cameleer-server-core,cameleer-server-app -Dtest="Agent*"` (agent-related tests only)
- **Per wave merge:** `mvn clean verify`
- **Phase gate:** Full suite green before /gsd:verify-work
### Wave 0 Gaps
- [ ] `cameleer3-server-core/.../agent/AgentRegistryServiceTest.java` -- covers AGNT-02, AGNT-03 (unit tests for registry logic)
- [ ] `cameleer3-server-app/.../controller/AgentRegistrationControllerIT.java` -- covers AGNT-01, AGNT-03
- [ ] `cameleer3-server-app/.../controller/AgentSseControllerIT.java` -- covers AGNT-04, AGNT-05, AGNT-06, AGNT-07
- [ ] `cameleer3-server-app/.../controller/AgentCommandControllerIT.java` -- covers command targeting (single, group, all)
- [ ] `cameleer-server-core/.../agent/AgentRegistryServiceTest.java` -- covers AGNT-02, AGNT-03 (unit tests for registry logic)
- [ ] `cameleer-server-app/.../controller/AgentRegistrationControllerIT.java` -- covers AGNT-01, AGNT-03
- [ ] `cameleer-server-app/.../controller/AgentSseControllerIT.java` -- covers AGNT-04, AGNT-05, AGNT-06, AGNT-07
- [ ] `cameleer-server-app/.../controller/AgentCommandControllerIT.java` -- covers command targeting (single, group, all)
- [ ] No new framework install needed -- JUnit 5 + Spring Boot Test + Awaitility already in place
### SSE Test Strategy

View File

@@ -18,8 +18,8 @@ created: 2026-03-11
| Property | Value |
|----------|-------|
| **Framework** | JUnit 5 + Spring Boot Test + Testcontainers ClickHouse 25.3 |
| **Config file** | cameleer3-server-app/pom.xml (Surefire + Failsafe configured) |
| **Quick run command** | `mvn test -pl cameleer3-server-core -Dtest=AgentRegistryServiceTest` |
| **Config file** | cameleer-server-app/pom.xml (Surefire + Failsafe configured) |
| **Quick run command** | `mvn test -pl cameleer-server-core -Dtest=AgentRegistryServiceTest` |
| **Full suite command** | `mvn clean verify` |
| **Estimated runtime** | ~50 seconds |
@@ -27,7 +27,7 @@ created: 2026-03-11
## Sampling Rate
- **After every task commit:** Run `mvn test -pl cameleer3-server-core,cameleer3-server-app -Dtest="Agent*"`
- **After every task commit:** Run `mvn test -pl cameleer-server-core,cameleer-server-app -Dtest="Agent*"`
- **After every plan wave:** Run `mvn clean verify`
- **Before `/gsd:verify-work`:** Full suite must be green
- **Max feedback latency:** 50 seconds
@@ -38,13 +38,13 @@ created: 2026-03-11
| Task ID | Plan | Wave | Requirement | Test Type | Automated Command | File Exists | Status |
|---------|------|------|-------------|-----------|-------------------|-------------|--------|
| 03-01-01 | 01 | 1 | AGNT-01 | integration | `mvn test -pl cameleer3-server-app -Dtest=AgentRegistrationControllerIT#registerAgent*` | ❌ W0 | ⬜ pending |
| 03-01-02 | 01 | 1 | AGNT-02 | unit | `mvn test -pl cameleer3-server-core -Dtest=AgentRegistryServiceTest#lifecycle*` | ❌ W0 | ⬜ pending |
| 03-01-03 | 01 | 1 | AGNT-03 | integration | `mvn test -pl cameleer3-server-app -Dtest=AgentRegistrationControllerIT#heartbeat*` | ❌ W0 | ⬜ pending |
| 03-02-01 | 02 | 1 | AGNT-04 | integration | `mvn test -pl cameleer3-server-app -Dtest=AgentSseControllerIT#configUpdate*` | ❌ W0 | ⬜ pending |
| 03-02-02 | 02 | 1 | AGNT-05 | integration | `mvn test -pl cameleer3-server-app -Dtest=AgentSseControllerIT#deepTrace*` | ❌ W0 | ⬜ pending |
| 03-02-03 | 02 | 1 | AGNT-06 | integration | `mvn test -pl cameleer3-server-app -Dtest=AgentSseControllerIT#replay*` | ❌ W0 | ⬜ pending |
| 03-02-04 | 02 | 1 | AGNT-07 | integration | `mvn test -pl cameleer3-server-app -Dtest=AgentSseControllerIT#pingKeepalive*` | ❌ W0 | ⬜ pending |
| 03-01-01 | 01 | 1 | AGNT-01 | integration | `mvn test -pl cameleer-server-app -Dtest=AgentRegistrationControllerIT#registerAgent*` | ❌ W0 | ⬜ pending |
| 03-01-02 | 01 | 1 | AGNT-02 | unit | `mvn test -pl cameleer-server-core -Dtest=AgentRegistryServiceTest#lifecycle*` | ❌ W0 | ⬜ pending |
| 03-01-03 | 01 | 1 | AGNT-03 | integration | `mvn test -pl cameleer-server-app -Dtest=AgentRegistrationControllerIT#heartbeat*` | ❌ W0 | ⬜ pending |
| 03-02-01 | 02 | 1 | AGNT-04 | integration | `mvn test -pl cameleer-server-app -Dtest=AgentSseControllerIT#configUpdate*` | ❌ W0 | ⬜ pending |
| 03-02-02 | 02 | 1 | AGNT-05 | integration | `mvn test -pl cameleer-server-app -Dtest=AgentSseControllerIT#deepTrace*` | ❌ W0 | ⬜ pending |
| 03-02-03 | 02 | 1 | AGNT-06 | integration | `mvn test -pl cameleer-server-app -Dtest=AgentSseControllerIT#replay*` | ❌ W0 | ⬜ pending |
| 03-02-04 | 02 | 1 | AGNT-07 | integration | `mvn test -pl cameleer-server-app -Dtest=AgentSseControllerIT#pingKeepalive*` | ❌ W0 | ⬜ pending |
*Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky*

View File

@@ -51,18 +51,18 @@ re_verification: false
| Artifact | Expected | Status | Details |
|----------|----------|--------|---------|
| `cameleer3-server-core/src/main/java/com/cameleer3/server/core/agent/AgentRegistryService.java` | Registration, heartbeat, lifecycle, find/filter, commands | VERIFIED | 281 lines; full implementation with ConcurrentHashMap, compute-based atomic swaps, eventListener bridge |
| `cameleer3-server-core/src/main/java/com/cameleer3/server/core/agent/AgentInfo.java` | Immutable record with all fields and wither methods | VERIFIED | 63 lines; record with 10 fields and 5 wither-style methods |
| `cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/AgentRegistrationController.java` | POST /register, POST /{id}/heartbeat, GET /agents | VERIFIED | 153 lines; all three endpoints implemented with OpenAPI annotations |
| `cameleer3-server-app/src/main/java/com/cameleer3/server/app/agent/AgentLifecycleMonitor.java` | @Scheduled LIVE->STALE->DEAD transitions | VERIFIED | 37 lines; calls `registryService.checkLifecycle()` and `expireOldCommands()` on schedule |
| `cameleer-server-core/src/main/java/com/cameleer/server/core/agent/AgentRegistryService.java` | Registration, heartbeat, lifecycle, find/filter, commands | VERIFIED | 281 lines; full implementation with ConcurrentHashMap, compute-based atomic swaps, eventListener bridge |
| `cameleer-server-core/src/main/java/com/cameleer/server/core/agent/AgentInfo.java` | Immutable record with all fields and wither methods | VERIFIED | 63 lines; record with 10 fields and 5 wither-style methods |
| `cameleer-server-app/src/main/java/com/cameleer/server/app/controller/AgentRegistrationController.java` | POST /register, POST /{id}/heartbeat, GET /agents | VERIFIED | 153 lines; all three endpoints implemented with OpenAPI annotations |
| `cameleer-server-app/src/main/java/com/cameleer/server/app/agent/AgentLifecycleMonitor.java` | @Scheduled LIVE->STALE->DEAD transitions | VERIFIED | 37 lines; calls `registryService.checkLifecycle()` and `expireOldCommands()` on schedule |
### Plan 02 Artifacts
| Artifact | Expected | Status | Details |
|----------|----------|--------|---------|
| `cameleer3-server-app/src/main/java/com/cameleer3/server/app/agent/SseConnectionManager.java` | Per-agent SseEmitter management, event sending, ping | VERIFIED | 158 lines; implements AgentEventListener, reference-equality removal, @PostConstruct registration |
| `cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/AgentSseController.java` | GET /{id}/events SSE endpoint | VERIFIED | 67 lines; checks agent exists, delegates to connectionManager.connect() |
| `cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/AgentCommandController.java` | POST commands (single/group/broadcast) + ack | VERIFIED | 182 lines; all four endpoints implemented |
| `cameleer-server-app/src/main/java/com/cameleer/server/app/agent/SseConnectionManager.java` | Per-agent SseEmitter management, event sending, ping | VERIFIED | 158 lines; implements AgentEventListener, reference-equality removal, @PostConstruct registration |
| `cameleer-server-app/src/main/java/com/cameleer/server/app/controller/AgentSseController.java` | GET /{id}/events SSE endpoint | VERIFIED | 67 lines; checks agent exists, delegates to connectionManager.connect() |
| `cameleer-server-app/src/main/java/com/cameleer/server/app/controller/AgentCommandController.java` | POST commands (single/group/broadcast) + ack | VERIFIED | 182 lines; all four endpoints implemented |
### Supporting Artifacts (confirmed present)
@@ -77,7 +77,7 @@ re_verification: false
| `AgentRegistryBeanConfig.java` (@Configuration) | VERIFIED — creates AgentRegistryService with config values |
| `application.yml` | VERIFIED — agent-registry section present; `spring.mvc.async.request-timeout: -1` present |
| `application-test.yml` | VERIFIED — `agent-registry.ping-interval-ms: 1000` for fast SSE test assertions |
| `Cameleer3ServerApplication.java` | VERIFIED — `AgentRegistryConfig.class` added to `@EnableConfigurationProperties` |
| `CameleerServerApplication.java` | VERIFIED — `AgentRegistryConfig.class` added to `@EnableConfigurationProperties` |
---

View File

@@ -5,19 +5,19 @@ type: execute
wave: 1
depends_on: []
files_modified:
- cameleer3-server-app/pom.xml
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/security/JwtService.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/security/Ed25519SigningService.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/JwtServiceImpl.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/Ed25519SigningServiceImpl.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/BootstrapTokenValidator.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/SecurityProperties.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/SecurityBeanConfig.java
- cameleer3-server-app/src/main/resources/application.yml
- cameleer3-server-app/src/test/resources/application-test.yml
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/security/JwtServiceTest.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/security/Ed25519SigningServiceTest.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/security/BootstrapTokenValidatorTest.java
- cameleer-server-app/pom.xml
- cameleer-server-core/src/main/java/com/cameleer/server/core/security/JwtService.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/security/Ed25519SigningService.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/security/JwtServiceImpl.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/security/Ed25519SigningServiceImpl.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/security/BootstrapTokenValidator.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/security/SecurityProperties.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/security/SecurityBeanConfig.java
- cameleer-server-app/src/main/resources/application.yml
- cameleer-server-app/src/test/resources/application-test.yml
- cameleer-server-app/src/test/java/com/cameleer/server/app/security/JwtServiceTest.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/security/Ed25519SigningServiceTest.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/security/BootstrapTokenValidatorTest.java
autonomous: true
requirements:
- SECU-03
@@ -31,15 +31,15 @@ must_haves:
- "BootstrapTokenValidator accepts CAMELEER_AUTH_TOKEN and optionally CAMELEER_AUTH_TOKEN_PREVIOUS using constant-time comparison"
- "Server fails fast on startup if CAMELEER_AUTH_TOKEN is not set"
artifacts:
- path: "cameleer3-server-core/src/main/java/com/cameleer3/server/core/security/JwtService.java"
- path: "cameleer-server-core/src/main/java/com/cameleer/server/core/security/JwtService.java"
provides: "JWT service interface with createAccessToken, createRefreshToken, validateAndExtractAgentId"
- path: "cameleer3-server-core/src/main/java/com/cameleer3/server/core/security/Ed25519SigningService.java"
- path: "cameleer-server-core/src/main/java/com/cameleer/server/core/security/Ed25519SigningService.java"
provides: "Ed25519 signing interface with sign(payload) and getPublicKeyBase64()"
- path: "cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/JwtServiceImpl.java"
- path: "cameleer-server-app/src/main/java/com/cameleer/server/app/security/JwtServiceImpl.java"
provides: "Nimbus JOSE+JWT HMAC-SHA256 implementation"
- path: "cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/Ed25519SigningServiceImpl.java"
- path: "cameleer-server-app/src/main/java/com/cameleer/server/app/security/Ed25519SigningServiceImpl.java"
provides: "JDK 17 Ed25519 KeyPairGenerator implementation"
- path: "cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/BootstrapTokenValidator.java"
- path: "cameleer-server-app/src/main/java/com/cameleer/server/app/security/BootstrapTokenValidator.java"
provides: "Constant-time bootstrap token validation with dual-token rotation"
key_links:
- from: "JwtServiceImpl"
@@ -76,10 +76,10 @@ Output: Working JwtService, Ed25519SigningService, BootstrapTokenValidator with
@.planning/phases/04-security/04-RESEARCH.md
@.planning/phases/04-security/04-VALIDATION.md
@cameleer3-server-app/pom.xml
@cameleer3-server-app/src/main/resources/application.yml
@cameleer3-server-app/src/test/resources/application-test.yml
@cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/AgentRegistryConfig.java
@cameleer-server-app/pom.xml
@cameleer-server-app/src/main/resources/application.yml
@cameleer-server-app/src/test/resources/application-test.yml
@cameleer-server-app/src/main/java/com/cameleer/server/app/config/AgentRegistryConfig.java
<interfaces>
<!-- Existing patterns to follow: core module = interfaces/domain, app module = Spring implementations -->
@@ -106,19 +106,19 @@ public class AgentRegistryConfig { ... }
<task type="auto" tdd="true">
<name>Task 1: Core interfaces + app implementations + Maven deps</name>
<files>
cameleer3-server-app/pom.xml,
cameleer3-server-core/src/main/java/com/cameleer3/server/core/security/JwtService.java,
cameleer3-server-core/src/main/java/com/cameleer3/server/core/security/Ed25519SigningService.java,
cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/JwtServiceImpl.java,
cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/Ed25519SigningServiceImpl.java,
cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/BootstrapTokenValidator.java,
cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/SecurityProperties.java,
cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/SecurityBeanConfig.java,
cameleer3-server-app/src/main/resources/application.yml,
cameleer3-server-app/src/test/resources/application-test.yml,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/security/JwtServiceTest.java,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/security/Ed25519SigningServiceTest.java,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/security/BootstrapTokenValidatorTest.java
cameleer-server-app/pom.xml,
cameleer-server-core/src/main/java/com/cameleer/server/core/security/JwtService.java,
cameleer-server-core/src/main/java/com/cameleer/server/core/security/Ed25519SigningService.java,
cameleer-server-app/src/main/java/com/cameleer/server/app/security/JwtServiceImpl.java,
cameleer-server-app/src/main/java/com/cameleer/server/app/security/Ed25519SigningServiceImpl.java,
cameleer-server-app/src/main/java/com/cameleer/server/app/security/BootstrapTokenValidator.java,
cameleer-server-app/src/main/java/com/cameleer/server/app/security/SecurityProperties.java,
cameleer-server-app/src/main/java/com/cameleer/server/app/security/SecurityBeanConfig.java,
cameleer-server-app/src/main/resources/application.yml,
cameleer-server-app/src/test/resources/application-test.yml,
cameleer-server-app/src/test/java/com/cameleer/server/app/security/JwtServiceTest.java,
cameleer-server-app/src/test/java/com/cameleer/server/app/security/Ed25519SigningServiceTest.java,
cameleer-server-app/src/test/java/com/cameleer/server/app/security/BootstrapTokenValidatorTest.java
</files>
<behavior>
JwtService tests:
@@ -145,7 +145,7 @@ public class AgentRegistryConfig { ... }
- Uses constant-time comparison (MessageDigest.isEqual)
</behavior>
<action>
1. Add Maven dependencies to cameleer3-server-app/pom.xml:
1. Add Maven dependencies to cameleer-server-app/pom.xml:
- `spring-boot-starter-security` (managed version)
- `com.nimbusds:nimbus-jose-jwt:9.47` (explicit, may not be transitive without OAuth2 resource server)
- `spring-security-test` scope test (managed version)
@@ -165,12 +165,12 @@ public class AgentRegistryConfig { ... }
5. Update application-test.yml: Add `security.bootstrap-token: test-bootstrap-token`, `security.bootstrap-token-previous: old-bootstrap-token`. Also set `CAMELEER_AUTH_TOKEN: test-bootstrap-token` as an env override if needed.
6. IMPORTANT: Adding spring-boot-starter-security will break ALL existing tests immediately (401 on all endpoints). To prevent this during Plan 01 (before the security filter chain is configured in Plan 02), add a temporary test security config class `src/test/java/com/cameleer3/server/app/security/TestSecurityConfig.java` annotated `@TestConfiguration` that creates a `SecurityFilterChain` permitting all requests. This keeps existing tests green while security services are built. Plan 02 will replace this with real security config and update tests.
6. IMPORTANT: Adding spring-boot-starter-security will break ALL existing tests immediately (401 on all endpoints). To prevent this during Plan 01 (before the security filter chain is configured in Plan 02), add a temporary test security config class `src/test/java/com/cameleer/server/app/security/TestSecurityConfig.java` annotated `@TestConfiguration` that creates a `SecurityFilterChain` permitting all requests. This keeps existing tests green while security services are built. Plan 02 will replace this with real security config and update tests.
7. Write unit tests per the behavior spec above. Tests should NOT require Spring context -- construct implementations directly with test SecurityProperties.
</action>
<verify>
<automated>cd /c/Users/Hendrik/Documents/projects/cameleer3-server && mvn test -pl cameleer3-server-app -Dtest="JwtServiceTest,Ed25519SigningServiceTest,BootstrapTokenValidatorTest" -Dsurefire.reuseForks=false</automated>
<automated>cd /c/Users/Hendrik/Documents/projects/cameleer-server && mvn test -pl cameleer-server-app -Dtest="JwtServiceTest,Ed25519SigningServiceTest,BootstrapTokenValidatorTest" -Dsurefire.reuseForks=false</automated>
</verify>
<done>
- JwtService creates and validates access/refresh JWTs with correct claims and expiry

View File

@@ -25,22 +25,22 @@ tech-stack:
key-files:
created:
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/security/JwtService.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/security/Ed25519SigningService.java
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/security/InvalidTokenException.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/JwtServiceImpl.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/Ed25519SigningServiceImpl.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/BootstrapTokenValidator.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/SecurityProperties.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/SecurityBeanConfig.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/security/TestSecurityConfig.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/security/JwtServiceTest.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/security/Ed25519SigningServiceTest.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/security/BootstrapTokenValidatorTest.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/security/JwtService.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/security/Ed25519SigningService.java
- cameleer-server-core/src/main/java/com/cameleer/server/core/security/InvalidTokenException.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/security/JwtServiceImpl.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/security/Ed25519SigningServiceImpl.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/security/BootstrapTokenValidator.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/security/SecurityProperties.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/security/SecurityBeanConfig.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/security/TestSecurityConfig.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/security/JwtServiceTest.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/security/Ed25519SigningServiceTest.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/security/BootstrapTokenValidatorTest.java
modified:
- cameleer3-server-app/pom.xml
- cameleer3-server-app/src/main/resources/application.yml
- cameleer3-server-app/src/test/resources/application-test.yml
- cameleer-server-app/pom.xml
- cameleer-server-app/src/main/resources/application.yml
- cameleer-server-app/src/test/resources/application-test.yml
key-decisions:
- "HMAC-SHA256 with ephemeral 256-bit secret for JWT signing (simpler than Ed25519 for tokens, Ed25519 reserved for config signing)"
@@ -91,21 +91,21 @@ _No REFACTOR commit needed -- implementations are clean and minimal._
## Files Created/Modified
- `cameleer3-server-core/.../security/JwtService.java` - JWT service interface with create/validate methods
- `cameleer3-server-core/.../security/Ed25519SigningService.java` - Ed25519 signing interface with sign/getPublicKeyBase64
- `cameleer3-server-core/.../security/InvalidTokenException.java` - Runtime exception for invalid/expired/wrong-type tokens
- `cameleer3-server-app/.../security/JwtServiceImpl.java` - Nimbus JOSE+JWT HMAC-SHA256 implementation
- `cameleer3-server-app/.../security/Ed25519SigningServiceImpl.java` - JDK 17 Ed25519 KeyPairGenerator implementation
- `cameleer3-server-app/.../security/BootstrapTokenValidator.java` - Constant-time bootstrap token validation
- `cameleer3-server-app/.../security/SecurityProperties.java` - Config properties for token expiry and bootstrap tokens
- `cameleer3-server-app/.../security/SecurityBeanConfig.java` - Bean wiring with fail-fast startup validation
- `cameleer3-server-app/.../security/TestSecurityConfig.java` - Temporary permit-all for existing test compatibility
- `cameleer3-server-app/pom.xml` - Added nimbus-jose-jwt, spring-boot-starter-security, spring-security-test
- `cameleer3-server-app/.../application.yml` - Security config section with env var mapping
- `cameleer3-server-app/.../application-test.yml` - Test bootstrap token values
- `cameleer3-server-app/.../security/JwtServiceTest.java` - 7 unit tests for JWT creation/validation
- `cameleer3-server-app/.../security/Ed25519SigningServiceTest.java` - 5 unit tests for signing/verification
- `cameleer3-server-app/.../security/BootstrapTokenValidatorTest.java` - 6 unit tests for token matching
- `cameleer-server-core/.../security/JwtService.java` - JWT service interface with create/validate methods
- `cameleer-server-core/.../security/Ed25519SigningService.java` - Ed25519 signing interface with sign/getPublicKeyBase64
- `cameleer-server-core/.../security/InvalidTokenException.java` - Runtime exception for invalid/expired/wrong-type tokens
- `cameleer-server-app/.../security/JwtServiceImpl.java` - Nimbus JOSE+JWT HMAC-SHA256 implementation
- `cameleer-server-app/.../security/Ed25519SigningServiceImpl.java` - JDK 17 Ed25519 KeyPairGenerator implementation
- `cameleer-server-app/.../security/BootstrapTokenValidator.java` - Constant-time bootstrap token validation
- `cameleer-server-app/.../security/SecurityProperties.java` - Config properties for token expiry and bootstrap tokens
- `cameleer-server-app/.../security/SecurityBeanConfig.java` - Bean wiring with fail-fast startup validation
- `cameleer-server-app/.../security/TestSecurityConfig.java` - Temporary permit-all for existing test compatibility
- `cameleer-server-app/pom.xml` - Added nimbus-jose-jwt, spring-boot-starter-security, spring-security-test
- `cameleer-server-app/.../application.yml` - Security config section with env var mapping
- `cameleer-server-app/.../application-test.yml` - Test bootstrap token values
- `cameleer-server-app/.../security/JwtServiceTest.java` - 7 unit tests for JWT creation/validation
- `cameleer-server-app/.../security/Ed25519SigningServiceTest.java` - 5 unit tests for signing/verification
- `cameleer-server-app/.../security/BootstrapTokenValidatorTest.java` - 6 unit tests for token matching
## Decisions Made

View File

@@ -5,17 +5,17 @@ type: execute
wave: 2
depends_on: ["04-01"]
files_modified:
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/JwtAuthenticationFilter.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/SecurityConfig.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/AgentRegistrationController.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/AgentSseController.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/WebConfig.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/security/SecurityFilterIT.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/security/JwtRefreshIT.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/security/RegistrationSecurityIT.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/security/BootstrapTokenIT.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/TestSecurityHelper.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/security/TestSecurityConfig.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/security/JwtAuthenticationFilter.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/security/SecurityConfig.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/controller/AgentRegistrationController.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/controller/AgentSseController.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/config/WebConfig.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/security/SecurityFilterIT.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/security/JwtRefreshIT.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/security/RegistrationSecurityIT.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/security/BootstrapTokenIT.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/TestSecurityHelper.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/security/TestSecurityConfig.java
autonomous: true
requirements:
- SECU-01
@@ -30,11 +30,11 @@ must_haves:
- "SSE endpoint accepts JWT via ?token= query parameter"
- "Health endpoint and Swagger UI remain publicly accessible"
artifacts:
- path: "cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/JwtAuthenticationFilter.java"
- path: "cameleer-server-app/src/main/java/com/cameleer/server/app/security/JwtAuthenticationFilter.java"
provides: "OncePerRequestFilter extracting JWT from header or query param"
- path: "cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/SecurityConfig.java"
- path: "cameleer-server-app/src/main/java/com/cameleer/server/app/security/SecurityConfig.java"
provides: "SecurityFilterChain with permitAll for public paths, authenticated for rest"
- path: "cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/AgentRegistrationController.java"
- path: "cameleer-server-app/src/main/java/com/cameleer/server/app/controller/AgentRegistrationController.java"
provides: "Updated register endpoint with bootstrap token validation, JWT issuance, public key"
key_links:
- from: "JwtAuthenticationFilter"
@@ -76,11 +76,11 @@ Output: Working security filter chain with protected/public endpoints, registrat
@.planning/phases/04-security/04-VALIDATION.md
@.planning/phases/04-security/04-01-SUMMARY.md
@cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/AgentRegistrationController.java
@cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/AgentSseController.java
@cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/WebConfig.java
@cameleer3-server-app/src/test/java/com/cameleer3/server/app/AbstractClickHouseIT.java
@cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/AgentRegistrationControllerIT.java
@cameleer-server-app/src/main/java/com/cameleer/server/app/controller/AgentRegistrationController.java
@cameleer-server-app/src/main/java/com/cameleer/server/app/controller/AgentSseController.java
@cameleer-server-app/src/main/java/com/cameleer/server/app/config/WebConfig.java
@cameleer-server-app/src/test/java/com/cameleer/server/app/AbstractClickHouseIT.java
@cameleer-server-app/src/test/java/com/cameleer/server/app/controller/AgentRegistrationControllerIT.java
<interfaces>
<!-- From Plan 01 (will exist after execution): -->
@@ -136,11 +136,11 @@ public class AgentRegistryService {
<task type="auto">
<name>Task 1: SecurityFilterChain + JwtAuthenticationFilter + registration/refresh integration</name>
<files>
cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/JwtAuthenticationFilter.java,
cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/SecurityConfig.java,
cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/AgentRegistrationController.java,
cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/AgentSseController.java,
cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/WebConfig.java
cameleer-server-app/src/main/java/com/cameleer/server/app/security/JwtAuthenticationFilter.java,
cameleer-server-app/src/main/java/com/cameleer/server/app/security/SecurityConfig.java,
cameleer-server-app/src/main/java/com/cameleer/server/app/controller/AgentRegistrationController.java,
cameleer-server-app/src/main/java/com/cameleer/server/app/controller/AgentSseController.java,
cameleer-server-app/src/main/java/com/cameleer/server/app/config/WebConfig.java
</files>
<action>
1. Create `JwtAuthenticationFilter extends OncePerRequestFilter` (NOT annotated @Component -- constructed in SecurityConfig to avoid double registration):
@@ -176,7 +176,7 @@ public class AgentRegistryService {
6. Update `WebConfig` if needed: The `ProtocolVersionInterceptor` excluded paths should align with Spring Security public paths. The SSE events path is already excluded from protocol version check (Phase 3 decision). Verify no conflicts.
</action>
<verify>
<automated>cd /c/Users/Hendrik/Documents/projects/cameleer3-server && mvn clean compile -pl cameleer3-server-app</automated>
<automated>cd /c/Users/Hendrik/Documents/projects/cameleer-server && mvn clean compile -pl cameleer-server-app</automated>
</verify>
<done>
- SecurityConfig creates stateless filter chain with correct public/protected path split
@@ -190,28 +190,28 @@ public class AgentRegistryService {
<task type="auto">
<name>Task 2: Security integration tests + existing test adaptation</name>
<files>
cameleer3-server-app/src/test/java/com/cameleer3/server/app/TestSecurityHelper.java,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/security/TestSecurityConfig.java,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/security/SecurityFilterIT.java,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/security/JwtRefreshIT.java,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/security/RegistrationSecurityIT.java,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/security/BootstrapTokenIT.java,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/AgentRegistrationControllerIT.java,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/ExecutionControllerIT.java,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/DiagramControllerIT.java,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/MetricsControllerIT.java,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/BackpressureIT.java,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/DiagramRenderControllerIT.java,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/DetailControllerIT.java,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/SearchControllerIT.java,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/AgentCommandControllerIT.java,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/AgentSseControllerIT.java,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/storage/DiagramLinkingIT.java,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/storage/IngestionSchemaIT.java,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/interceptor/ProtocolVersionIT.java,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/OpenApiIT.java,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/ForwardCompatIT.java,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/HealthControllerIT.java
cameleer-server-app/src/test/java/com/cameleer/server/app/TestSecurityHelper.java,
cameleer-server-app/src/test/java/com/cameleer/server/app/security/TestSecurityConfig.java,
cameleer-server-app/src/test/java/com/cameleer/server/app/security/SecurityFilterIT.java,
cameleer-server-app/src/test/java/com/cameleer/server/app/security/JwtRefreshIT.java,
cameleer-server-app/src/test/java/com/cameleer/server/app/security/RegistrationSecurityIT.java,
cameleer-server-app/src/test/java/com/cameleer/server/app/security/BootstrapTokenIT.java,
cameleer-server-app/src/test/java/com/cameleer/server/app/controller/AgentRegistrationControllerIT.java,
cameleer-server-app/src/test/java/com/cameleer/server/app/controller/ExecutionControllerIT.java,
cameleer-server-app/src/test/java/com/cameleer/server/app/controller/DiagramControllerIT.java,
cameleer-server-app/src/test/java/com/cameleer/server/app/controller/MetricsControllerIT.java,
cameleer-server-app/src/test/java/com/cameleer/server/app/controller/BackpressureIT.java,
cameleer-server-app/src/test/java/com/cameleer/server/app/controller/DiagramRenderControllerIT.java,
cameleer-server-app/src/test/java/com/cameleer/server/app/controller/DetailControllerIT.java,
cameleer-server-app/src/test/java/com/cameleer/server/app/controller/SearchControllerIT.java,
cameleer-server-app/src/test/java/com/cameleer/server/app/controller/AgentCommandControllerIT.java,
cameleer-server-app/src/test/java/com/cameleer/server/app/controller/AgentSseControllerIT.java,
cameleer-server-app/src/test/java/com/cameleer/server/app/storage/DiagramLinkingIT.java,
cameleer-server-app/src/test/java/com/cameleer/server/app/storage/IngestionSchemaIT.java,
cameleer-server-app/src/test/java/com/cameleer/server/app/interceptor/ProtocolVersionIT.java,
cameleer-server-app/src/test/java/com/cameleer/server/app/controller/OpenApiIT.java,
cameleer-server-app/src/test/java/com/cameleer/server/app/controller/ForwardCompatIT.java,
cameleer-server-app/src/test/java/com/cameleer/server/app/controller/HealthControllerIT.java
</files>
<action>
1. Replace the Plan 01 temporary `TestSecurityConfig` (permit-all) with real security active in tests. Remove the permit-all override so tests run with actual security enforcement.
@@ -259,7 +259,7 @@ public class AgentRegistryService {
- Test: New access token from refresh can access protected endpoints
</action>
<verify>
<automated>cd /c/Users/Hendrik/Documents/projects/cameleer3-server && mvn clean verify</automated>
<automated>cd /c/Users/Hendrik/Documents/projects/cameleer-server && mvn clean verify</automated>
</verify>
<done>
- All 17 existing ITs pass with JWT authentication

View File

@@ -25,31 +25,31 @@ tech-stack:
key-files:
created:
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/JwtAuthenticationFilter.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/SecurityConfig.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/TestSecurityHelper.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/security/SecurityFilterIT.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/security/BootstrapTokenIT.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/security/RegistrationSecurityIT.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/security/JwtRefreshIT.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/security/JwtAuthenticationFilter.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/security/SecurityConfig.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/TestSecurityHelper.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/security/SecurityFilterIT.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/security/BootstrapTokenIT.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/security/RegistrationSecurityIT.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/security/JwtRefreshIT.java
modified:
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/AgentRegistrationController.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/WebConfig.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/security/TestSecurityConfig.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/AgentRegistrationControllerIT.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/ExecutionControllerIT.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/DiagramControllerIT.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/MetricsControllerIT.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/BackpressureIT.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/DiagramRenderControllerIT.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/DetailControllerIT.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/SearchControllerIT.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/AgentCommandControllerIT.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/AgentSseControllerIT.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/storage/DiagramLinkingIT.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/storage/IngestionSchemaIT.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/interceptor/ProtocolVersionIT.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/ForwardCompatIT.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/controller/AgentRegistrationController.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/config/WebConfig.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/security/TestSecurityConfig.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/controller/AgentRegistrationControllerIT.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/controller/ExecutionControllerIT.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/controller/DiagramControllerIT.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/controller/MetricsControllerIT.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/controller/BackpressureIT.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/controller/DiagramRenderControllerIT.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/controller/DetailControllerIT.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/controller/SearchControllerIT.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/controller/AgentCommandControllerIT.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/controller/AgentSseControllerIT.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/storage/DiagramLinkingIT.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/storage/IngestionSchemaIT.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/interceptor/ProtocolVersionIT.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/controller/ForwardCompatIT.java
key-decisions:
- "Added /error to SecurityConfig permitAll to allow Spring Boot error page forwarding through security"

View File

@@ -5,10 +5,10 @@ type: execute
wave: 2
depends_on: ["04-01"]
files_modified:
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/agent/SseConnectionManager.java
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/agent/SsePayloadSigner.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/security/SseSigningIT.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/agent/SsePayloadSignerTest.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/agent/SseConnectionManager.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/agent/SsePayloadSigner.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/security/SseSigningIT.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/agent/SsePayloadSignerTest.java
autonomous: true
requirements:
- SECU-04
@@ -19,9 +19,9 @@ must_haves:
- "Signature is computed over the payload JSON without the signature field, then added as a 'signature' field"
- "Agent can verify the signature using the public key received at registration"
artifacts:
- path: "cameleer3-server-app/src/main/java/com/cameleer3/server/app/agent/SsePayloadSigner.java"
- path: "cameleer-server-app/src/main/java/com/cameleer/server/app/agent/SsePayloadSigner.java"
provides: "Component that signs SSE command payloads before delivery"
- path: "cameleer3-server-app/src/main/java/com/cameleer3/server/app/agent/SseConnectionManager.java"
- path: "cameleer-server-app/src/main/java/com/cameleer/server/app/agent/SseConnectionManager.java"
provides: "Updated onCommandReady with signing before sendEvent"
key_links:
- from: "SseConnectionManager.onCommandReady"
@@ -54,7 +54,7 @@ Output: All SSE command events carry verifiable Ed25519 signatures.
@.planning/phases/04-security/04-RESEARCH.md
@.planning/phases/04-security/04-01-SUMMARY.md
@cameleer3-server-app/src/main/java/com/cameleer3/server/app/agent/SseConnectionManager.java
@cameleer-server-app/src/main/java/com/cameleer/server/app/agent/SseConnectionManager.java
<interfaces>
<!-- From Plan 01 (will exist after execution): -->
@@ -98,10 +98,10 @@ public record AgentCommand(String id, CommandType type, String payload, String a
<task type="auto" tdd="true">
<name>Task 1: SsePayloadSigner + signing integration in SseConnectionManager</name>
<files>
cameleer3-server-app/src/main/java/com/cameleer3/server/app/agent/SsePayloadSigner.java,
cameleer3-server-app/src/main/java/com/cameleer3/server/app/agent/SseConnectionManager.java,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/agent/SsePayloadSignerTest.java,
cameleer3-server-app/src/test/java/com/cameleer3/server/app/security/SseSigningIT.java
cameleer-server-app/src/main/java/com/cameleer/server/app/agent/SsePayloadSigner.java,
cameleer-server-app/src/main/java/com/cameleer/server/app/agent/SseConnectionManager.java,
cameleer-server-app/src/test/java/com/cameleer/server/app/agent/SsePayloadSignerTest.java,
cameleer-server-app/src/test/java/com/cameleer/server/app/security/SseSigningIT.java
</files>
<behavior>
SsePayloadSigner unit tests:
@@ -155,7 +155,7 @@ public record AgentCommand(String id, CommandType type, String payload, String a
- NOTE: This test depends on Plan 02's bootstrap token and JWT auth being in place. If Plan 03 executes before Plan 02, the test will need the TestSecurityHelper or a different auth approach. Since both are Wave 2 but independent, document this: "If Plan 02 is not yet complete, use TestSecurityHelper from Plan 01's temporary permit-all config."
</action>
<verify>
<automated>cd /c/Users/Hendrik/Documents/projects/cameleer3-server && mvn test -pl cameleer3-server-app -Dtest="SsePayloadSignerTest,SseSigningIT" -Dsurefire.reuseForks=false</automated>
<automated>cd /c/Users/Hendrik/Documents/projects/cameleer-server && mvn test -pl cameleer-server-app -Dtest="SsePayloadSignerTest,SseSigningIT" -Dsurefire.reuseForks=false</automated>
</verify>
<done>
- SsePayloadSigner signs JSON payloads with Ed25519 and adds signature field

View File

@@ -22,11 +22,11 @@ tech-stack:
key-files:
created:
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/agent/SsePayloadSigner.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/agent/SsePayloadSignerTest.java
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/security/SseSigningIT.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/agent/SsePayloadSigner.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/agent/SsePayloadSignerTest.java
- cameleer-server-app/src/test/java/com/cameleer/server/app/security/SseSigningIT.java
modified:
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/agent/SseConnectionManager.java
- cameleer-server-app/src/main/java/com/cameleer/server/app/agent/SseConnectionManager.java
key-decisions:
- "Signed payload parsed to JsonNode before passing to SseEmitter to avoid double-quoting raw JSON strings"
@@ -73,10 +73,10 @@ _No REFACTOR commit needed -- implementation is clean and minimal._
## Files Created/Modified
- `cameleer3-server-app/.../agent/SsePayloadSigner.java` - Component that signs JSON payloads with Ed25519 and adds signature field
- `cameleer3-server-app/.../agent/SseConnectionManager.java` - Updated onCommandReady to sign payload before SSE delivery
- `cameleer3-server-app/.../agent/SsePayloadSignerTest.java` - 7 unit tests for signing behavior and edge cases
- `cameleer3-server-app/.../security/SseSigningIT.java` - 2 integration tests for end-to-end signature verification
- `cameleer-server-app/.../agent/SsePayloadSigner.java` - Component that signs JSON payloads with Ed25519 and adds signature field
- `cameleer-server-app/.../agent/SseConnectionManager.java` - Updated onCommandReady to sign payload before SSE delivery
- `cameleer-server-app/.../agent/SsePayloadSignerTest.java` - 7 unit tests for signing behavior and edge cases
- `cameleer-server-app/.../security/SseSigningIT.java` - 2 integration tests for end-to-end signature verification
## Decisions Made

View File

@@ -6,7 +6,7 @@
## Summary
This phase adds authentication and integrity protection to the Cameleer3 server. The implementation uses Spring Security 6.4.3 (managed by Spring Boot 3.4.3) with a custom `OncePerRequestFilter` for JWT validation, JDK 17 built-in Ed25519 for signing SSE payloads, and environment variable-based bootstrap tokens for agent registration. The approach is deliberately simple -- no OAuth2 resource server, no external identity provider, just symmetric HMAC JWTs for access control and Ed25519 signatures for payload integrity.
This phase adds authentication and integrity protection to the Cameleer server. The implementation uses Spring Security 6.4.3 (managed by Spring Boot 3.4.3) with a custom `OncePerRequestFilter` for JWT validation, JDK 17 built-in Ed25519 for signing SSE payloads, and environment variable-based bootstrap tokens for agent registration. The approach is deliberately simple -- no OAuth2 resource server, no external identity provider, just symmetric HMAC JWTs for access control and Ed25519 signatures for payload integrity.
The existing codebase has clear integration points: `AgentRegistrationController.register()` already returns `serverPublicKey: null` as a placeholder, `SseConnectionManager.onCommandReady()` is the signing hook for SSE events, and `WebConfig` already defines excluded paths that align with the public endpoint list. Spring Security's `SecurityFilterChain` replaces the need for hand-rolled authorization logic -- endpoints are protected by default, with explicit `permitAll()` for health, register, and docs.
@@ -89,7 +89,7 @@ The existing codebase has clear integration points: `AgentRegistrationController
- **Ed25519 library:** Use JDK built-in. Zero external dependencies, native performance, well-tested in JDK 17+.
- **Refresh token storage:** Use stateless signed refresh tokens (also HMAC-signed JWTs with different claims/expiry). This avoids any in-memory storage for refresh tokens and scales naturally. The refresh token is just a JWT with `type=refresh`, `sub=agentId`, and 7-day expiry. On refresh, validate the refresh JWT, check agent still exists, issue new access JWT.
**Installation (add to cameleer3-server-app pom.xml):**
**Installation (add to cameleer-server-app pom.xml):**
```xml
<dependency>
<groupId>org.springframework.boot</groupId>
@@ -108,12 +108,12 @@ Note: If `spring-boot-starter-security` brings Nimbus transitively (via `spring-
### Recommended Project Structure
```
cameleer3-server-core/src/main/java/com/cameleer3/server/core/
cameleer-server-core/src/main/java/com/cameleer/server/core/
security/
JwtService.java # Interface: createAccessToken, createRefreshToken, validateToken, extractAgentId
Ed25519SigningService.java # Interface: sign(payload) -> signature, getPublicKeyBase64()
cameleer3-server-app/src/main/java/com/cameleer3/server/app/
cameleer-server-app/src/main/java/com/cameleer/server/app/
security/
JwtServiceImpl.java # Nimbus JOSE+JWT HMAC implementation
Ed25519SigningServiceImpl.java # JDK Ed25519 keypair + signing implementation
@@ -439,23 +439,23 @@ public boolean validateBootstrapToken(String provided) {
| Property | Value |
|----------|-------|
| Framework | JUnit 5 + Spring Boot Test (spring-boot-starter-test) |
| Config file | `cameleer3-server-app/src/test/resources/application-test.yml` |
| Quick run command | `mvn test -pl cameleer3-server-app -Dtest=Security*Test -Dsurefire.reuseForks=false` |
| Config file | `cameleer-server-app/src/test/resources/application-test.yml` |
| Quick run command | `mvn test -pl cameleer-server-app -Dtest=Security*Test -Dsurefire.reuseForks=false` |
| Full suite command | `mvn clean verify` |
### Phase Requirements to Test Map
| Req ID | Behavior | Test Type | Automated Command | File Exists? |
|--------|----------|-----------|-------------------|-------------|
| SECU-01 | Protected endpoints reject requests without JWT; public endpoints accessible | integration | `mvn test -pl cameleer3-server-app -Dtest=SecurityFilterIT -Dsurefire.reuseForks=false` | No -- Wave 0 |
| SECU-02 | Refresh endpoint issues new access JWT from valid refresh token | integration | `mvn test -pl cameleer3-server-app -Dtest=JwtRefreshIT -Dsurefire.reuseForks=false` | No -- Wave 0 |
| SECU-03 | Ed25519 keypair generated at startup; public key in registration response | integration | `mvn test -pl cameleer3-server-app -Dtest=RegistrationSecurityIT -Dsurefire.reuseForks=false` | No -- Wave 0 |
| SECU-04 | SSE payloads carry valid Ed25519 signature | integration | `mvn test -pl cameleer3-server-app -Dtest=SseSigningIT -Dsurefire.reuseForks=false` | No -- Wave 0 |
| SECU-05 | Bootstrap token required for registration; rejects invalid/missing tokens | integration | `mvn test -pl cameleer3-server-app -Dtest=BootstrapTokenIT -Dsurefire.reuseForks=false` | No -- Wave 0 |
| N/A | JWT creation, validation, expiry logic | unit | `mvn test -pl cameleer3-server-app -Dtest=JwtServiceTest -Dsurefire.reuseForks=false` | No -- Wave 0 |
| N/A | Ed25519 signing and verification roundtrip | unit | `mvn test -pl cameleer3-server-app -Dtest=Ed25519SigningServiceTest -Dsurefire.reuseForks=false` | No -- Wave 0 |
| SECU-01 | Protected endpoints reject requests without JWT; public endpoints accessible | integration | `mvn test -pl cameleer-server-app -Dtest=SecurityFilterIT -Dsurefire.reuseForks=false` | No -- Wave 0 |
| SECU-02 | Refresh endpoint issues new access JWT from valid refresh token | integration | `mvn test -pl cameleer-server-app -Dtest=JwtRefreshIT -Dsurefire.reuseForks=false` | No -- Wave 0 |
| SECU-03 | Ed25519 keypair generated at startup; public key in registration response | integration | `mvn test -pl cameleer-server-app -Dtest=RegistrationSecurityIT -Dsurefire.reuseForks=false` | No -- Wave 0 |
| SECU-04 | SSE payloads carry valid Ed25519 signature | integration | `mvn test -pl cameleer-server-app -Dtest=SseSigningIT -Dsurefire.reuseForks=false` | No -- Wave 0 |
| SECU-05 | Bootstrap token required for registration; rejects invalid/missing tokens | integration | `mvn test -pl cameleer-server-app -Dtest=BootstrapTokenIT -Dsurefire.reuseForks=false` | No -- Wave 0 |
| N/A | JWT creation, validation, expiry logic | unit | `mvn test -pl cameleer-server-app -Dtest=JwtServiceTest -Dsurefire.reuseForks=false` | No -- Wave 0 |
| N/A | Ed25519 signing and verification roundtrip | unit | `mvn test -pl cameleer-server-app -Dtest=Ed25519SigningServiceTest -Dsurefire.reuseForks=false` | No -- Wave 0 |
### Sampling Rate
- **Per task commit:** `mvn test -pl cameleer3-server-app -Dsurefire.reuseForks=false`
- **Per task commit:** `mvn test -pl cameleer-server-app -Dsurefire.reuseForks=false`
- **Per wave merge:** `mvn clean verify`
- **Phase gate:** Full suite green before `/gsd:verify-work`

View File

@@ -18,8 +18,8 @@ created: 2026-03-11
| Property | Value |
|----------|-------|
| **Framework** | JUnit 5 + Spring Boot Test + Spring Security Test |
| **Config file** | cameleer3-server-app/src/test/resources/application-test.yml |
| **Quick run command** | `mvn test -pl cameleer3-server-app -Dtest="Security*,Jwt*,Bootstrap*,Ed25519*" -Dsurefire.reuseForks=false` |
| **Config file** | cameleer-server-app/src/test/resources/application-test.yml |
| **Quick run command** | `mvn test -pl cameleer-server-app -Dtest="Security*,Jwt*,Bootstrap*,Ed25519*" -Dsurefire.reuseForks=false` |
| **Full suite command** | `mvn clean verify` |
| **Estimated runtime** | ~60 seconds |
@@ -27,7 +27,7 @@ created: 2026-03-11
## Sampling Rate
- **After every task commit:** Run `mvn test -pl cameleer3-server-app -Dsurefire.reuseForks=false`
- **After every task commit:** Run `mvn test -pl cameleer-server-app -Dsurefire.reuseForks=false`
- **After every plan wave:** Run `mvn clean verify`
- **Before `/gsd:verify-work`:** Full suite must be green
- **Max feedback latency:** 60 seconds
@@ -38,13 +38,13 @@ created: 2026-03-11
| Task ID | Plan | Wave | Requirement | Test Type | Automated Command | File Exists | Status |
|---------|------|------|-------------|-----------|-------------------|-------------|--------|
| 04-01-01 | 01 | 1 | SECU-03 | unit | `mvn test -pl cameleer3-server-app -Dtest=Ed25519SigningServiceTest -Dsurefire.reuseForks=false` | ❌ W0 | ⬜ pending |
| 04-01-02 | 01 | 1 | SECU-01 | unit | `mvn test -pl cameleer3-server-app -Dtest=JwtServiceTest -Dsurefire.reuseForks=false` | ❌ W0 | ⬜ pending |
| 04-01-03 | 01 | 1 | SECU-05 | integration | `mvn test -pl cameleer3-server-app -Dtest=BootstrapTokenIT -Dsurefire.reuseForks=false` | ❌ W0 | ⬜ pending |
| 04-01-04 | 01 | 1 | SECU-01 | integration | `mvn test -pl cameleer3-server-app -Dtest=SecurityFilterIT -Dsurefire.reuseForks=false` | ❌ W0 | ⬜ pending |
| 04-01-05 | 01 | 1 | SECU-02 | integration | `mvn test -pl cameleer3-server-app -Dtest=JwtRefreshIT -Dsurefire.reuseForks=false` | ❌ W0 | ⬜ pending |
| 04-01-06 | 01 | 1 | SECU-04 | integration | `mvn test -pl cameleer3-server-app -Dtest=SseSigningIT -Dsurefire.reuseForks=false` | ❌ W0 | ⬜ pending |
| 04-01-07 | 01 | 1 | N/A | integration | `mvn test -pl cameleer3-server-app -Dtest=RegistrationSecurityIT -Dsurefire.reuseForks=false` | ❌ W0 | ⬜ pending |
| 04-01-01 | 01 | 1 | SECU-03 | unit | `mvn test -pl cameleer-server-app -Dtest=Ed25519SigningServiceTest -Dsurefire.reuseForks=false` | ❌ W0 | ⬜ pending |
| 04-01-02 | 01 | 1 | SECU-01 | unit | `mvn test -pl cameleer-server-app -Dtest=JwtServiceTest -Dsurefire.reuseForks=false` | ❌ W0 | ⬜ pending |
| 04-01-03 | 01 | 1 | SECU-05 | integration | `mvn test -pl cameleer-server-app -Dtest=BootstrapTokenIT -Dsurefire.reuseForks=false` | ❌ W0 | ⬜ pending |
| 04-01-04 | 01 | 1 | SECU-01 | integration | `mvn test -pl cameleer-server-app -Dtest=SecurityFilterIT -Dsurefire.reuseForks=false` | ❌ W0 | ⬜ pending |
| 04-01-05 | 01 | 1 | SECU-02 | integration | `mvn test -pl cameleer-server-app -Dtest=JwtRefreshIT -Dsurefire.reuseForks=false` | ❌ W0 | ⬜ pending |
| 04-01-06 | 01 | 1 | SECU-04 | integration | `mvn test -pl cameleer-server-app -Dtest=SseSigningIT -Dsurefire.reuseForks=false` | ❌ W0 | ⬜ pending |
| 04-01-07 | 01 | 1 | N/A | integration | `mvn test -pl cameleer-server-app -Dtest=RegistrationSecurityIT -Dsurefire.reuseForks=false` | ❌ W0 | ⬜ pending |
*Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky*

View File

@@ -39,19 +39,19 @@ All truths drawn from PLAN frontmatter must_haves across plans 01, 02, and 03.
| Artifact | Provides | Status | Details |
|----------|----------|--------|---------|
| `cameleer3-server-core/.../security/JwtService.java` | JWT interface: createAccessToken, createRefreshToken, validateAndExtractAgentId, validateRefreshToken | VERIFIED | 49 lines, substantive interface with 4 methods |
| `cameleer3-server-core/.../security/Ed25519SigningService.java` | Ed25519 interface: sign(payload), getPublicKeyBase64() | VERIFIED | 29 lines, substantive interface with 2 methods |
| `cameleer3-server-app/.../security/JwtServiceImpl.java` | Nimbus JOSE+JWT HMAC-SHA256 implementation | VERIFIED | 120 lines; uses `MACSigner`/`MACVerifier`, ephemeral 256-bit secret, correct claims |
| `cameleer3-server-app/.../security/Ed25519SigningServiceImpl.java` | JDK 17 Ed25519 KeyPairGenerator implementation | VERIFIED | 54 lines; `KeyPairGenerator.getInstance("Ed25519")`, `Signature.getInstance("Ed25519")`, Base64-encoded output |
| `cameleer3-server-app/.../security/BootstrapTokenValidator.java` | Constant-time bootstrap token validation with dual-token rotation | VERIFIED | 50 lines; `MessageDigest.isEqual()`, checks current and previous token, null/blank guard |
| `cameleer3-server-app/.../security/SecurityProperties.java` | Config binding with env var mapping | VERIFIED | 48 lines; `@ConfigurationProperties(prefix="security")`; all 4 fields with defaults |
| `cameleer3-server-app/.../security/SecurityBeanConfig.java` | Bean wiring with fail-fast validation | VERIFIED | 43 lines; `@EnableConfigurationProperties`, all 3 service beans, `InitializingBean` check |
| `cameleer3-server-app/.../security/JwtAuthenticationFilter.java` | OncePerRequestFilter extracting JWT from header or query param | VERIFIED | 72 lines; extracts from `Authorization: Bearer` then `?token=` query param; sets `SecurityContextHolder` |
| `cameleer3-server-app/.../security/SecurityConfig.java` | SecurityFilterChain with permitAll for public paths, authenticated for rest | VERIFIED | 54 lines; stateless, CSRF disabled, correct permitAll list, `addFilterBefore` JwtAuthenticationFilter |
| `cameleer3-server-app/.../controller/AgentRegistrationController.java` | Updated register endpoint with bootstrap token validation, JWT issuance, public key; refresh endpoint | VERIFIED | 230 lines; both `/register` and `/{id}/refresh` endpoints fully wired |
| `cameleer3-server-app/.../agent/SsePayloadSigner.java` | Component that signs SSE command payloads | VERIFIED | 77 lines; `@Component`, signs then adds field, defensive null/blank handling |
| `cameleer3-server-app/.../agent/SseConnectionManager.java` | Updated onCommandReady with signing before sendEvent | VERIFIED | `onCommandReady()` calls `ssePayloadSigner.signPayload()`, parses to `JsonNode` to avoid double-quoting |
| `cameleer3-server-app/.../resources/application.yml` | Security config with env var mapping | VERIFIED | `security.bootstrap-token: ${CAMELEER_AUTH_TOKEN:}` and `security.bootstrap-token-previous: ${CAMELEER_AUTH_TOKEN_PREVIOUS:}` present |
| `cameleer-server-core/.../security/JwtService.java` | JWT interface: createAccessToken, createRefreshToken, validateAndExtractAgentId, validateRefreshToken | VERIFIED | 49 lines, substantive interface with 4 methods |
| `cameleer-server-core/.../security/Ed25519SigningService.java` | Ed25519 interface: sign(payload), getPublicKeyBase64() | VERIFIED | 29 lines, substantive interface with 2 methods |
| `cameleer-server-app/.../security/JwtServiceImpl.java` | Nimbus JOSE+JWT HMAC-SHA256 implementation | VERIFIED | 120 lines; uses `MACSigner`/`MACVerifier`, ephemeral 256-bit secret, correct claims |
| `cameleer-server-app/.../security/Ed25519SigningServiceImpl.java` | JDK 17 Ed25519 KeyPairGenerator implementation | VERIFIED | 54 lines; `KeyPairGenerator.getInstance("Ed25519")`, `Signature.getInstance("Ed25519")`, Base64-encoded output |
| `cameleer-server-app/.../security/BootstrapTokenValidator.java` | Constant-time bootstrap token validation with dual-token rotation | VERIFIED | 50 lines; `MessageDigest.isEqual()`, checks current and previous token, null/blank guard |
| `cameleer-server-app/.../security/SecurityProperties.java` | Config binding with env var mapping | VERIFIED | 48 lines; `@ConfigurationProperties(prefix="security")`; all 4 fields with defaults |
| `cameleer-server-app/.../security/SecurityBeanConfig.java` | Bean wiring with fail-fast validation | VERIFIED | 43 lines; `@EnableConfigurationProperties`, all 3 service beans, `InitializingBean` check |
| `cameleer-server-app/.../security/JwtAuthenticationFilter.java` | OncePerRequestFilter extracting JWT from header or query param | VERIFIED | 72 lines; extracts from `Authorization: Bearer` then `?token=` query param; sets `SecurityContextHolder` |
| `cameleer-server-app/.../security/SecurityConfig.java` | SecurityFilterChain with permitAll for public paths, authenticated for rest | VERIFIED | 54 lines; stateless, CSRF disabled, correct permitAll list, `addFilterBefore` JwtAuthenticationFilter |
| `cameleer-server-app/.../controller/AgentRegistrationController.java` | Updated register endpoint with bootstrap token validation, JWT issuance, public key; refresh endpoint | VERIFIED | 230 lines; both `/register` and `/{id}/refresh` endpoints fully wired |
| `cameleer-server-app/.../agent/SsePayloadSigner.java` | Component that signs SSE command payloads | VERIFIED | 77 lines; `@Component`, signs then adds field, defensive null/blank handling |
| `cameleer-server-app/.../agent/SseConnectionManager.java` | Updated onCommandReady with signing before sendEvent | VERIFIED | `onCommandReady()` calls `ssePayloadSigner.signPayload()`, parses to `JsonNode` to avoid double-quoting |
| `cameleer-server-app/.../resources/application.yml` | Security config with env var mapping | VERIFIED | `security.bootstrap-token: ${CAMELEER_AUTH_TOKEN:}` and `security.bootstrap-token-previous: ${CAMELEER_AUTH_TOKEN_PREVIOUS:}` present |
### Key Link Verification

View File

@@ -57,7 +57,7 @@ Agents (50+) Users / UI
Agent POST /api/v1/ingest
|
v
[IngestController] -- validates JWT, deserializes using cameleer3-common models
[IngestController] -- validates JWT, deserializes using cameleer-common models
|
v
[IngestionService.accept(batch)] -- accepts TransactionData/ActivityData
@@ -126,7 +126,7 @@ Each registered SseEmitter sends event to connected agent
Agent POST /api/v1/diagrams (on startup or route change)
|
v
[DiagramController] -- receives route definition (XML/YAML/JSON from cameleer3-common)
[DiagramController] -- receives route definition (XML/YAML/JSON from cameleer-common)
|
v
[DiagramService.storeVersion(definition)]
@@ -341,11 +341,11 @@ public record PageCursor(Instant timestamp, String id) {}
## Module Boundary Design
### Core Module (`cameleer3-server-core`)
### Core Module (`cameleer-server-core`)
The core module is the domain layer. It contains:
- **Domain models** -- Transaction, Activity, Agent, DiagramVersion, etc. (may extend or complement cameleer3-common models)
- **Domain models** -- Transaction, Activity, Agent, DiagramVersion, etc. (may extend or complement cameleer-common models)
- **Service interfaces and implementations** -- TransactionService, AgentRegistryService, DiagramService, QueryEngine
- **Repository interfaces** -- TransactionRepository, DiagramRepository, AgentRepository (interfaces only, no implementations)
- **Ingestion logic** -- WriteBuffer, batch assembly, backpressure signaling
@@ -356,7 +356,7 @@ The core module is the domain layer. It contains:
**No Spring Boot dependencies.** Jackson is acceptable (already present). JUnit for tests.
### App Module (`cameleer3-server-app`)
### App Module (`cameleer-server-app`)
The app module is the infrastructure/adapter layer. It contains:
@@ -376,8 +376,8 @@ The app module is the infrastructure/adapter layer. It contains:
```
app --> core (allowed)
core --> app (NEVER)
core --> cameleer3-common (allowed)
app --> cameleer3-common (transitively via core)
core --> cameleer-common (allowed)
app --> cameleer-common (transitively via core)
```
## Ingestion Pipeline Detail
@@ -393,7 +393,7 @@ Use a two-stage approach:
- WriteBuffer has a bounded capacity (configurable, default 50,000 items).
- When buffer is >80% full, respond with HTTP 429 + `Retry-After` header.
- Agents (cameleer3) should implement exponential backoff on 429.
- Agents (cameleer) should implement exponential backoff on 429.
- Monitor buffer fill level as a metric.
### Batch Size Tuning

View File

@@ -1,6 +1,6 @@
# Domain Pitfalls
**Domain:** Transaction monitoring / observability server (Cameleer3 Server)
**Domain:** Transaction monitoring / observability server (Cameleer Server)
**Researched:** 2026-03-11
**Confidence:** MEDIUM (based on established patterns for ClickHouse, SSE, high-volume ingestion; no web verification available)

View File

@@ -1,6 +1,6 @@
# Technology Stack
**Project:** Cameleer3 Server
**Project:** Cameleer Server
**Researched:** 2026-03-11
**Overall confidence:** MEDIUM (no live source verification available; versions based on training data up to May 2025)

View File

@@ -1,4 +1,4 @@
# Research Summary: Cameleer3 Server
# Research Summary: Cameleer Server
**Domain:** Transaction observability server for Apache Camel integrations
**Researched:** 2026-03-11
@@ -6,7 +6,7 @@
## Executive Summary
Cameleer3 Server is a write-heavy, read-occasional observability system that receives millions of transaction records per day from distributed Apache Camel agents, stores them with 30-day retention, and provides structured + full-text search. The architecture closely parallels established observability platforms like Jaeger, Zipkin, and njams Server, with the key differentiator being Camel route diagram visualization tied to individual transactions.
Cameleer Server is a write-heavy, read-occasional observability system that receives millions of transaction records per day from distributed Apache Camel agents, stores them with 30-day retention, and provides structured + full-text search. The architecture closely parallels established observability platforms like Jaeger, Zipkin, and njams Server, with the key differentiator being Camel route diagram visualization tied to individual transactions.
The recommended stack centers on **ClickHouse** as the primary data store. ClickHouse's columnar MergeTree engine provides the exact properties this project needs: massive batch insert throughput, excellent time-range query performance, native TTL-based retention, and 10-20x compression on structured observability data. This is a well-established pattern used by production observability platforms (SigNoz, Uptrace, PostHog all run on ClickHouse).
@@ -93,9 +93,9 @@ Based on research, suggested phase structure:
## Gaps to Address
- **ClickHouse Java client API:** The clickhouse-java library has undergone significant changes. Exact API, connection pooling, and Spring Boot integration patterns need phase-specific research
- **cameleer3-common PROTOCOL.md:** Must read the agent protocol definition before designing ClickHouse schema -- this defines the exact data structures being ingested
- **cameleer-common PROTOCOL.md:** Must read the agent protocol definition before designing ClickHouse schema -- this defines the exact data structures being ingested
- **ClickHouse Docker setup:** Optimal ClickHouse Docker configuration (memory limits, merge settings) for development and production
- **Full-text search decision:** ClickHouse skip indexes may or may not meet the "search by any content" requirement. This needs prototyping with realistic data
- **Diagram rendering library:** Server-side route diagram rendering is a significant unknown; needs prototyping with actual Camel route graph data from cameleer3-common
- **Diagram rendering library:** Server-side route diagram rendering is a significant unknown; needs prototyping with actual Camel route graph data from cameleer-common
- **Frontend framework:** No research on UI technology -- deferred to UI phase
- **Agent protocol stability:** The cameleer3-common protocol is still evolving. Schema evolution strategy needs alignment with agent development
- **Agent protocol stability:** The cameleer-common protocol is still evolving. Schema evolution strategy needs alignment with agent development

View File

@@ -0,0 +1 @@
{"reason":"idle timeout","timestamp":1774616238650}

View File

@@ -0,0 +1 @@
10188

View File

@@ -0,0 +1,105 @@
<h2>ProcessDiagram Component Hierarchy</h2>
<p class="subtitle">How the SVG rendering is structured — from data fetch to pixels</p>
<div class="section">
<div class="mockup">
<div class="mockup-header">Component Tree</div>
<div class="mockup-body" style="padding: 20px; font-family: 'JetBrains Mono', monospace; font-size: 13px; line-height: 1.8; color: #1A1612;">
<div><strong style="color: #1A7F8E;">ProcessDiagram</strong> &mdash; root, fetches layout, manages state</div>
<div style="padding-left: 24px; border-left: 2px solid #E4DFD8;">
<div>&lt;svg&gt; container with viewBox (zoom/pan transforms)</div>
<div style="padding-left: 24px; border-left: 2px solid #E4DFD8;">
<div><strong style="color: #7C3AED;">DiagramSection</strong> label="Main Route"</div>
<div style="padding-left: 24px; border-left: 2px solid #E4DFD8;">
<div><strong style="color: #9C9184;">&lt;g&gt;</strong> edges layer (rendered first, behind nodes)</div>
<div style="padding-left: 24px;">
<div><strong style="color: #C6820E;">DiagramEdge</strong> &times; N &mdash; SVG &lt;path&gt; with arrowhead</div>
</div>
<div><strong style="color: #9C9184;">&lt;g&gt;</strong> nodes layer</div>
<div style="padding-left: 24px;">
<div><strong style="color: #C6820E;">DiagramNode</strong> &times; N &mdash; top-bar card</div>
<div style="padding-left: 24px; border-left: 2px solid #E4DFD8;">
<div><strong style="color: #3D7C47;">ConfigBadge</strong> &times; 0..N &mdash; tap/trace indicators</div>
<div><strong style="color: #3D7C47;">NodeToolbar</strong> &mdash; floating on hover</div>
</div>
<div><strong style="color: #C6820E;">CompoundNode</strong> &times; 0..N &mdash; choice/split container</div>
<div style="padding-left: 24px; border-left: 2px solid #E4DFD8;">
<div><strong style="color: #C6820E;">DiagramNode</strong> &times; N &mdash; children inside compound</div>
</div>
</div>
</div>
<div style="margin-top: 8px;"><strong style="color: #C0392B;">DiagramSection</strong> label="onException" variant="error"</div>
<div style="padding-left: 24px; border-left: 2px solid #C0392B;">
<div><em style="color: #9C9184;">same edge + node structure as above</em></div>
</div>
</div>
<div style="margin-top: 8px;"><strong style="color: #1A7F8E;">ZoomControls</strong> &mdash; HTML overlay (not SVG)</div>
</div>
</div>
</div>
</div>
<div class="section" style="margin-top: 24px;">
<div class="mockup">
<div class="mockup-header">SVG Structure (simplified)</div>
<div class="mockup-body" style="padding: 20px; font-family: 'JetBrains Mono', monospace; font-size: 12px; line-height: 1.7; color: #5C5347; background: #F5F2ED;">
<pre style="margin: 0;">&lt;div class="process-diagram"&gt; <span style="color:#9C9184">/* wrapper div */</span>
&lt;svg viewBox="0 0 {w} {h}"&gt; <span style="color:#9C9184">/* zoom = viewBox transform */</span>
&lt;g class="diagram-content"&gt; <span style="color:#9C9184">/* pan offset */</span>
<span style="color:#7C3AED">&lt;!-- Main Route section --&gt;</span>
&lt;g class="section section--main"&gt;
&lt;g class="edges"&gt;
&lt;path d="M 100 40 C ..." /&gt; <span style="color:#9C9184">/* cubic bezier edge */</span>
&lt;marker&gt;...&lt;/marker&gt; <span style="color:#9C9184">/* arrowhead def */</span>
&lt;/g&gt;
&lt;g class="nodes"&gt;
&lt;g transform="translate(x, y)"&gt; <span style="color:#9C9184">/* positioned by ELK */</span>
&lt;rect .../&gt; <span style="color:#9C9184">/* card background */</span>
&lt;rect .../&gt; <span style="color:#9C9184">/* color top bar */</span>
&lt;text&gt;LOG&lt;/text&gt; <span style="color:#9C9184">/* label */</span>
&lt;g class="badges"&gt;...&lt;/g&gt; <span style="color:#9C9184">/* config indicators */</span>
&lt;/g&gt;
&lt;/g&gt;
&lt;/g&gt;
<span style="color:#C0392B">&lt;!-- Error Handler section --&gt;</span>
&lt;g class="section section--error"
transform="translate(0, {mainH + gap})"&gt;
&lt;text&gt;onException&lt;/text&gt; <span style="color:#9C9184">/* section label */</span>
&lt;line .../&gt; <span style="color:#9C9184">/* divider line */</span>
&lt;g class="edges"&gt;...&lt;/g&gt;
&lt;g class="nodes"&gt;...&lt;/g&gt;
&lt;/g&gt;
&lt;/g&gt;
&lt;/svg&gt;
&lt;div class="zoom-controls"&gt;...&lt;/div&gt; <span style="color:#9C9184">/* HTML overlay */</span>
&lt;/div&gt;</pre>
</div>
</div>
</div>
<div class="section" style="margin-top: 24px;">
<div class="mockup">
<div class="mockup-header">Data Flow</div>
<div class="mockup-body" style="padding: 20px; font-family: 'JetBrains Mono', monospace; font-size: 12px; line-height: 1.8; color: #5C5347;">
<pre style="margin: 0;">
<span style="color:#1A7F8E">GET /diagrams/{hash}/render?direction=LR</span>
DiagramLayout { nodes[], edges[], width, height }
<span style="color:#7C3AED">separateFlows(nodes)</span> → mainNodes[] + errorSections[]
│ │
▼ ▼
<span style="color:#C6820E">renderMainSection()</span> <span style="color:#C0392B">renderErrorSection()</span>
│ │
▼ ▼
SVG groups with SVG groups offset below
ELK x/y coordinates main section by mainHeight + gap
</pre>
</div>
</div>
</div>

View File

@@ -0,0 +1,164 @@
<h2>Node Interactions & Config Badges</h2>
<p class="subtitle">Hover toolbar, selection states, and active config indicators</p>
<div class="section">
<div class="mockup">
<div class="mockup-header">Node States</div>
<div class="mockup-body" style="padding: 24px; background: #F5F2ED;">
<svg width="100%" height="340" viewBox="0 0 520 340">
<!-- 1. Normal state -->
<text x="10" y="16" fill="#9C9184" font-size="11" font-weight="600">NORMAL</text>
<g transform="translate(10, 24)">
<rect x="0" y="0" width="200" height="56" rx="4" fill="white" stroke="#E4DFD8" stroke-width="1"/>
<rect x="0" y="0" width="200" height="6" rx="4" fill="#C6820E"/>
<rect x="4" y="0" width="192" height="6" fill="#C6820E"/>
<text x="16" y="32" fill="#C6820E" font-size="14">&#9881;</text>
<text x="36" y="28" fill="#1A1612" font-size="11" font-weight="600">LOG</text>
<text x="36" y="44" fill="#5C5347" font-size="10">Processing order</text>
</g>
<!-- 2. Hovered state with toolbar -->
<text x="270" y="16" fill="#9C9184" font-size="11" font-weight="600">HOVERED (toolbar appears)</text>
<g transform="translate(270, 24)">
<rect x="0" y="0" width="200" height="56" rx="4" fill="#FFFCF5" stroke="#C6820E" stroke-width="1.5"/>
<rect x="0" y="0" width="200" height="6" rx="4" fill="#C6820E"/>
<rect x="4" y="0" width="192" height="6" fill="#C6820E"/>
<text x="16" y="32" fill="#C6820E" font-size="14">&#9881;</text>
<text x="36" y="28" fill="#1A1612" font-size="11" font-weight="600">LOG</text>
<text x="36" y="44" fill="#5C5347" font-size="10">Processing order</text>
<!-- Floating toolbar -->
<g transform="translate(30, -32)">
<rect x="0" y="0" width="140" height="28" rx="6" fill="#1A1612" opacity="0.92"/>
<!-- Icons as circles -->
<g transform="translate(10, 4)">
<circle cx="10" cy="10" r="9" fill="rgba(255,255,255,0.15)"/>
<text x="10" y="14" fill="white" font-size="11" text-anchor="middle">&#128269;</text>
</g>
<g transform="translate(40, 4)">
<circle cx="10" cy="10" r="9" fill="rgba(255,255,255,0.15)"/>
<text x="10" y="14" fill="white" font-size="11" text-anchor="middle">T</text>
</g>
<g transform="translate(70, 4)">
<circle cx="10" cy="10" r="9" fill="rgba(255,255,255,0.15)"/>
<text x="10" y="14" fill="white" font-size="11" text-anchor="middle">&#9998;</text>
</g>
<g transform="translate(100, 4)">
<circle cx="10" cy="10" r="9" fill="rgba(255,255,255,0.15)"/>
<text x="10" y="14" fill="white" font-size="11" text-anchor="middle">&#8943;</text>
</g>
</g>
</g>
<!-- 3. Selected state -->
<text x="10" y="112" fill="#9C9184" font-size="11" font-weight="600">SELECTED (click)</text>
<g transform="translate(10, 120)">
<rect x="-2" y="-2" width="204" height="60" rx="6" fill="none" stroke="#C6820E" stroke-width="2.5" stroke-dasharray="none"/>
<rect x="0" y="0" width="200" height="56" rx="4" fill="white" stroke="#C6820E" stroke-width="1"/>
<rect x="0" y="0" width="200" height="6" rx="4" fill="#C6820E"/>
<rect x="4" y="0" width="192" height="6" fill="#C6820E"/>
<text x="16" y="32" fill="#C6820E" font-size="14">&#9881;</text>
<text x="36" y="28" fill="#1A1612" font-size="11" font-weight="600">LOG</text>
<text x="36" y="44" fill="#5C5347" font-size="10">Processing order</text>
</g>
<!-- 4. With config badges -->
<text x="270" y="112" fill="#9C9184" font-size="11" font-weight="600">WITH CONFIG BADGES</text>
<g transform="translate(270, 120)">
<rect x="0" y="0" width="200" height="56" rx="4" fill="white" stroke="#E4DFD8" stroke-width="1"/>
<rect x="0" y="0" width="200" height="6" rx="4" fill="#C6820E"/>
<rect x="4" y="0" width="192" height="6" fill="#C6820E"/>
<text x="16" y="32" fill="#C6820E" font-size="14">&#9881;</text>
<text x="36" y="28" fill="#1A1612" font-size="11" font-weight="600">LOG</text>
<text x="36" y="44" fill="#5C5347" font-size="10">Processing order</text>
<!-- Trace badge (top-right corner) -->
<g transform="translate(165, -6)">
<rect x="0" y="0" width="38" height="16" rx="8" fill="#1A7F8E"/>
<text x="19" y="12" fill="white" font-size="8" font-weight="600" text-anchor="middle">TRACE</text>
</g>
<!-- Tap badge -->
<g transform="translate(124, -6)">
<rect x="0" y="0" width="36" height="16" rx="8" fill="#7C3AED"/>
<text x="18" y="12" fill="white" font-size="8" font-weight="600" text-anchor="middle">TAP</text>
</g>
</g>
<!-- 5. Error node style -->
<text x="10" y="210" fill="#9C9184" font-size="11" font-weight="600">ERROR HANDLER NODE</text>
<g transform="translate(10, 218)">
<rect x="0" y="0" width="200" height="56" rx="4" fill="white" stroke="#E4DFD8" stroke-width="1"/>
<rect x="0" y="0" width="200" height="6" rx="4" fill="#C0392B"/>
<rect x="4" y="0" width="192" height="6" fill="#C0392B"/>
<text x="16" y="32" fill="#C0392B" font-size="14">&#9888;</text>
<text x="36" y="28" fill="#1A1612" font-size="11" font-weight="600">ON_EXCEPTION</text>
<text x="36" y="44" fill="#5C5347" font-size="10">java.lang.Exception</text>
</g>
<!-- 6. Compound node (Choice) -->
<text x="270" y="210" fill="#9C9184" font-size="11" font-weight="600">COMPOUND NODE (CHOICE)</text>
<g transform="translate(270, 218)">
<rect x="0" y="0" width="220" height="110" rx="4" fill="white" stroke="#7C3AED" stroke-width="1.5"/>
<rect x="0" y="0" width="220" height="22" rx="4" fill="#7C3AED"/>
<rect x="4" y="4" width="212" height="18" fill="#7C3AED"/>
<text x="110" y="16" fill="white" font-size="10" font-weight="600" text-anchor="middle">&#9670; CHOICE</text>
<!-- Children -->
<g transform="translate(10, 30)">
<rect x="0" y="0" width="200" height="32" rx="3" fill="white" stroke="#E4DFD8" stroke-width="1"/>
<rect x="0" y="0" width="200" height="4" rx="3" fill="#7C3AED"/>
<rect x="3" y="0" width="194" height="4" fill="#7C3AED"/>
<text x="12" y="22" fill="#7C3AED" font-size="10">&#9670;</text>
<text x="28" y="22" fill="#1A1612" font-size="10" font-weight="600">WHEN</text>
<text x="66" y="22" fill="#5C5347" font-size="9">type == 'A'</text>
</g>
<g transform="translate(10, 70)">
<rect x="0" y="0" width="200" height="32" rx="3" fill="white" stroke="#E4DFD8" stroke-width="1"/>
<rect x="0" y="0" width="200" height="4" rx="3" fill="#7C3AED"/>
<rect x="3" y="0" width="194" height="4" fill="#7C3AED"/>
<text x="12" y="22" fill="#7C3AED" font-size="10">&#9670;</text>
<text x="28" y="22" fill="#1A1612" font-size="10" font-weight="600">OTHERWISE</text>
</g>
</g>
</svg>
</div>
</div>
</div>
<div class="section" style="margin-top: 24px;">
<div class="mockup">
<div class="mockup-header">Toolbar Actions</div>
<div class="mockup-body" style="padding: 16px;">
<table style="width: 100%; border-collapse: collapse; font-size: 13px;">
<thead>
<tr style="border-bottom: 2px solid #E4DFD8;">
<th style="text-align: left; padding: 8px; color: #5C5347;">Icon</th>
<th style="text-align: left; padding: 8px; color: #5C5347;">Action</th>
<th style="text-align: left; padding: 8px; color: #5C5347;">Description</th>
</tr>
</thead>
<tbody>
<tr style="border-bottom: 1px solid #EDE9E3;">
<td style="padding: 8px;">&#128269;</td>
<td style="padding: 8px; font-weight: 600;">Inspect</td>
<td style="padding: 8px; color: #5C5347;">Select node &amp; open detail side-panel</td>
</tr>
<tr style="border-bottom: 1px solid #EDE9E3;">
<td style="padding: 8px;">T</td>
<td style="padding: 8px; font-weight: 600;">Toggle Trace</td>
<td style="padding: 8px; color: #5C5347;">Enable/disable capture of input+output for this processor</td>
</tr>
<tr style="border-bottom: 1px solid #EDE9E3;">
<td style="padding: 8px;">&#9998;</td>
<td style="padding: 8px; font-weight: 600;">Configure Tap</td>
<td style="padding: 8px; color: #5C5347;">Open tap expression editor for this processor</td>
</tr>
<tr>
<td style="padding: 8px;">&#8943;</td>
<td style="padding: 8px; font-weight: 600;">More</td>
<td style="padding: 8px; color: #5C5347;">Copy processor ID, jump to code, view in search</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>

View File

@@ -0,0 +1,119 @@
<h2>Node Interaction Model</h2>
<p class="subtitle">What happens when you interact with a processor node on the diagram?</p>
<div class="cards">
<!-- Option A: Click-to-select + context menu -->
<div class="card" data-choice="a" onclick="toggleSelect(this)">
<div class="card-image" style="padding: 24px; background: #F5F2ED;">
<svg width="100%" height="180" viewBox="0 0 420 180">
<!-- Normal node -->
<g transform="translate(10, 10)">
<rect x="0" y="0" width="180" height="56" rx="4" fill="white" stroke="#E4DFD8" stroke-width="1"/>
<rect x="0" y="0" width="180" height="6" rx="4" fill="#C6820E"/>
<rect x="4" y="0" width="172" height="6" fill="#C6820E"/>
<text x="16" y="32" fill="#C6820E" font-size="14">&#9881;</text>
<text x="36" y="30" fill="#1A1612" font-size="11" font-weight="600">LOG</text>
<text x="36" y="44" fill="#5C5347" font-size="10">Processing order</text>
<text x="96" y="72" fill="#9C9184" font-size="10" text-anchor="middle" font-style="italic">normal state</text>
</g>
<!-- Selected node (amber ring) -->
<g transform="translate(10, 100)">
<rect x="-2" y="-2" width="184" height="60" rx="6" fill="none" stroke="#C6820E" stroke-width="2.5"/>
<rect x="0" y="0" width="180" height="56" rx="4" fill="white" stroke="#C6820E" stroke-width="1"/>
<rect x="0" y="0" width="180" height="6" rx="4" fill="#C6820E"/>
<rect x="4" y="0" width="172" height="6" fill="#C6820E"/>
<text x="16" y="32" fill="#C6820E" font-size="14">&#9881;</text>
<text x="36" y="30" fill="#1A1612" font-size="11" font-weight="600">LOG</text>
<text x="36" y="44" fill="#5C5347" font-size="10">Processing order</text>
<text x="96" y="72" fill="#C6820E" font-size="10" text-anchor="middle" font-weight="600">click = select</text>
</g>
<!-- Context menu on right-click -->
<g transform="translate(220, 10)">
<rect x="0" y="0" width="180" height="56" rx="4" fill="white" stroke="#E4DFD8" stroke-width="1"/>
<rect x="0" y="0" width="180" height="6" rx="4" fill="#C6820E"/>
<rect x="4" y="0" width="172" height="6" fill="#C6820E"/>
<text x="16" y="32" fill="#C6820E" font-size="14">&#9881;</text>
<text x="36" y="30" fill="#1A1612" font-size="11" font-weight="600">LOG</text>
<text x="36" y="44" fill="#5C5347" font-size="10">Processing order</text>
<!-- Context menu -->
<g transform="translate(100, 40)">
<rect x="0" y="0" width="140" height="96" rx="6" fill="white" stroke="#E4DFD8" stroke-width="1" filter="url(#shadow)"/>
<text x="12" y="20" fill="#1A1612" font-size="11">&#128269; View Snapshot</text>
<line x1="8" y1="28" x2="132" y2="28" stroke="#EDE9E3" stroke-width="1"/>
<text x="12" y="44" fill="#1A7F8E" font-size="11">&#9881; Enable Tracing</text>
<text x="12" y="64" fill="#1A7F8E" font-size="11">&#128204; Set Tap</text>
<line x1="8" y1="72" x2="132" y2="72" stroke="#EDE9E3" stroke-width="1"/>
<text x="12" y="88" fill="#5C5347" font-size="11">&#128203; Copy Processor ID</text>
</g>
<text x="90" y="152" fill="#9C9184" font-size="10" text-anchor="middle" font-style="italic">right-click = context menu</text>
</g>
<defs>
<filter id="shadow" x="-4" y="-2" width="148" height="104">
<feDropShadow dx="0" dy="2" stdDeviation="4" flood-opacity="0.12"/>
</filter>
</defs>
</svg>
</div>
<div class="card-body">
<h3>A: Click-Select + Right-Click Menu</h3>
<p>Click to select a node (amber highlight ring). Right-click for context menu with tracing/tap/snapshot actions. Clean separation of concerns. Standard desktop UX.</p>
</div>
</div>
<!-- Option B: Hover toolbar -->
<div class="card" data-choice="b" onclick="toggleSelect(this)">
<div class="card-image" style="padding: 24px; background: #F5F2ED;">
<svg width="100%" height="180" viewBox="0 0 420 180">
<!-- Normal node -->
<g transform="translate(10, 10)">
<rect x="0" y="0" width="180" height="56" rx="4" fill="white" stroke="#E4DFD8" stroke-width="1"/>
<rect x="0" y="0" width="180" height="6" rx="4" fill="#C6820E"/>
<rect x="4" y="0" width="172" height="6" fill="#C6820E"/>
<text x="16" y="32" fill="#C6820E" font-size="14">&#9881;</text>
<text x="36" y="30" fill="#1A1612" font-size="11" font-weight="600">LOG</text>
<text x="36" y="44" fill="#5C5347" font-size="10">Processing order</text>
<text x="96" y="72" fill="#9C9184" font-size="10" text-anchor="middle" font-style="italic">normal state</text>
</g>
<!-- Hovered node with floating toolbar -->
<g transform="translate(10, 100)">
<rect x="0" y="0" width="180" height="56" rx="4" fill="#FFFCF5" stroke="#C6820E" stroke-width="1.5"/>
<rect x="0" y="0" width="180" height="6" rx="4" fill="#C6820E"/>
<rect x="4" y="0" width="172" height="6" fill="#C6820E"/>
<text x="16" y="32" fill="#C6820E" font-size="14">&#9881;</text>
<text x="36" y="30" fill="#1A1612" font-size="11" font-weight="600">LOG</text>
<text x="36" y="44" fill="#5C5347" font-size="10">Processing order</text>
<!-- Floating toolbar above -->
<g transform="translate(20, -30)">
<rect x="0" y="0" width="140" height="26" rx="13" fill="#1A1612" opacity="0.9"/>
<text x="18" y="17" fill="white" font-size="12" title="View">&#128269;</text>
<text x="46" y="17" fill="white" font-size="12" title="Trace">&#9881;</text>
<text x="74" y="17" fill="white" font-size="12" title="Tap">&#128204;</text>
<text x="102" y="17" fill="white" font-size="12" title="Copy">&#128203;</text>
<text x="124" y="17" fill="white" font-size="12" title="More">&#8943;</text>
</g>
<text x="96" y="72" fill="#C6820E" font-size="10" text-anchor="middle" font-weight="600">hover = toolbar appears</text>
</g>
<!-- Click = select (same as A) -->
<g transform="translate(220, 50)">
<rect x="-2" y="-2" width="184" height="60" rx="6" fill="none" stroke="#C6820E" stroke-width="2.5"/>
<rect x="0" y="0" width="180" height="56" rx="4" fill="white" stroke="#C6820E" stroke-width="1"/>
<rect x="0" y="0" width="180" height="6" rx="4" fill="#C6820E"/>
<rect x="4" y="0" width="172" height="6" fill="#C6820E"/>
<text x="16" y="32" fill="#C6820E" font-size="14">&#9881;</text>
<text x="36" y="30" fill="#1A1612" font-size="11" font-weight="600">LOG</text>
<text x="36" y="44" fill="#5C5347" font-size="10">Processing order</text>
<text x="96" y="72" fill="#C6820E" font-size="10" text-anchor="middle" font-weight="600">click = select</text>
</g>
</svg>
</div>
<div class="card-body">
<h3>B: Hover Floating Toolbar</h3>
<p>Hover reveals a dark floating icon toolbar above the node. Click still selects. More discoverable than right-click, but can feel cluttered on dense diagrams.</p>
</div>
</div>
</div>

View File

@@ -0,0 +1,208 @@
<h2>Node Visual Style</h2>
<p class="subtitle">Which processor node style fits our design system best? Think MuleSoft / TIBCO BW5 but adapted to our warm parchment theme.</p>
<div class="cards">
<!-- Option A: Icon-first blocks (MuleSoft-inspired) -->
<div class="card" data-choice="a" onclick="toggleSelect(this)">
<div class="card-image" style="padding: 24px; background: #F5F2ED;">
<svg width="100%" height="220" viewBox="0 0 400 220">
<!-- FROM node -->
<g transform="translate(20, 10)">
<rect x="0" y="0" width="160" height="56" rx="8" fill="#1A7F8E" opacity="0.12" stroke="#1A7F8E" stroke-width="1.5"/>
<rect x="0" y="0" width="42" height="56" rx="8" fill="#1A7F8E"/>
<rect x="8" y="0" width="34" height="56" fill="#1A7F8E"/>
<text x="21" y="34" fill="white" font-size="20" text-anchor="middle">&#9654;</text>
<text x="100" y="25" fill="#1A1612" font-size="12" font-weight="600" text-anchor="middle">FROM</text>
<text x="100" y="42" fill="#5C5347" font-size="11" text-anchor="middle">direct:orders</text>
</g>
<!-- Connector -->
<line x1="100" y1="66" x2="100" y2="86" stroke="#9C9184" stroke-width="1.5"/>
<polygon points="95,82 100,90 105,82" fill="#9C9184"/>
<!-- PROCESS node -->
<g transform="translate(20, 90)">
<rect x="0" y="0" width="160" height="56" rx="8" fill="#C6820E" opacity="0.12" stroke="#C6820E" stroke-width="1.5"/>
<rect x="0" y="0" width="42" height="56" rx="8" fill="#C6820E"/>
<rect x="8" y="0" width="34" height="56" fill="#C6820E"/>
<text x="21" y="34" fill="white" font-size="18" text-anchor="middle">&#9881;</text>
<text x="100" y="25" fill="#1A1612" font-size="12" font-weight="600" text-anchor="middle">LOG</text>
<text x="100" y="42" fill="#5C5347" font-size="11" text-anchor="middle">Processing order</text>
</g>
<!-- Connector -->
<line x1="100" y1="146" x2="100" y2="166" stroke="#9C9184" stroke-width="1.5"/>
<polygon points="95,162 100,170 105,162" fill="#9C9184"/>
<!-- TO node -->
<g transform="translate(20, 170)">
<rect x="0" y="0" width="160" height="56" rx="8" fill="#3D7C47" opacity="0.12" stroke="#3D7C47" stroke-width="1.5"/>
<rect x="0" y="0" width="42" height="56" rx="8" fill="#3D7C47"/>
<rect x="8" y="0" width="34" height="56" fill="#3D7C47"/>
<text x="21" y="34" fill="white" font-size="18" text-anchor="middle">&#9724;</text>
<text x="100" y="25" fill="#1A1612" font-size="12" font-weight="600" text-anchor="middle">TO</text>
<text x="100" y="42" fill="#5C5347" font-size="11" text-anchor="middle">kafka:processed</text>
</g>
<!-- CHOICE compound on the right -->
<g transform="translate(210, 10)">
<rect x="0" y="0" width="180" height="210" rx="10" fill="#7C3AED" opacity="0.06" stroke="#7C3AED" stroke-width="1.5" stroke-dasharray="4 2"/>
<text x="10" y="20" fill="#7C3AED" font-size="11" font-weight="600">CHOICE</text>
<!-- When child -->
<g transform="translate(10, 30)">
<rect x="0" y="0" width="160" height="48" rx="6" fill="#7C3AED" opacity="0.12" stroke="#7C3AED" stroke-width="1"/>
<rect x="0" y="0" width="36" height="48" rx="6" fill="#7C3AED"/>
<rect x="6" y="0" width="30" height="48" fill="#7C3AED"/>
<text x="18" y="30" fill="white" font-size="14" text-anchor="middle">&#9670;</text>
<text x="96" y="20" fill="#1A1612" font-size="11" font-weight="600" text-anchor="middle">WHEN</text>
<text x="96" y="36" fill="#5C5347" font-size="10" text-anchor="middle">header.type == 'A'</text>
</g>
<!-- Otherwise child -->
<g transform="translate(10, 88)">
<rect x="0" y="0" width="160" height="48" rx="6" fill="#7C3AED" opacity="0.12" stroke="#7C3AED" stroke-width="1"/>
<rect x="0" y="0" width="36" height="48" rx="6" fill="#7C3AED"/>
<rect x="6" y="0" width="30" height="48" fill="#7C3AED"/>
<text x="18" y="30" fill="white" font-size="14" text-anchor="middle">&#9670;</text>
<text x="96" y="20" fill="#1A1612" font-size="11" font-weight="600" text-anchor="middle">OTHERWISE</text>
<text x="96" y="36" fill="#5C5347" font-size="10" text-anchor="middle">default branch</text>
</g>
</g>
</svg>
</div>
<div class="card-body">
<h3>A: Icon Sidebar Blocks</h3>
<p>MuleSoft-style: colored icon strip on the left, label + detail on the right. Color encodes node type. Compound nodes (choice, split) use dashed containers.</p>
</div>
</div>
<!-- Option B: Rounded pill with centered icon -->
<div class="card" data-choice="b" onclick="toggleSelect(this)">
<div class="card-image" style="padding: 24px; background: #F5F2ED;">
<svg width="100%" height="220" viewBox="0 0 400 220">
<!-- FROM node -->
<g transform="translate(20, 10)">
<rect x="0" y="0" width="160" height="50" rx="25" fill="#1A7F8E" opacity="0.15" stroke="#1A7F8E" stroke-width="1.5"/>
<circle cx="30" cy="25" r="16" fill="#1A7F8E"/>
<text x="30" y="31" fill="white" font-size="14" text-anchor="middle">&#9654;</text>
<text x="98" y="22" fill="#1A1612" font-size="12" font-weight="600" text-anchor="middle">FROM</text>
<text x="98" y="38" fill="#5C5347" font-size="10" text-anchor="middle">direct:orders</text>
</g>
<!-- Connector -->
<line x1="100" y1="60" x2="100" y2="80" stroke="#9C9184" stroke-width="1.5"/>
<polygon points="95,76 100,84 105,76" fill="#9C9184"/>
<!-- PROCESS node -->
<g transform="translate(20, 84)">
<rect x="0" y="0" width="160" height="50" rx="25" fill="#C6820E" opacity="0.15" stroke="#C6820E" stroke-width="1.5"/>
<circle cx="30" cy="25" r="16" fill="#C6820E"/>
<text x="30" y="31" fill="white" font-size="14" text-anchor="middle">&#9881;</text>
<text x="98" y="22" fill="#1A1612" font-size="12" font-weight="600" text-anchor="middle">LOG</text>
<text x="98" y="38" fill="#5C5347" font-size="10" text-anchor="middle">Processing order</text>
</g>
<!-- Connector -->
<line x1="100" y1="134" x2="100" y2="154" stroke="#9C9184" stroke-width="1.5"/>
<polygon points="95,150 100,158 105,150" fill="#9C9184"/>
<!-- TO node -->
<g transform="translate(20, 158)">
<rect x="0" y="0" width="160" height="50" rx="25" fill="#3D7C47" opacity="0.15" stroke="#3D7C47" stroke-width="1.5"/>
<circle cx="30" cy="25" r="16" fill="#3D7C47"/>
<text x="30" y="31" fill="white" font-size="14" text-anchor="middle">&#9724;</text>
<text x="98" y="22" fill="#1A1612" font-size="12" font-weight="600" text-anchor="middle">TO</text>
<text x="98" y="38" fill="#5C5347" font-size="10" text-anchor="middle">kafka:processed</text>
</g>
<!-- CHOICE compound on the right -->
<g transform="translate(210, 10)">
<rect x="0" y="0" width="180" height="200" rx="12" fill="#7C3AED" opacity="0.06" stroke="#7C3AED" stroke-width="1.5" stroke-dasharray="5 3"/>
<text x="90" y="20" fill="#7C3AED" font-size="11" font-weight="600" text-anchor="middle">CHOICE</text>
<!-- When child -->
<g transform="translate(10, 30)">
<rect x="0" y="0" width="160" height="44" rx="22" fill="#7C3AED" opacity="0.15" stroke="#7C3AED" stroke-width="1"/>
<circle cx="26" cy="22" r="14" fill="#7C3AED"/>
<text x="26" y="28" fill="white" font-size="12" text-anchor="middle">&#9670;</text>
<text x="96" y="18" fill="#1A1612" font-size="11" font-weight="600" text-anchor="middle">WHEN</text>
<text x="96" y="34" fill="#5C5347" font-size="10" text-anchor="middle">type == 'A'</text>
</g>
<!-- Otherwise child -->
<g transform="translate(10, 84)">
<rect x="0" y="0" width="160" height="44" rx="22" fill="#7C3AED" opacity="0.15" stroke="#7C3AED" stroke-width="1"/>
<circle cx="26" cy="22" r="14" fill="#7C3AED"/>
<text x="26" y="28" fill="white" font-size="12" text-anchor="middle">&#9670;</text>
<text x="96" y="18" fill="#1A1612" font-size="11" font-weight="600" text-anchor="middle">OTHERWISE</text>
<text x="96" y="34" fill="#5C5347" font-size="10" text-anchor="middle">default</text>
</g>
</g>
</svg>
</div>
<div class="card-body">
<h3>B: Rounded Pills</h3>
<p>Softer, more modern look with pill-shaped nodes and circular icons. Lighter feel. Compounds still use dashed containers.</p>
</div>
</div>
<!-- Option C: TIBCO BW5 style - rectangular with top color bar -->
<div class="card" data-choice="c" onclick="toggleSelect(this)">
<div class="card-image" style="padding: 24px; background: #F5F2ED;">
<svg width="100%" height="220" viewBox="0 0 400 220">
<!-- FROM node -->
<g transform="translate(20, 10)">
<rect x="0" y="0" width="160" height="56" rx="4" fill="white" stroke="#E4DFD8" stroke-width="1"/>
<rect x="0" y="0" width="160" height="6" rx="4" fill="#1A7F8E"/>
<rect x="4" y="0" width="152" height="6" fill="#1A7F8E"/>
<text x="18" y="32" fill="#1A7F8E" font-size="16">&#9654;</text>
<text x="40" y="30" fill="#1A1612" font-size="12" font-weight="600">FROM</text>
<text x="40" y="46" fill="#5C5347" font-size="10">direct:orders</text>
</g>
<!-- Connector -->
<line x1="100" y1="66" x2="100" y2="86" stroke="#9C9184" stroke-width="1.5"/>
<polygon points="95,82 100,90 105,82" fill="#9C9184"/>
<!-- PROCESS node -->
<g transform="translate(20, 90)">
<rect x="0" y="0" width="160" height="56" rx="4" fill="white" stroke="#E4DFD8" stroke-width="1"/>
<rect x="0" y="0" width="160" height="6" rx="4" fill="#C6820E"/>
<rect x="4" y="0" width="152" height="6" fill="#C6820E"/>
<text x="18" y="32" fill="#C6820E" font-size="16">&#9881;</text>
<text x="40" y="30" fill="#1A1612" font-size="12" font-weight="600">LOG</text>
<text x="40" y="46" fill="#5C5347" font-size="10">Processing order</text>
</g>
<!-- Connector -->
<line x1="100" y1="146" x2="100" y2="166" stroke="#9C9184" stroke-width="1.5"/>
<polygon points="95,162 100,170 105,162" fill="#9C9184"/>
<!-- TO node -->
<g transform="translate(20, 170)">
<rect x="0" y="0" width="160" height="56" rx="4" fill="white" stroke="#E4DFD8" stroke-width="1"/>
<rect x="0" y="0" width="160" height="6" rx="4" fill="#3D7C47"/>
<rect x="4" y="0" width="152" height="6" fill="#3D7C47"/>
<text x="18" y="32" fill="#3D7C47" font-size="16">&#9724;</text>
<text x="40" y="30" fill="#1A1612" font-size="12" font-weight="600">TO</text>
<text x="40" y="46" fill="#5C5347" font-size="10">kafka:processed</text>
</g>
<!-- CHOICE compound on the right -->
<g transform="translate(210, 10)">
<rect x="0" y="0" width="180" height="210" rx="4" fill="white" stroke="#7C3AED" stroke-width="1.5"/>
<rect x="0" y="0" width="180" height="22" rx="4" fill="#7C3AED"/>
<rect x="4" y="4" width="172" height="18" fill="#7C3AED"/>
<text x="90" y="16" fill="white" font-size="11" font-weight="600" text-anchor="middle">CHOICE</text>
<!-- When child -->
<g transform="translate(10, 32)">
<rect x="0" y="0" width="160" height="48" rx="4" fill="white" stroke="#E4DFD8" stroke-width="1"/>
<rect x="0" y="0" width="160" height="5" rx="4" fill="#7C3AED"/>
<rect x="4" y="0" width="152" height="5" fill="#7C3AED"/>
<text x="14" y="28" fill="#7C3AED" font-size="14">&#9670;</text>
<text x="34" y="26" fill="#1A1612" font-size="11" font-weight="600">WHEN</text>
<text x="34" y="40" fill="#5C5347" font-size="10">type == 'A'</text>
</g>
<!-- Otherwise child -->
<g transform="translate(10, 90)">
<rect x="0" y="0" width="160" height="48" rx="4" fill="white" stroke="#E4DFD8" stroke-width="1"/>
<rect x="0" y="0" width="160" height="5" rx="4" fill="#7C3AED"/>
<rect x="4" y="0" width="152" height="5" fill="#7C3AED"/>
<text x="14" y="28" fill="#7C3AED" font-size="14">&#9670;</text>
<text x="34" y="26" fill="#1A1612" font-size="11" font-weight="600">OTHERWISE</text>
<text x="34" y="40" fill="#5C5347" font-size="10">default</text>
</g>
</g>
</svg>
</div>
<div class="card-body">
<h3>C: Top-Bar Cards</h3>
<p>TIBCO BW5-inspired: white cards with colored top accent bar. Clean, professional, card-like. Compound nodes get a full colored header bar with white title text.</p>
</div>
</div>
</div>

View File

@@ -0,0 +1,3 @@
<div style="display:flex;align-items:center;justify-content:center;min-height:60vh">
<p class="subtitle">Continuing in terminal...</p>
</div>

View File

@@ -0,0 +1 @@
{"reason":"idle timeout","timestamp":1774632733532}

View File

@@ -0,0 +1 @@
14618

View File

@@ -0,0 +1,287 @@
<h2>Detail Panel: Tab Designs</h2>
<p class="subtitle">Bottom panel content when a processor node is selected</p>
<div class="mockup">
<div class="mockup-header">Info Tab — processor metadata + attributes</div>
<div class="mockup-body" style="background: #fff; padding: 0;">
<!-- Processor header -->
<div style="display: flex; align-items: center; gap: 10px; padding: 6px 14px; border-bottom: 1px solid #E4DFD8; background: #FAFAF8;">
<span style="font-size: 11px; font-weight: 600; color: #1A1612;">bean:validate</span>
<span style="font-size: 10px; color: #C0392B; background: #FDF2F0; padding: 1px 6px; border-radius: 8px;">FAILED</span>
<span style="font-size: 10px; color: #9C9184;">processor-5</span>
</div>
<!-- Tabs -->
<div style="display: flex; gap: 0; border-bottom: 1px solid #E4DFD8; background: #FAFAF8; padding: 0 14px;">
<div style="font-size: 11px; padding: 6px 12px; color: #C6820E; border-bottom: 2px solid #C6820E; font-weight: 600; cursor: pointer;">Info</div>
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Headers</div>
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Input</div>
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Output</div>
<div style="font-size: 11px; padding: 6px 12px; color: #C0392B; cursor: pointer;">Error</div>
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer; opacity: 0.4;">Config</div>
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Timeline</div>
</div>
<!-- Info content -->
<div style="padding: 12px 14px; display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px 24px; font-size: 12px;">
<div>
<div style="font-size: 10px; color: #9C9184; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 2px;">Processor ID</div>
<div style="color: #1A1612; font-family: monospace; font-size: 11px;">processor-5</div>
</div>
<div>
<div style="font-size: 10px; color: #9C9184; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 2px;">Type</div>
<div style="color: #1A1612;">BEAN</div>
</div>
<div>
<div style="font-size: 10px; color: #9C9184; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 2px;">Status</div>
<div style="color: #C0392B; font-weight: 500;">FAILED</div>
</div>
<div>
<div style="font-size: 10px; color: #9C9184; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 2px;">Start Time</div>
<div style="color: #1A1612;">14:32:05.123</div>
</div>
<div>
<div style="font-size: 10px; color: #9C9184; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 2px;">End Time</div>
<div style="color: #1A1612;">14:32:05.243</div>
</div>
<div>
<div style="font-size: 10px; color: #9C9184; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 2px;">Duration</div>
<div style="color: #1A1612; font-weight: 500;">120ms</div>
</div>
<div>
<div style="font-size: 10px; color: #9C9184; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 2px;">Endpoint URI</div>
<div style="color: #1A1612; font-family: monospace; font-size: 11px;">bean:orderValidator?method=validate</div>
</div>
<div style="grid-column: span 2;">
<div style="font-size: 10px; color: #9C9184; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 2px;">Resolved URI</div>
<div style="color: #1A1612; font-family: monospace; font-size: 11px;">bean://com.example.OrderValidator?method=validate</div>
</div>
<!-- Attributes from taps -->
<div style="grid-column: span 3; border-top: 1px solid #E4DFD8; padding-top: 8px; margin-top: 4px;">
<div style="font-size: 10px; color: #9C9184; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 6px;">Attributes</div>
<div style="display: flex; gap: 6px; flex-wrap: wrap;">
<span style="font-size: 10px; padding: 2px 8px; background: #F5F0EA; border-radius: 10px; color: #5C5347;">orderId: <strong>ORD-1234</strong></span>
<span style="font-size: 10px; padding: 2px 8px; background: #F5F0EA; border-radius: 10px; color: #5C5347;">customer: <strong>Acme Corp</strong></span>
<span style="font-size: 10px; padding: 2px 8px; background: #F5F0EA; border-radius: 10px; color: #5C5347;">priority: <strong>HIGH</strong></span>
</div>
</div>
</div>
</div>
</div>
<div style="margin-top: 20px;"></div>
<div class="mockup">
<div class="mockup-header">Headers Tab — input vs output side by side</div>
<div class="mockup-body" style="background: #fff; padding: 0;">
<div style="display: flex; align-items: center; gap: 10px; padding: 6px 14px; border-bottom: 1px solid #E4DFD8; background: #FAFAF8;">
<span style="font-size: 11px; font-weight: 600; color: #1A1612;">log:incoming</span>
<span style="font-size: 10px; color: #3D7C47; background: #F0F9F1; padding: 1px 6px; border-radius: 8px;">COMPLETED</span>
<span style="font-size: 10px; color: #9C9184;">processor-2</span>
</div>
<div style="display: flex; gap: 0; border-bottom: 1px solid #E4DFD8; background: #FAFAF8; padding: 0 14px;">
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Info</div>
<div style="font-size: 11px; padding: 6px 12px; color: #C6820E; border-bottom: 2px solid #C6820E; font-weight: 600; cursor: pointer;">Headers</div>
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Input</div>
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Output</div>
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer; opacity: 0.4;">Error</div>
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer; opacity: 0.4;">Config</div>
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Timeline</div>
</div>
<!-- Headers side by side -->
<div style="display: flex; gap: 0; padding: 0;">
<!-- Input headers -->
<div style="flex: 1; padding: 10px 14px; border-right: 1px solid #E4DFD8;">
<div style="font-size: 10px; color: #9C9184; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 8px;">Input Headers</div>
<table style="width: 100%; font-size: 11px; border-collapse: collapse;">
<tr style="border-bottom: 1px solid #F5F0EA;">
<td style="padding: 3px 0; color: #5C5347; font-weight: 500; width: 40%;">Content-Type</td>
<td style="padding: 3px 0; color: #1A1612; font-family: monospace; font-size: 10px;">application/json</td>
</tr>
<tr style="border-bottom: 1px solid #F5F0EA;">
<td style="padding: 3px 0; color: #5C5347; font-weight: 500;">JMSMessageID</td>
<td style="padding: 3px 0; color: #1A1612; font-family: monospace; font-size: 10px;">ID:broker-42</td>
</tr>
<tr style="border-bottom: 1px solid #F5F0EA;">
<td style="padding: 3px 0; color: #5C5347; font-weight: 500;">breadcrumbId</td>
<td style="padding: 3px 0; color: #1A1612; font-family: monospace; font-size: 10px;">abc-123-def</td>
</tr>
<tr>
<td style="padding: 3px 0; color: #5C5347; font-weight: 500;">CamelHttpMethod</td>
<td style="padding: 3px 0; color: #1A1612; font-family: monospace; font-size: 10px;">POST</td>
</tr>
</table>
</div>
<!-- Output headers -->
<div style="flex: 1; padding: 10px 14px;">
<div style="font-size: 10px; color: #9C9184; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 8px;">Output Headers</div>
<table style="width: 100%; font-size: 11px; border-collapse: collapse;">
<tr style="border-bottom: 1px solid #F5F0EA;">
<td style="padding: 3px 0; color: #5C5347; font-weight: 500; width: 40%;">Content-Type</td>
<td style="padding: 3px 0; color: #1A1612; font-family: monospace; font-size: 10px;">application/json</td>
</tr>
<tr style="border-bottom: 1px solid #F5F0EA;">
<td style="padding: 3px 0; color: #5C5347; font-weight: 500;">JMSMessageID</td>
<td style="padding: 3px 0; color: #1A1612; font-family: monospace; font-size: 10px;">ID:broker-42</td>
</tr>
<tr style="border-bottom: 1px solid #F5F0EA;">
<td style="padding: 3px 0; color: #5C5347; font-weight: 500;">breadcrumbId</td>
<td style="padding: 3px 0; color: #1A1612; font-family: monospace; font-size: 10px;">abc-123-def</td>
</tr>
<tr style="border-bottom: 1px solid #F5F0EA;">
<td style="padding: 3px 0; color: #5C5347; font-weight: 500;">CamelHttpMethod</td>
<td style="padding: 3px 0; color: #1A1612; font-family: monospace; font-size: 10px;">POST</td>
</tr>
<tr>
<td style="padding: 3px 0; color: #5C5347; font-weight: 500; color: #3D7C47;">orderStatus</td>
<td style="padding: 3px 0; color: #3D7C47; font-family: monospace; font-size: 10px;">validated <span style="font-size: 9px; color: #9C9184; font-family: sans-serif;">(new)</span></td>
</tr>
</table>
</div>
</div>
</div>
</div>
<div style="margin-top: 20px;"></div>
<div class="mockup">
<div class="mockup-header">Input Tab — formatted message body</div>
<div class="mockup-body" style="background: #fff; padding: 0;">
<div style="display: flex; align-items: center; gap: 10px; padding: 6px 14px; border-bottom: 1px solid #E4DFD8; background: #FAFAF8;">
<span style="font-size: 11px; font-weight: 600; color: #1A1612;">log:incoming</span>
<span style="font-size: 10px; color: #3D7C47; background: #F0F9F1; padding: 1px 6px; border-radius: 8px;">COMPLETED</span>
<span style="font-size: 10px; color: #9C9184;">processor-2 &middot; 5ms</span>
</div>
<div style="display: flex; gap: 0; border-bottom: 1px solid #E4DFD8; background: #FAFAF8; padding: 0 14px;">
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Info</div>
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Headers</div>
<div style="font-size: 11px; padding: 6px 12px; color: #C6820E; border-bottom: 2px solid #C6820E; font-weight: 600; cursor: pointer;">Input</div>
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Output</div>
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer; opacity: 0.4;">Error</div>
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer; opacity: 0.4;">Config</div>
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Timeline</div>
</div>
<!-- Body content with syntax highlighting -->
<div style="padding: 10px 14px;">
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px;">
<span style="font-size: 10px; color: #9C9184;">JSON &middot; 234 bytes</span>
<button style="font-size: 9px; padding: 2px 8px; border: 1px solid #E4DFD8; background: #FAFAF8; border-radius: 3px; cursor: pointer; color: #5C5347;">Copy</button>
</div>
<pre style="font-size: 11px; background: #1A1612; color: #E4DFD8; padding: 12px; border-radius: 6px; margin: 0; line-height: 1.6; overflow-x: auto;">{
<span style="color: #1A7F8E;">"orderId"</span>: <span style="color: #C6820E;">"ORD-1234"</span>,
<span style="color: #1A7F8E;">"customer"</span>: {
<span style="color: #1A7F8E;">"name"</span>: <span style="color: #C6820E;">"Acme Corp"</span>,
<span style="color: #1A7F8E;">"id"</span>: <span style="color: #7C3AED;">42</span>
},
<span style="color: #1A7F8E;">"items"</span>: [
{
<span style="color: #1A7F8E;">"product"</span>: <span style="color: #C6820E;">"Widget A"</span>,
<span style="color: #1A7F8E;">"quantity"</span>: <span style="color: #7C3AED;">5</span>,
<span style="color: #1A7F8E;">"price"</span>: <span style="color: #7C3AED;">29.99</span>
}
],
<span style="color: #1A7F8E;">"priority"</span>: <span style="color: #C6820E;">"HIGH"</span>
}</pre>
</div>
</div>
</div>
<div style="margin-top: 20px;"></div>
<div class="mockup">
<div class="mockup-header">Timeline Tab — Gantt-style processor durations</div>
<div class="mockup-body" style="background: #fff; padding: 0;">
<div style="display: flex; align-items: center; gap: 10px; padding: 6px 14px; border-bottom: 1px solid #E4DFD8; background: #FAFAF8;">
<span style="font-size: 11px; font-weight: 600; color: #1A1612;">content-based-routing</span>
<span style="font-size: 10px; color: #C0392B; background: #FDF2F0; padding: 1px 6px; border-radius: 8px;">FAILED</span>
<span style="font-size: 10px; color: #9C9184;">247ms total</span>
</div>
<div style="display: flex; gap: 0; border-bottom: 1px solid #E4DFD8; background: #FAFAF8; padding: 0 14px;">
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Info</div>
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Headers</div>
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Input</div>
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Output</div>
<div style="font-size: 11px; padding: 6px 12px; color: #C0392B; cursor: pointer;">Error</div>
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer; opacity: 0.4;">Config</div>
<div style="font-size: 11px; padding: 6px 12px; color: #C6820E; border-bottom: 2px solid #C6820E; font-weight: 600; cursor: pointer;">Timeline</div>
</div>
<!-- Gantt chart -->
<div style="padding: 10px 14px;">
<!-- Time axis -->
<div style="display: flex; justify-content: space-between; font-size: 9px; color: #9C9184; margin-bottom: 4px; padding-left: 110px;">
<span>0ms</span><span>50ms</span><span>100ms</span><span>150ms</span><span>200ms</span><span>247ms</span>
</div>
<!-- Processor rows -->
<div style="display: flex; flex-direction: column; gap: 3px;">
<!-- from:jms -->
<div style="display: flex; align-items: center; gap: 8px;">
<span style="font-size: 10px; color: #5C5347; width: 100px; text-align: right; flex-shrink: 0;">from:jms</span>
<div style="flex: 1; height: 16px; background: #F5F0EA; border-radius: 2px; position: relative;">
<div style="position: absolute; left: 0%; width: 0.8%; height: 100%; background: #3D7C47; border-radius: 2px; min-width: 3px;"></div>
</div>
<span style="font-size: 9px; color: #9C9184; width: 36px; flex-shrink: 0;">2ms</span>
</div>
<!-- log -->
<div style="display: flex; align-items: center; gap: 8px;">
<span style="font-size: 10px; color: #5C5347; width: 100px; text-align: right; flex-shrink: 0;">log</span>
<div style="flex: 1; height: 16px; background: #F5F0EA; border-radius: 2px; position: relative;">
<div style="position: absolute; left: 0.8%; width: 2%; height: 100%; background: #3D7C47; border-radius: 2px; min-width: 3px;"></div>
</div>
<span style="font-size: 9px; color: #9C9184; width: 36px; flex-shrink: 0;">5ms</span>
</div>
<!-- setHeader -->
<div style="display: flex; align-items: center; gap: 8px;">
<span style="font-size: 10px; color: #5C5347; width: 100px; text-align: right; flex-shrink: 0;">setHeader</span>
<div style="flex: 1; height: 16px; background: #F5F0EA; border-radius: 2px; position: relative;">
<div style="position: absolute; left: 2.8%; width: 0.4%; height: 100%; background: #3D7C47; border-radius: 2px; min-width: 3px;"></div>
</div>
<span style="font-size: 9px; color: #9C9184; width: 36px; flex-shrink: 0;">1ms</span>
</div>
<!-- bean:validate (FAILED - long) -->
<div style="display: flex; align-items: center; gap: 8px;">
<span style="font-size: 10px; color: #C0392B; font-weight: 600; width: 100px; text-align: right; flex-shrink: 0;">bean:validate</span>
<div style="flex: 1; height: 16px; background: #F5F0EA; border-radius: 2px; position: relative;">
<div style="position: absolute; left: 3.2%; width: 48.6%; height: 100%; background: #C0392B; border-radius: 2px; opacity: 0.8;"></div>
</div>
<span style="font-size: 9px; color: #C0392B; font-weight: 500; width: 36px; flex-shrink: 0;">120ms</span>
</div>
<!-- to:http (skipped) -->
<div style="display: flex; align-items: center; gap: 8px; opacity: 0.35;">
<span style="font-size: 10px; color: #5C5347; width: 100px; text-align: right; flex-shrink: 0;">to:http</span>
<div style="flex: 1; height: 16px; background: #F5F0EA; border-radius: 2px;"></div>
<span style="font-size: 9px; color: #9C9184; width: 36px; flex-shrink: 0;"></span>
</div>
<!-- to:jms (skipped) -->
<div style="display: flex; align-items: center; gap: 8px; opacity: 0.35;">
<span style="font-size: 10px; color: #5C5347; width: 100px; text-align: right; flex-shrink: 0;">to:jms</span>
<div style="flex: 1; height: 16px; background: #F5F0EA; border-radius: 2px;"></div>
<span style="font-size: 9px; color: #9C9184; width: 36px; flex-shrink: 0;"></span>
</div>
</div>
<div style="margin-top: 8px; font-size: 10px; color: #9C9184;">Click a bar to select that processor in the diagram</div>
</div>
</div>
</div>
<div style="margin-top: 20px;"></div>
<div class="mockup">
<div class="mockup-header">Error Tab — grayed out when no error on selected processor</div>
<div class="mockup-body" style="background: #fff; padding: 0;">
<div style="display: flex; align-items: center; gap: 10px; padding: 6px 14px; border-bottom: 1px solid #E4DFD8; background: #FAFAF8;">
<span style="font-size: 11px; font-weight: 600; color: #1A1612;">log:incoming</span>
<span style="font-size: 10px; color: #3D7C47; background: #F0F9F1; padding: 1px 6px; border-radius: 8px;">COMPLETED</span>
<span style="font-size: 10px; color: #9C9184;">processor-2 &middot; 5ms</span>
</div>
<div style="display: flex; gap: 0; border-bottom: 1px solid #E4DFD8; background: #FAFAF8; padding: 0 14px;">
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Info</div>
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Headers</div>
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Input</div>
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Output</div>
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer; opacity: 0.4; cursor: not-allowed;">Error</div>
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer; opacity: 0.4;">Config</div>
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Timeline</div>
</div>
<div style="padding: 20px 14px; text-align: center; color: #9C9184; font-size: 12px;">
No error on this processor
</div>
</div>
</div>

View File

@@ -0,0 +1,207 @@
<h2>Execution Overlay: Full Design Mockup</h2>
<p class="subtitle">ExecutionDiagram component — diagram with execution overlay + detail panel</p>
<div class="mockup">
<div class="mockup-header">ExecutionDiagram — Failed Exchange View</div>
<div class="mockup-body" style="background: #FAFAF8; padding: 0;">
<!-- Top bar: Exchange summary -->
<div style="display: flex; align-items: center; gap: 12px; padding: 8px 14px; background: #fff; border-bottom: 1px solid #E4DFD8; font-size: 12px; color: #5C5347;">
<span style="font-weight: 600; color: #1A1612;">Exchange</span>
<code style="font-size: 11px; background: #F5F0EA; padding: 2px 6px; border-radius: 3px; color: #1A1612;">abc-123-def-456</code>
<span style="background: #C0392B; color: white; font-size: 10px; padding: 1px 8px; border-radius: 10px; font-weight: 600;">FAILED</span>
<span style="color: #9C9184;">sample-app / content-based-routing</span>
<span style="color: #9C9184;">247ms</span>
<div style="margin-left: auto; display: flex; gap: 6px;">
<button style="font-size: 10px; padding: 3px 10px; border: 1px solid #C0392B; background: #FDF2F0; color: #C0392B; border-radius: 4px; cursor: pointer; font-weight: 500;">Jump to Error</button>
</div>
</div>
<!-- Main content: Diagram top, Detail bottom -->
<div style="display: flex; flex-direction: column; height: 480px;">
<!-- TOP: Process Diagram with Overlay -->
<div style="flex: 1; position: relative; overflow: hidden; background: #fff; border-bottom: 2px solid #E4DFD8;">
<!-- Breadcrumbs (if drilled down) -->
<!-- Diagram content -->
<div style="padding: 24px 30px;">
<!-- Main flow -->
<div style="display: flex; align-items: center; gap: 0;">
<!-- Node: from:jms (COMPLETED) -->
<div style="position: relative;">
<div style="width: 140px; height: 52px; background: #fff; border: 1px solid #E4DFD8; border-radius: 6px; border-left: 4px solid #3D7C47; overflow: hidden;">
<div style="height: 5px; background: #1A7F8E;"></div>
<div style="padding: 4px 8px;">
<div style="font-size: 10px; font-weight: 600; color: #1A1612;">from:jms:orders</div>
<div style="font-size: 9px; color: #9C9184;">ENDPOINT</div>
</div>
</div>
<div style="position: absolute; bottom: 2px; right: 6px; font-size: 8px; color: #3D7C47; font-weight: 500;">2ms</div>
</div>
<!-- Edge -->
<svg width="30" height="10" style="flex-shrink:0;"><line x1="0" y1="5" x2="30" y2="5" stroke="#9CA3AF" stroke-width="1.5"/><polygon points="25,2 30,5 25,8" fill="#9CA3AF"/></svg>
<!-- Node: log (COMPLETED) -->
<div style="position: relative;">
<div style="width: 140px; height: 52px; background: #fff; border: 1px solid #E4DFD8; border-radius: 6px; border-left: 4px solid #3D7C47; overflow: hidden;">
<div style="height: 5px; background: #C6820E;"></div>
<div style="padding: 4px 8px;">
<div style="font-size: 10px; font-weight: 600; color: #1A1612;">log:incoming</div>
<div style="font-size: 9px; color: #9C9184;">LOG</div>
</div>
</div>
<div style="position: absolute; bottom: 2px; right: 6px; font-size: 8px; color: #3D7C47; font-weight: 500;">5ms</div>
</div>
<!-- Edge -->
<svg width="30" height="10" style="flex-shrink:0;"><line x1="0" y1="5" x2="30" y2="5" stroke="#9CA3AF" stroke-width="1.5"/><polygon points="25,2 30,5 25,8" fill="#9CA3AF"/></svg>
<!-- Node: CHOICE compound -->
<div style="position: relative; border: 2px dashed #7C3AED; border-radius: 8px; padding: 0; background: #FAFAFF;">
<!-- Compound header -->
<div style="background: #7C3AED; color: white; font-size: 10px; font-weight: 600; padding: 3px 10px; border-radius: 5px 5px 0 0;">CHOICE</div>
<div style="padding: 10px; display: flex; gap: 16px;">
<!-- WHEN branch (taken, failed) -->
<div style="border: 1px solid #E4DFD8; border-radius: 5px; padding: 6px; background: #fff;">
<div style="font-size: 8px; color: #7C3AED; font-weight: 600; margin-bottom: 4px;">WHEN: header.type == 'A'</div>
<div style="display: flex; align-items: center; gap: 0;">
<!-- Node: bean (FAILED) -->
<div style="position: relative;">
<div style="width: 120px; height: 48px; background: #FDF2F0; border: 2px solid #C0392B; border-radius: 6px; overflow: hidden;">
<div style="height: 5px; background: #C6820E;"></div>
<div style="padding: 3px 6px;">
<div style="font-size: 9px; font-weight: 600; color: #C0392B;">bean:validate</div>
<div style="font-size: 8px; color: #C0392B;">FAILED</div>
</div>
</div>
<div style="position: absolute; bottom: 1px; right: 4px; font-size: 7px; color: #C0392B; font-weight: 500;">120ms</div>
<!-- Error icon -->
<div style="position: absolute; top: -6px; right: -6px; width: 16px; height: 16px; background: #C0392B; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 9px; color: white; font-weight: bold;">!</div>
</div>
<svg width="20" height="10" style="flex-shrink:0;"><line x1="0" y1="5" x2="20" y2="5" stroke="#9CA3AF" stroke-width="1"/></svg>
<!-- Node: to:http (NOT EXECUTED - dimmed) -->
<div style="position: relative; opacity: 0.35;">
<div style="width: 120px; height: 48px; background: #fff; border: 1px solid #E4DFD8; border-radius: 6px; overflow: hidden;">
<div style="height: 5px; background: #3D7C47;"></div>
<div style="padding: 3px 6px;">
<div style="font-size: 9px; font-weight: 600; color: #1A1612;">to:http:api</div>
<div style="font-size: 8px; color: #9C9184;">TO</div>
</div>
</div>
</div>
</div>
</div>
<!-- OTHERWISE branch (not taken - dimmed) -->
<div style="border: 1px solid #E4DFD8; border-radius: 5px; padding: 6px; background: #fff; opacity: 0.35;">
<div style="font-size: 8px; color: #7C3AED; font-weight: 600; margin-bottom: 4px;">OTHERWISE</div>
<div style="width: 120px; height: 48px; background: #fff; border: 1px solid #E4DFD8; border-radius: 6px; overflow: hidden;">
<div style="height: 5px; background: #3D7C47;"></div>
<div style="padding: 3px 6px;">
<div style="font-size: 9px; font-weight: 600; color: #1A1612;">to:direct:alt</div>
<div style="font-size: 8px; color: #9C9184;">DIRECT</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Zoom controls (bottom-right) -->
<div style="position: absolute; bottom: 8px; right: 8px; display: flex; align-items: center; gap: 3px; background: #fff; border: 1px solid #E4DFD8; border-radius: 4px; padding: 3px; box-shadow: 0 1px 4px rgba(0,0,0,0.06);">
<button style="width: 22px; height: 22px; border: none; background: transparent; font-size: 12px; cursor: pointer; color: #1A1612;">+</button>
<span style="font-size: 9px; color: #9C9184; min-width: 30px; text-align: center;">100%</span>
<button style="width: 22px; height: 22px; border: none; background: transparent; font-size: 12px; cursor: pointer; color: #1A1612;">-</button>
<button style="width: 22px; height: 22px; border: none; background: transparent; font-size: 10px; cursor: pointer; color: #1A1612;">Fit</button>
</div>
<!-- Minimap (bottom-left) -->
<div style="position: absolute; bottom: 8px; left: 8px; width: 100px; height: 60px; background: #fff; border: 1px solid #E4DFD8; border-radius: 4px; box-shadow: 0 1px 4px rgba(0,0,0,0.06); overflow: hidden;">
<div style="padding: 4px;">
<div style="display: flex; gap: 2px; align-items: center; transform: scale(0.3); transform-origin: top left;">
<div style="width: 60px; height: 20px; background: #1A7F8E; border-radius: 2px;"></div>
<div style="width: 60px; height: 20px; background: #C6820E; border-radius: 2px;"></div>
<div style="width: 100px; height: 40px; border: 1px solid #7C3AED; border-radius: 2px;"></div>
</div>
</div>
</div>
</div>
<!-- SPLITTER -->
<div style="height: 4px; background: #E4DFD8; cursor: row-resize; flex-shrink: 0;"></div>
<!-- BOTTOM: Detail Panel -->
<div style="flex: 0 0 180px; background: #fff; display: flex; flex-direction: column;">
<!-- Selected processor header -->
<div style="display: flex; align-items: center; gap: 10px; padding: 6px 14px; border-bottom: 1px solid #E4DFD8; background: #FAFAF8;">
<span style="font-size: 11px; font-weight: 600; color: #C0392B;">bean:validate</span>
<span style="font-size: 10px; color: #C0392B; background: #FDF2F0; padding: 1px 6px; border-radius: 8px;">FAILED</span>
<span style="font-size: 10px; color: #9C9184;">processor-5 &middot; 120ms</span>
</div>
<!-- Tabs -->
<div style="display: flex; gap: 0; border-bottom: 1px solid #E4DFD8; background: #FAFAF8; padding: 0 14px;">
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Info</div>
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Headers</div>
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Input</div>
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Output</div>
<div style="font-size: 11px; padding: 6px 12px; color: #C0392B; border-bottom: 2px solid #C0392B; font-weight: 600; cursor: pointer;">Error</div>
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer; opacity: 0.5;">Config</div>
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Timeline</div>
</div>
<!-- Tab content: Error -->
<div style="flex: 1; padding: 10px 14px; overflow-y: auto;">
<div style="margin-bottom: 8px;">
<div style="font-size: 10px; color: #9C9184; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 2px;">Exception</div>
<div style="font-size: 12px; color: #C0392B; font-weight: 500;">javax.validation.ValidationException</div>
</div>
<div style="margin-bottom: 8px;">
<div style="font-size: 10px; color: #9C9184; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 2px;">Message</div>
<div style="font-size: 12px; color: #1A1612;">Order quantity must be positive: received -3</div>
</div>
<div style="margin-bottom: 8px;">
<div style="font-size: 10px; color: #9C9184; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 2px;">Root Cause</div>
<div style="font-size: 12px; color: #C0392B;">java.lang.IllegalArgumentException: quantity must be > 0</div>
</div>
<div>
<div style="font-size: 10px; color: #9C9184; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 2px;">Stack Trace</div>
<pre style="font-size: 10px; color: #5C5347; background: #F5F0EA; padding: 8px; border-radius: 4px; overflow-x: auto; margin: 0; line-height: 1.6;">at com.example.OrderValidator.validate(OrderValidator.java:42)
at com.example.OrderRoute.process(OrderRoute.java:18)
at org.apache.camel.processor.DelegateSyncProcessor.process(...)
at org.apache.camel.processor.Pipeline.process(Pipeline.java:184)
... 8 more</pre>
</div>
</div>
</div>
</div>
</div>
</div>
<div style="margin-top: 24px;">
<h3>Design Decisions Shown</h3>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; font-size: 13px; color: #5C5347;">
<div style="background: #f8f8f6; padding: 10px; border-radius: 6px; border-left: 3px solid #3D7C47;">
<strong style="color: #1A1612;">Executed (OK)</strong><br/>
Green left border, duration badge bottom-right
</div>
<div style="background: #f8f8f6; padding: 10px; border-radius: 6px; border-left: 3px solid #C0392B;">
<strong style="color: #1A1612;">Failed</strong><br/>
Red border, red tint background, red ! badge top-right
</div>
<div style="background: #f8f8f6; padding: 10px; border-radius: 6px; border-left: 3px solid #9C9184;">
<strong style="color: #1A1612;">Not Executed</strong><br/>
Dimmed to 35% opacity — full topology visible
</div>
<div style="background: #f8f8f6; padding: 10px; border-radius: 6px; border-left: 3px solid #C6820E;">
<strong style="color: #1A1612;">Selected</strong><br/>
Amber ring (existing), detail panel updates below
</div>
</div>
</div>

View File

@@ -0,0 +1,143 @@
<h2>Per-Compound Iteration Stepper</h2>
<p class="subtitle">Each loop/split compound gets its own stepper in the header bar</p>
<div class="mockup">
<div class="mockup-header">Loop with iteration stepper — iteration 3 of 5</div>
<div class="mockup-body" style="background: #FAFAF8; padding: 20px;">
<!-- LOOP compound -->
<div style="position: relative; border: 2px dashed #7C3AED; border-radius: 8px; background: #FAFAFF; max-width: 600px;">
<!-- Compound header with stepper -->
<div style="background: #7C3AED; color: white; font-size: 11px; font-weight: 600; padding: 4px 10px; border-radius: 5px 5px 0 0; display: flex; align-items: center; justify-content: space-between;">
<span>LOOP</span>
<!-- Iteration stepper -->
<div style="display: flex; align-items: center; gap: 4px; background: rgba(255,255,255,0.15); border-radius: 3px; padding: 1px 4px;">
<button style="width: 18px; height: 18px; border: none; background: rgba(255,255,255,0.2); color: white; border-radius: 2px; cursor: pointer; font-size: 10px; display: flex; align-items: center; justify-content: center;">&lsaquo;</button>
<span style="font-size: 10px; min-width: 30px; text-align: center; font-variant-numeric: tabular-nums;">3 / 5</span>
<button style="width: 18px; height: 18px; border: none; background: rgba(255,255,255,0.2); color: white; border-radius: 2px; cursor: pointer; font-size: 10px; display: flex; align-items: center; justify-content: center;">&rsaquo;</button>
</div>
</div>
<!-- Loop body: showing iteration 3 data -->
<div style="padding: 12px; display: flex; align-items: center; gap: 0;">
<!-- transform (OK in iteration 3) -->
<div style="position: relative;">
<div style="width: 130px; height: 48px; background: #F0F9F1; border: 1.5px solid #3D7C47; border-radius: 5px; border-left: 3px solid #3D7C47; overflow: hidden;">
<div style="height: 4px; background: #C6820E;"></div>
<div style="padding: 3px 6px;">
<div style="font-size: 9px; font-weight: 600; color: #1A1612;">transform</div>
<div style="font-size: 8px; color: #9C9184;">TRANSFORM</div>
</div>
</div>
<div style="position: absolute; bottom: 1px; right: 4px; font-size: 7px; color: #3D7C47;">3ms</div>
<div style="position: absolute; top: -5px; right: -5px; width: 13px; height: 13px; background: #3D7C47; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 8px; color: white;">&#10003;</div>
</div>
<svg width="24" height="10" style="flex-shrink:0;"><line x1="0" y1="5" x2="20" y2="5" stroke="#3D7C47" stroke-width="1.5"/><polygon points="17,2 22,5 17,8" fill="#3D7C47"/></svg>
<!-- to:http (OK in iteration 3) -->
<div style="position: relative;">
<div style="width: 130px; height: 48px; background: #F0F9F1; border: 1.5px solid #3D7C47; border-radius: 5px; border-left: 3px solid #3D7C47; overflow: hidden;">
<div style="height: 4px; background: #3D7C47;"></div>
<div style="padding: 3px 6px;">
<div style="font-size: 9px; font-weight: 600; color: #1A1612;">to:http:api</div>
<div style="font-size: 8px; color: #9C9184;">TO</div>
</div>
</div>
<div style="position: absolute; bottom: 1px; right: 4px; font-size: 7px; color: #3D7C47;">45ms</div>
<div style="position: absolute; top: -5px; right: -5px; width: 13px; height: 13px; background: #3D7C47; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 8px; color: white;">&#10003;</div>
</div>
<svg width="24" height="10" style="flex-shrink:0;"><line x1="0" y1="5" x2="20" y2="5" stroke="#3D7C47" stroke-width="1.5"/><polygon points="17,2 22,5 17,8" fill="#3D7C47"/></svg>
<!-- log (OK in iteration 3) -->
<div style="position: relative;">
<div style="width: 130px; height: 48px; background: #F0F9F1; border: 1.5px solid #3D7C47; border-radius: 5px; border-left: 3px solid #3D7C47; overflow: hidden;">
<div style="height: 4px; background: #C6820E;"></div>
<div style="padding: 3px 6px;">
<div style="font-size: 9px; font-weight: 600; color: #1A1612;">log:result</div>
<div style="font-size: 8px; color: #9C9184;">LOG</div>
</div>
</div>
<div style="position: absolute; bottom: 1px; right: 4px; font-size: 7px; color: #3D7C47;">1ms</div>
<div style="position: absolute; top: -5px; right: -5px; width: 13px; height: 13px; background: #3D7C47; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 8px; color: white;">&#10003;</div>
</div>
</div>
</div>
</div>
</div>
<h3 style="margin-top: 24px;">Nested Loops</h3>
<p class="subtitle">Each compound level has its own independent stepper</p>
<div class="mockup">
<div class="mockup-header">Outer loop (iteration 2/3) containing inner split (branch 1/4)</div>
<div class="mockup-body" style="background: #FAFAF8; padding: 20px;">
<!-- Outer LOOP -->
<div style="border: 2px dashed #7C3AED; border-radius: 8px; background: #FAFAFF; max-width: 550px;">
<div style="background: #7C3AED; color: white; font-size: 11px; font-weight: 600; padding: 4px 10px; border-radius: 5px 5px 0 0; display: flex; align-items: center; justify-content: space-between;">
<span>LOOP</span>
<div style="display: flex; align-items: center; gap: 4px; background: rgba(255,255,255,0.15); border-radius: 3px; padding: 1px 4px;">
<button style="width: 18px; height: 18px; border: none; background: rgba(255,255,255,0.2); color: white; border-radius: 2px; cursor: pointer; font-size: 10px; display: flex; align-items: center; justify-content: center;">&lsaquo;</button>
<span style="font-size: 10px; min-width: 30px; text-align: center;">2 / 3</span>
<button style="width: 18px; height: 18px; border: none; background: rgba(255,255,255,0.2); color: white; border-radius: 2px; cursor: pointer; font-size: 10px; display: flex; align-items: center; justify-content: center;">&rsaquo;</button>
</div>
</div>
<div style="padding: 12px;">
<div style="display: flex; align-items: center; gap: 0;">
<!-- Processor before split -->
<div style="position: relative;">
<div style="width: 110px; height: 44px; background: #F0F9F1; border: 1.5px solid #3D7C47; border-radius: 5px; border-left: 3px solid #3D7C47; overflow: hidden;">
<div style="height: 4px; background: #C6820E;"></div>
<div style="padding: 3px 6px;">
<div style="font-size: 9px; font-weight: 600; color: #1A1612;">setBody</div>
<div style="font-size: 8px; color: #9C9184;">SET_BODY</div>
</div>
</div>
<div style="position: absolute; top: -5px; right: -5px; width: 13px; height: 13px; background: #3D7C47; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 8px; color: white;">&#10003;</div>
</div>
<svg width="20" height="10" style="flex-shrink:0;"><line x1="0" y1="5" x2="16" y2="5" stroke="#3D7C47" stroke-width="1.5"/></svg>
<!-- Inner SPLIT -->
<div style="border: 2px dashed #7C3AED; border-radius: 6px; background: #F8F7FF;">
<div style="background: #9B6AED; color: white; font-size: 10px; font-weight: 600; padding: 3px 8px; border-radius: 3px 3px 0 0; display: flex; align-items: center; justify-content: space-between;">
<span>SPLIT</span>
<div style="display: flex; align-items: center; gap: 3px; background: rgba(255,255,255,0.15); border-radius: 3px; padding: 1px 3px;">
<button style="width: 16px; height: 16px; border: none; background: rgba(255,255,255,0.2); color: white; border-radius: 2px; cursor: pointer; font-size: 9px; display: flex; align-items: center; justify-content: center;">&lsaquo;</button>
<span style="font-size: 9px; min-width: 26px; text-align: center;">1 / 4</span>
<button style="width: 16px; height: 16px; border: none; background: rgba(255,255,255,0.2); color: white; border-radius: 2px; cursor: pointer; font-size: 9px; display: flex; align-items: center; justify-content: center;">&rsaquo;</button>
</div>
</div>
<div style="padding: 8px; display: flex; align-items: center; gap: 0;">
<div style="position: relative;">
<div style="width: 100px; height: 40px; background: #F0F9F1; border: 1.5px solid #3D7C47; border-radius: 4px; border-left: 3px solid #3D7C47; overflow: hidden;">
<div style="height: 3px; background: #3D7C47;"></div>
<div style="padding: 2px 5px;">
<div style="font-size: 8px; font-weight: 600; color: #1A1612;">to:kafka</div>
<div style="font-size: 7px; color: #9C9184;">TO</div>
</div>
</div>
<div style="position: absolute; top: -4px; right: -4px; width: 12px; height: 12px; background: #3D7C47; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 7px; color: white;">&#10003;</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<h3 style="margin-top: 24px;">Stepper Behavior</h3>
<div style="font-size: 13px; color: #5C5347; line-height: 1.8;">
<ul style="margin: 0; padding-left: 20px;">
<li><strong>Independent per compound</strong> — outer loop at iteration 2, inner split at branch 1</li>
<li><strong>Overlay updates per-compound</strong> — stepping the loop re-renders its children's execution data for that iteration</li>
<li><strong>CHOICE shows which branch was taken</strong> — no stepper, just highlights the taken branch</li>
<li><strong>Keyboard</strong> — when a compound is focused/hovered, left/right arrow keys step through iterations</li>
<li><strong>Detail panel syncs</strong> — selecting a processor inside a loop shows that iteration's data</li>
</ul>
</div>

View File

@@ -0,0 +1,166 @@
<h2>Execution Overlay: Page Layout</h2>
<p class="subtitle">How should the diagram + execution details be arranged?</p>
<div class="cards">
<!-- Option A: Horizontal Split -->
<div class="card" data-choice="a" onclick="toggleSelect(this)">
<div class="card-image">
<div style="padding: 16px; background: #1a1612; border-radius: 6px;">
<!-- IDE-style: diagram top, detail bottom -->
<div style="display: flex; flex-direction: column; gap: 8px; height: 280px;">
<!-- Top: Diagram -->
<div style="flex: 1; background: #2a2520; border-radius: 4px; padding: 12px; position: relative; overflow: hidden;">
<div style="font-size: 10px; color: #9C9184; margin-bottom: 8px;">DIAGRAM</div>
<!-- Mini route flow mockup -->
<div style="display: flex; align-items: center; gap: 6px; margin-left: 20px;">
<div style="width: 60px; height: 28px; background: #1A7F8E; border-radius: 3px; display: flex; align-items: center; justify-content: center; font-size: 8px; color: white;">from:jms</div>
<div style="width: 20px; height: 1px; background: #5C5347;"></div>
<div style="width: 60px; height: 28px; background: #C6820E; border-radius: 3px; display: flex; align-items: center; justify-content: center; font-size: 8px; color: white; border: 2px solid #3D7C47;">log</div>
<div style="width: 20px; height: 1px; background: #5C5347;"></div>
<div style="width: 60px; height: 28px; background: #C0392B; border-radius: 3px; display: flex; align-items: center; justify-content: center; font-size: 8px; color: white; border: 2px solid #C0392B; opacity: 0.9;">bean</div>
<div style="width: 20px; height: 1px; background: #5C5347;"></div>
<div style="width: 60px; height: 28px; background: #3D7C47; border-radius: 3px; display: flex; align-items: center; justify-content: center; font-size: 8px; color: white; opacity: 0.4;">to:http</div>
</div>
<!-- Zoom controls hint -->
<div style="position: absolute; bottom: 6px; right: 6px; font-size: 8px; color: #5C5347; background: #2a2520; padding: 2px 6px; border: 1px solid #3a3530; border-radius: 3px;">100%</div>
<!-- Iteration stepper -->
<div style="position: absolute; top: 6px; right: 6px; font-size: 8px; color: #C6820E; background: #2a2520; padding: 2px 8px; border: 1px solid #3a3530; border-radius: 3px;">Loop 2/5</div>
</div>
<!-- Resizable splitter -->
<div style="height: 3px; background: #3a3530; border-radius: 2px; cursor: row-resize;"></div>
<!-- Bottom: Details -->
<div style="flex: 0 0 100px; background: #2a2520; border-radius: 4px; padding: 8px; overflow: hidden;">
<div style="display: flex; gap: 12px; font-size: 9px; color: #9C9184; border-bottom: 1px solid #3a3530; padding-bottom: 4px; margin-bottom: 6px;">
<span style="color: #C6820E; border-bottom: 2px solid #C6820E; padding-bottom: 3px;">Input</span>
<span>Output</span>
<span>Headers</span>
<span>Error</span>
<span>Timeline</span>
</div>
<div style="font-family: monospace; font-size: 8px; color: #9C9184; line-height: 1.5;">
<div>{"orderId": "ORD-1234",</div>
<div>&nbsp;"product": "Widget A",</div>
<div>&nbsp;"quantity": 5}</div>
</div>
</div>
</div>
</div>
</div>
<div class="card-body">
<h3>A: Top/Bottom Split (IDE Style)</h3>
<p>Diagram on top, tabbed detail panel below. Resizable splitter between them. Maximizes diagram width. Tabs: Input, Output, Headers, Error, Timeline.</p>
<div class="pros-cons">
<div class="pros"><h4>Pros</h4><ul><li>Full diagram width</li><li>Familiar IDE pattern</li><li>Detail panel always visible</li></ul></div>
<div class="cons"><h4>Cons</h4><ul><li>Vertical space shared</li><li>Diagram shrinks on small screens</li></ul></div>
</div>
</div>
</div>
<!-- Option B: Right Panel -->
<div class="card" data-choice="b" onclick="toggleSelect(this)">
<div class="card-image">
<div style="padding: 16px; background: #1a1612; border-radius: 6px;">
<div style="display: flex; gap: 8px; height: 280px;">
<!-- Left: Diagram -->
<div style="flex: 1; background: #2a2520; border-radius: 4px; padding: 12px; position: relative; overflow: hidden;">
<div style="font-size: 10px; color: #9C9184; margin-bottom: 8px;">DIAGRAM</div>
<div style="display: flex; align-items: center; gap: 6px; margin-left: 10px;">
<div style="width: 55px; height: 26px; background: #1A7F8E; border-radius: 3px; display: flex; align-items: center; justify-content: center; font-size: 7px; color: white;">from:jms</div>
<div style="width: 14px; height: 1px; background: #5C5347;"></div>
<div style="width: 55px; height: 26px; background: #C6820E; border-radius: 3px; display: flex; align-items: center; justify-content: center; font-size: 7px; color: white; border: 2px solid #3D7C47;">log</div>
<div style="width: 14px; height: 1px; background: #5C5347;"></div>
<div style="width: 55px; height: 26px; background: #C0392B; border-radius: 3px; display: flex; align-items: center; justify-content: center; font-size: 7px; color: white; border: 2px solid #C0392B;">bean</div>
</div>
<div style="position: absolute; bottom: 6px; right: 6px; font-size: 8px; color: #5C5347; background: #2a2520; padding: 2px 6px; border: 1px solid #3a3530; border-radius: 3px;">100%</div>
</div>
<!-- Resizable splitter -->
<div style="width: 3px; background: #3a3530; border-radius: 2px; cursor: col-resize;"></div>
<!-- Right: Detail Panel -->
<div style="flex: 0 0 200px; background: #2a2520; border-radius: 4px; padding: 8px; overflow: hidden;">
<div style="font-size: 9px; color: #C6820E; font-weight: 600; margin-bottom: 6px;">log (processor-3)</div>
<div style="font-size: 8px; color: #3D7C47; margin-bottom: 8px;">COMPLETED - 12ms</div>
<div style="display: flex; flex-direction: column; gap: 2px; font-size: 8px; color: #9C9184; border-bottom: 1px solid #3a3530; padding-bottom: 4px; margin-bottom: 6px;">
<div style="display: flex; gap: 6px;">
<span style="color: #C6820E; font-weight: 600;">Input</span>
<span>Output</span>
<span>Headers</span>
</div>
</div>
<div style="font-family: monospace; font-size: 7px; color: #9C9184; line-height: 1.4;">
<div>{"orderId": "ORD-1234",</div>
<div>&nbsp;"product": "Widget A",</div>
<div>&nbsp;"quantity": 5,</div>
<div>&nbsp;"price": 29.99}</div>
</div>
</div>
</div>
</div>
</div>
<div class="card-body">
<h3>B: Left/Right Split</h3>
<p>Diagram on left, collapsible detail panel on right. Slide-in when node selected. Diagram keeps full height.</p>
<div class="pros-cons">
<div class="pros"><h4>Pros</h4><ul><li>Full diagram height</li><li>Panel can collapse</li><li>Good for wide screens</li></ul></div>
<div class="cons"><h4>Cons</h4><ul><li>Steals diagram width</li><li>Tight on narrow screens</li></ul></div>
</div>
</div>
</div>
<!-- Option C: Hybrid -->
<div class="card" data-choice="c" onclick="toggleSelect(this)">
<div class="card-image">
<div style="padding: 16px; background: #1a1612; border-radius: 6px;">
<div style="display: flex; flex-direction: column; gap: 8px; height: 280px;">
<!-- Top: Full width diagram -->
<div style="flex: 1; background: #2a2520; border-radius: 4px; padding: 12px; position: relative; overflow: hidden;">
<div style="font-size: 10px; color: #9C9184; margin-bottom: 8px;">DIAGRAM</div>
<div style="display: flex; align-items: center; gap: 6px; margin-left: 20px;">
<div style="width: 60px; height: 28px; background: #1A7F8E; border-radius: 3px; display: flex; align-items: center; justify-content: center; font-size: 8px; color: white;">from:jms</div>
<div style="width: 20px; height: 1px; background: #5C5347;"></div>
<div style="width: 60px; height: 28px; background: #C6820E; border-radius: 3px; display: flex; align-items: center; justify-content: center; font-size: 8px; color: white; border: 2px solid #3D7C47;">log</div>
<div style="width: 20px; height: 1px; background: #5C5347;"></div>
<div style="width: 60px; height: 28px; background: #C0392B; border-radius: 3px; display: flex; align-items: center; justify-content: center; font-size: 8px; color: white; border: 2px solid #C0392B;">bean</div>
<div style="width: 20px; height: 1px; background: #5C5347;"></div>
<div style="width: 60px; height: 28px; background: #3D7C47; border-radius: 3px; display: flex; align-items: center; justify-content: center; font-size: 8px; color: white; opacity: 0.4;">to:http</div>
</div>
<div style="position: absolute; bottom: 6px; right: 6px; font-size: 8px; color: #5C5347; background: #2a2520; padding: 2px 6px; border: 1px solid #3a3530; border-radius: 3px;">100%</div>
</div>
<!-- Bottom: Two-column detail -->
<div style="height: 3px; background: #3a3530; border-radius: 2px;"></div>
<div style="flex: 0 0 100px; display: flex; gap: 8px;">
<!-- Left: Processor list / timeline -->
<div style="flex: 0 0 140px; background: #2a2520; border-radius: 4px; padding: 6px; overflow: hidden;">
<div style="font-size: 8px; color: #9C9184; margin-bottom: 4px; font-weight: 600;">Processors</div>
<div style="font-size: 7px; line-height: 1.8;">
<div style="color: #3D7C47; padding: 1px 4px; background: #2a2a20; border-radius: 2px;">from:jms - 2ms</div>
<div style="color: #C6820E; padding: 1px 4px; background: #3a3020; border-radius: 2px; border-left: 2px solid #C6820E;">log - 12ms</div>
<div style="color: #C0392B; padding: 1px 4px;">bean - FAILED</div>
<div style="color: #5C5347; padding: 1px 4px; opacity: 0.5;">to:http - skipped</div>
</div>
</div>
<!-- Right: Selected processor detail -->
<div style="flex: 1; background: #2a2520; border-radius: 4px; padding: 6px; overflow: hidden;">
<div style="display: flex; gap: 8px; font-size: 8px; color: #9C9184; border-bottom: 1px solid #3a3530; padding-bottom: 3px; margin-bottom: 4px;">
<span style="color: #C6820E;">Input</span>
<span>Output</span>
<span>Headers</span>
</div>
<div style="font-family: monospace; font-size: 7px; color: #9C9184; line-height: 1.4;">
<div>{"orderId": "ORD-1234",</div>
<div>&nbsp;"product": "Widget A"}</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="card-body">
<h3>C: Top/Bottom with Processor List</h3>
<p>Diagram on top, bottom split into processor list (left) + detail tabs (right). Clicking processor in list or diagram syncs selection. Most information density.</p>
<div class="pros-cons">
<div class="pros"><h4>Pros</h4><ul><li>Processor list as navigation</li><li>Full diagram width</li><li>Maximum information density</li></ul></div>
<div class="cons"><h4>Cons</h4><ul><li>More complex layout</li><li>May feel crowded</li></ul></div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,190 @@
<h2>Execution Overlay: Visual Intensity Comparison</h2>
<p class="subtitle">How strong should the overlay tinting be?</p>
<div class="split">
<!-- Current: Subtle -->
<div class="mockup" data-choice="subtle" onclick="toggleSelect(this)">
<div class="mockup-header">Current: Subtle (border only)</div>
<div class="mockup-body" style="background: #FAFAF8; padding: 16px;">
<div style="display: flex; flex-direction: column; gap: 12px;">
<!-- OK node - border only -->
<div style="display: flex; align-items: center; gap: 10px;">
<span style="font-size: 10px; color: #9C9184; width: 70px;">Completed</span>
<div style="position: relative; width: 160px; height: 52px; background: #fff; border: 1px solid #E4DFD8; border-radius: 6px; border-left: 4px solid #3D7C47; overflow: hidden;">
<div style="height: 5px; background: #1A7F8E;"></div>
<div style="padding: 4px 8px;">
<div style="font-size: 10px; font-weight: 600; color: #1A1612;">from:jms:orders</div>
<div style="font-size: 9px; color: #9C9184;">ENDPOINT</div>
</div>
<div style="position: absolute; bottom: 2px; right: 6px; font-size: 8px; color: #3D7C47; font-weight: 500;">2ms</div>
</div>
</div>
<!-- Failed node - border only -->
<div style="display: flex; align-items: center; gap: 10px;">
<span style="font-size: 10px; color: #9C9184; width: 70px;">Failed</span>
<div style="position: relative; width: 160px; height: 52px; background: #fff; border: 2px solid #C0392B; border-radius: 6px; overflow: hidden;">
<div style="height: 5px; background: #C6820E;"></div>
<div style="padding: 4px 8px;">
<div style="font-size: 10px; font-weight: 600; color: #1A1612;">bean:validate</div>
<div style="font-size: 9px; color: #9C9184;">BEAN</div>
</div>
<div style="position: absolute; bottom: 2px; right: 6px; font-size: 8px; color: #C0392B; font-weight: 500;">120ms</div>
<div style="position: absolute; top: -6px; right: -6px; width: 16px; height: 16px; background: #C0392B; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 9px; color: white; font-weight: bold;">!</div>
</div>
</div>
<!-- Skipped node -->
<div style="display: flex; align-items: center; gap: 10px;">
<span style="font-size: 10px; color: #9C9184; width: 70px;">Skipped</span>
<div style="opacity: 0.35; width: 160px; height: 52px; background: #fff; border: 1px solid #E4DFD8; border-radius: 6px; overflow: hidden;">
<div style="height: 5px; background: #3D7C47;"></div>
<div style="padding: 4px 8px;">
<div style="font-size: 10px; font-weight: 600; color: #1A1612;">to:http:api</div>
<div style="font-size: 9px; color: #9C9184;">TO</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Proposed: Tinted backgrounds -->
<div class="mockup" data-choice="tinted" onclick="toggleSelect(this)">
<div class="mockup-header">Proposed: Tinted backgrounds</div>
<div class="mockup-body" style="background: #FAFAF8; padding: 16px;">
<div style="display: flex; flex-direction: column; gap: 12px;">
<!-- OK node - green tint -->
<div style="display: flex; align-items: center; gap: 10px;">
<span style="font-size: 10px; color: #9C9184; width: 70px;">Completed</span>
<div style="position: relative; width: 160px; height: 52px; background: #F0F9F1; border: 1.5px solid #3D7C47; border-radius: 6px; border-left: 4px solid #3D7C47; overflow: hidden;">
<div style="height: 5px; background: #1A7F8E;"></div>
<div style="padding: 4px 8px;">
<div style="font-size: 10px; font-weight: 600; color: #1A1612;">from:jms:orders</div>
<div style="font-size: 9px; color: #9C9184;">ENDPOINT</div>
</div>
<div style="position: absolute; bottom: 2px; right: 6px; font-size: 8px; color: #3D7C47; font-weight: 500;">2ms</div>
</div>
</div>
<!-- Failed node - red tint -->
<div style="display: flex; align-items: center; gap: 10px;">
<span style="font-size: 10px; color: #9C9184; width: 70px;">Failed</span>
<div style="position: relative; width: 160px; height: 52px; background: #FDF2F0; border: 2px solid #C0392B; border-radius: 6px; overflow: hidden;">
<div style="height: 5px; background: #C6820E;"></div>
<div style="padding: 4px 8px;">
<div style="font-size: 10px; font-weight: 600; color: #C0392B;">bean:validate</div>
<div style="font-size: 9px; color: #C0392B;">FAILED</div>
</div>
<div style="position: absolute; bottom: 2px; right: 6px; font-size: 8px; color: #C0392B; font-weight: 500;">120ms</div>
<div style="position: absolute; top: -6px; right: -6px; width: 16px; height: 16px; background: #C0392B; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 9px; color: white; font-weight: bold;">!</div>
</div>
</div>
<!-- Skipped node -->
<div style="display: flex; align-items: center; gap: 10px;">
<span style="font-size: 10px; color: #9C9184; width: 70px;">Skipped</span>
<div style="opacity: 0.35; width: 160px; height: 52px; background: #fff; border: 1px solid #E4DFD8; border-radius: 6px; overflow: hidden;">
<div style="height: 5px; background: #3D7C47;"></div>
<div style="padding: 4px 8px;">
<div style="font-size: 10px; font-weight: 600; color: #1A1612;">to:http:api</div>
<div style="font-size: 9px; color: #9C9184;">TO</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<h3 style="margin-top: 24px;">Full Flow Comparison</h3>
<p class="subtitle">Same route, tinted version — see how it reads at a glance</p>
<div class="mockup">
<div class="mockup-header">Tinted overlay on a full route</div>
<div class="mockup-body" style="background: #FAFAF8; padding: 20px;">
<div style="display: flex; align-items: center; gap: 0;">
<!-- from:jms (OK) -->
<div style="position: relative;">
<div style="width: 140px; height: 52px; background: #F0F9F1; border: 1.5px solid #3D7C47; border-radius: 6px; border-left: 4px solid #3D7C47; overflow: hidden;">
<div style="height: 5px; background: #1A7F8E;"></div>
<div style="padding: 4px 8px;">
<div style="font-size: 10px; font-weight: 600; color: #1A1612;">from:jms:orders</div>
<div style="font-size: 9px; color: #9C9184;">ENDPOINT</div>
</div>
</div>
<div style="position: absolute; bottom: 2px; right: 6px; font-size: 8px; color: #3D7C47; font-weight: 500;">2ms</div>
</div>
<svg width="30" height="10" style="flex-shrink:0;"><line x1="0" y1="5" x2="25" y2="5" stroke="#3D7C47" stroke-width="1.5"/><polygon points="22,2 28,5 22,8" fill="#3D7C47"/></svg>
<!-- log (OK) -->
<div style="position: relative;">
<div style="width: 140px; height: 52px; background: #F0F9F1; border: 1.5px solid #3D7C47; border-radius: 6px; border-left: 4px solid #3D7C47; overflow: hidden;">
<div style="height: 5px; background: #C6820E;"></div>
<div style="padding: 4px 8px;">
<div style="font-size: 10px; font-weight: 600; color: #1A1612;">log:incoming</div>
<div style="font-size: 9px; color: #9C9184;">LOG</div>
</div>
</div>
<div style="position: absolute; bottom: 2px; right: 6px; font-size: 8px; color: #3D7C47; font-weight: 500;">5ms</div>
</div>
<svg width="30" height="10" style="flex-shrink:0;"><line x1="0" y1="5" x2="25" y2="5" stroke="#3D7C47" stroke-width="1.5"/><polygon points="22,2 28,5 22,8" fill="#3D7C47"/></svg>
<!-- setHeader (OK) -->
<div style="position: relative;">
<div style="width: 140px; height: 52px; background: #F0F9F1; border: 1.5px solid #3D7C47; border-radius: 6px; border-left: 4px solid #3D7C47; overflow: hidden;">
<div style="height: 5px; background: #C6820E;"></div>
<div style="padding: 4px 8px;">
<div style="font-size: 10px; font-weight: 600; color: #1A1612;">setHeader:type</div>
<div style="font-size: 9px; color: #9C9184;">SET_HEADER</div>
</div>
</div>
<div style="position: absolute; bottom: 2px; right: 6px; font-size: 8px; color: #3D7C47; font-weight: 500;">1ms</div>
</div>
<svg width="30" height="10" style="flex-shrink:0;"><line x1="0" y1="5" x2="25" y2="5" stroke="#3D7C47" stroke-width="1.5"/><polygon points="22,2 28,5 22,8" fill="#3D7C47"/></svg>
<!-- bean:validate (FAILED) -->
<div style="position: relative;">
<div style="width: 140px; height: 52px; background: #FDF2F0; border: 2px solid #C0392B; border-radius: 6px; overflow: hidden;">
<div style="height: 5px; background: #C6820E;"></div>
<div style="padding: 4px 8px;">
<div style="font-size: 10px; font-weight: 600; color: #C0392B;">bean:validate</div>
<div style="font-size: 9px; color: #C0392B;">FAILED</div>
</div>
</div>
<div style="position: absolute; bottom: 2px; right: 6px; font-size: 8px; color: #C0392B; font-weight: 500;">120ms</div>
<div style="position: absolute; top: -6px; right: -6px; width: 16px; height: 16px; background: #C0392B; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 9px; color: white; font-weight: bold;">!</div>
</div>
<svg width="30" height="10" style="flex-shrink:0;"><line x1="0" y1="5" x2="25" y2="5" stroke="#9CA3AF" stroke-width="1" stroke-dasharray="3,3"/></svg>
<!-- to:http (SKIPPED) -->
<div style="opacity: 0.35;">
<div style="width: 140px; height: 52px; background: #fff; border: 1px solid #E4DFD8; border-radius: 6px; overflow: hidden;">
<div style="height: 5px; background: #3D7C47;"></div>
<div style="padding: 4px 8px;">
<div style="font-size: 10px; font-weight: 600; color: #1A1612;">to:http:api</div>
<div style="font-size: 9px; color: #9C9184;">TO</div>
</div>
</div>
</div>
<svg width="30" height="10" style="flex-shrink:0;"><line x1="0" y1="5" x2="25" y2="5" stroke="#9CA3AF" stroke-width="1" stroke-dasharray="3,3"/></svg>
<!-- to:jms (SKIPPED) -->
<div style="opacity: 0.35;">
<div style="width: 140px; height: 52px; background: #fff; border: 1px solid #E4DFD8; border-radius: 6px; overflow: hidden;">
<div style="height: 5px; background: #3D7C47;"></div>
<div style="padding: 4px 8px;">
<div style="font-size: 10px; font-weight: 600; color: #1A1612;">to:jms:result</div>
<div style="font-size: 9px; color: #9C9184;">TO</div>
</div>
</div>
</div>
</div>
<div style="margin-top: 16px; font-size: 11px; color: #5C5347;">
<strong>Note:</strong> Edges between executed nodes turn green. Edges leading to skipped nodes become dashed gray.
</div>
</div>
</div>

View File

@@ -0,0 +1,159 @@
<h2>Execution Overlay: Success + Error Markers</h2>
<p class="subtitle">Every executed node gets a status badge — green check or red exclamation</p>
<div class="mockup">
<div class="mockup-header">Full route with status markers</div>
<div class="mockup-body" style="background: #FAFAF8; padding: 20px;">
<div style="display: flex; align-items: center; gap: 0;">
<!-- from:jms (OK) -->
<div style="position: relative;">
<div style="width: 140px; height: 52px; background: #F0F9F1; border: 1.5px solid #3D7C47; border-radius: 6px; border-left: 4px solid #3D7C47; overflow: hidden;">
<div style="height: 5px; background: #1A7F8E;"></div>
<div style="padding: 4px 8px;">
<div style="font-size: 10px; font-weight: 600; color: #1A1612;">from:jms:orders</div>
<div style="font-size: 9px; color: #9C9184;">ENDPOINT</div>
</div>
</div>
<div style="position: absolute; bottom: 2px; right: 6px; font-size: 8px; color: #3D7C47; font-weight: 500;">2ms</div>
<!-- Success marker -->
<div style="position: absolute; top: -6px; right: -6px; width: 16px; height: 16px; background: #3D7C47; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 10px; color: white; font-weight: bold;">&#10003;</div>
</div>
<svg width="30" height="10" style="flex-shrink:0;"><line x1="0" y1="5" x2="25" y2="5" stroke="#3D7C47" stroke-width="1.5"/><polygon points="22,2 28,5 22,8" fill="#3D7C47"/></svg>
<!-- log (OK) -->
<div style="position: relative;">
<div style="width: 140px; height: 52px; background: #F0F9F1; border: 1.5px solid #3D7C47; border-radius: 6px; border-left: 4px solid #3D7C47; overflow: hidden;">
<div style="height: 5px; background: #C6820E;"></div>
<div style="padding: 4px 8px;">
<div style="font-size: 10px; font-weight: 600; color: #1A1612;">log:incoming</div>
<div style="font-size: 9px; color: #9C9184;">LOG</div>
</div>
</div>
<div style="position: absolute; bottom: 2px; right: 6px; font-size: 8px; color: #3D7C47; font-weight: 500;">5ms</div>
<div style="position: absolute; top: -6px; right: -6px; width: 16px; height: 16px; background: #3D7C47; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 10px; color: white; font-weight: bold;">&#10003;</div>
</div>
<svg width="30" height="10" style="flex-shrink:0;"><line x1="0" y1="5" x2="25" y2="5" stroke="#3D7C47" stroke-width="1.5"/><polygon points="22,2 28,5 22,8" fill="#3D7C47"/></svg>
<!-- setHeader (OK) -->
<div style="position: relative;">
<div style="width: 140px; height: 52px; background: #F0F9F1; border: 1.5px solid #3D7C47; border-radius: 6px; border-left: 4px solid #3D7C47; overflow: hidden;">
<div style="height: 5px; background: #C6820E;"></div>
<div style="padding: 4px 8px;">
<div style="font-size: 10px; font-weight: 600; color: #1A1612;">setHeader:type</div>
<div style="font-size: 9px; color: #9C9184;">SET_HEADER</div>
</div>
</div>
<div style="position: absolute; bottom: 2px; right: 6px; font-size: 8px; color: #3D7C47; font-weight: 500;">1ms</div>
<div style="position: absolute; top: -6px; right: -6px; width: 16px; height: 16px; background: #3D7C47; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 10px; color: white; font-weight: bold;">&#10003;</div>
</div>
<svg width="30" height="10" style="flex-shrink:0;"><line x1="0" y1="5" x2="25" y2="5" stroke="#3D7C47" stroke-width="1.5"/><polygon points="22,2 28,5 22,8" fill="#3D7C47"/></svg>
<!-- bean:validate (FAILED) -->
<div style="position: relative;">
<div style="width: 140px; height: 52px; background: #FDF2F0; border: 2px solid #C0392B; border-radius: 6px; overflow: hidden;">
<div style="height: 5px; background: #C6820E;"></div>
<div style="padding: 4px 8px;">
<div style="font-size: 10px; font-weight: 600; color: #C0392B;">bean:validate</div>
<div style="font-size: 9px; color: #C0392B;">FAILED</div>
</div>
</div>
<div style="position: absolute; bottom: 2px; right: 6px; font-size: 8px; color: #C0392B; font-weight: 500;">120ms</div>
<!-- Error marker -->
<div style="position: absolute; top: -6px; right: -6px; width: 16px; height: 16px; background: #C0392B; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 9px; color: white; font-weight: bold;">!</div>
</div>
<svg width="30" height="10" style="flex-shrink:0;"><line x1="0" y1="5" x2="25" y2="5" stroke="#9CA3AF" stroke-width="1" stroke-dasharray="3,3"/></svg>
<!-- to:http (SKIPPED) -->
<div style="opacity: 0.35;">
<div style="width: 140px; height: 52px; background: #fff; border: 1px solid #E4DFD8; border-radius: 6px; overflow: hidden;">
<div style="height: 5px; background: #3D7C47;"></div>
<div style="padding: 4px 8px;">
<div style="font-size: 10px; font-weight: 600; color: #1A1612;">to:http:api</div>
<div style="font-size: 9px; color: #9C9184;">TO</div>
</div>
</div>
</div>
<svg width="30" height="10" style="flex-shrink:0;"><line x1="0" y1="5" x2="25" y2="5" stroke="#9CA3AF" stroke-width="1" stroke-dasharray="3,3"/></svg>
<!-- to:jms (SKIPPED) -->
<div style="opacity: 0.35;">
<div style="width: 140px; height: 52px; background: #fff; border: 1px solid #E4DFD8; border-radius: 6px; overflow: hidden;">
<div style="height: 5px; background: #3D7C47;"></div>
<div style="padding: 4px 8px;">
<div style="font-size: 10px; font-weight: 600; color: #1A1612;">to:jms:result</div>
<div style="font-size: 9px; color: #9C9184;">TO</div>
</div>
</div>
</div>
</div>
</div>
</div>
<h3 style="margin-top: 24px;">Node State Legend</h3>
<div style="display: flex; gap: 16px; flex-wrap: wrap; margin-top: 8px;">
<!-- Completed -->
<div style="display: flex; align-items: center; gap: 8px; background: #f8f8f6; padding: 10px 14px; border-radius: 6px;">
<div style="position: relative; width: 80px; height: 36px; background: #F0F9F1; border: 1.5px solid #3D7C47; border-radius: 4px; border-left: 3px solid #3D7C47;">
<div style="position: absolute; top: -5px; right: -5px; width: 14px; height: 14px; background: #3D7C47; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 8px; color: white;">&#10003;</div>
<div style="position: absolute; bottom: 1px; right: 4px; font-size: 7px; color: #3D7C47;">5ms</div>
</div>
<div>
<div style="font-size: 12px; font-weight: 600; color: #3D7C47;">Completed</div>
<div style="font-size: 10px; color: #9C9184;">Green tint + border + check badge + duration</div>
</div>
</div>
<!-- Failed -->
<div style="display: flex; align-items: center; gap: 8px; background: #f8f8f6; padding: 10px 14px; border-radius: 6px;">
<div style="position: relative; width: 80px; height: 36px; background: #FDF2F0; border: 2px solid #C0392B; border-radius: 4px;">
<div style="position: absolute; top: -5px; right: -5px; width: 14px; height: 14px; background: #C0392B; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 8px; color: white; font-weight: bold;">!</div>
<div style="position: absolute; bottom: 1px; right: 4px; font-size: 7px; color: #C0392B;">120ms</div>
</div>
<div>
<div style="font-size: 12px; font-weight: 600; color: #C0392B;">Failed</div>
<div style="font-size: 10px; color: #9C9184;">Red tint + border + ! badge + duration</div>
</div>
</div>
<!-- Sub-route failure -->
<div style="display: flex; align-items: center; gap: 8px; background: #f8f8f6; padding: 10px 14px; border-radius: 6px;">
<div style="position: relative; width: 80px; height: 36px; background: #FDF2F0; border: 2px solid #C0392B; border-radius: 4px;">
<div style="position: absolute; top: -5px; right: -5px; width: 14px; height: 14px; background: #C0392B; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 8px; color: white; font-weight: bold;">!</div>
<div style="position: absolute; bottom: 1px; left: 4px; font-size: 8px; color: #C0392B;">&#8628;</div>
<div style="position: absolute; bottom: 1px; right: 4px; font-size: 7px; color: #C0392B;">85ms</div>
</div>
<div>
<div style="font-size: 12px; font-weight: 600; color: #C0392B;">Sub-route Failure</div>
<div style="font-size: 10px; color: #9C9184;">Same as failed + drill-down arrow</div>
</div>
</div>
<!-- Skipped -->
<div style="display: flex; align-items: center; gap: 8px; background: #f8f8f6; padding: 10px 14px; border-radius: 6px;">
<div style="opacity: 0.35; width: 80px; height: 36px; background: #fff; border: 1px solid #E4DFD8; border-radius: 4px;">
</div>
<div>
<div style="font-size: 12px; font-weight: 600; color: #9C9184;">Skipped</div>
<div style="font-size: 10px; color: #9C9184;">35% opacity, no badge, no duration</div>
</div>
</div>
</div>
<h3 style="margin-top: 24px;">Edge States</h3>
<div style="display: flex; gap: 16px; flex-wrap: wrap; margin-top: 8px;">
<div style="display: flex; align-items: center; gap: 8px; background: #f8f8f6; padding: 10px 14px; border-radius: 6px;">
<svg width="60" height="10"><line x1="0" y1="5" x2="50" y2="5" stroke="#3D7C47" stroke-width="1.5"/><polygon points="47,2 53,5 47,8" fill="#3D7C47"/></svg>
<div style="font-size: 11px; color: #5C5347;"><strong>Traversed</strong> — green, solid</div>
</div>
<div style="display: flex; align-items: center; gap: 8px; background: #f8f8f6; padding: 10px 14px; border-radius: 6px;">
<svg width="60" height="10"><line x1="0" y1="5" x2="50" y2="5" stroke="#9CA3AF" stroke-width="1" stroke-dasharray="3,3"/><polygon points="47,2 53,5 47,8" fill="#9CA3AF"/></svg>
<div style="font-size: 11px; color: #5C5347;"><strong>Not traversed</strong> — gray, dashed</div>
</div>
</div>

View File

@@ -0,0 +1,3 @@
<div style="display:flex;align-items:center;justify-content:center;min-height:60vh">
<p class="subtitle">Continuing in terminal...</p>
</div>

View File

@@ -0,0 +1,181 @@
<h2>AppConfigDetailPage — New Sections</h2>
<p class="subtitle">Taps overview, route recording map, and compress success toggle added to existing config page</p>
<div class="mockup">
<div class="mockup-header">AppConfigDetailPage — Full Layout (scrollable)</div>
<div class="mockup-body" style="background:#1a1a2e;color:#e0e0e0;padding:20px;font-family:system-ui,-apple-system,sans-serif;font-size:13px;">
<!-- Back + Header -->
<div style="display:flex;align-items:center;gap:8px;margin-bottom:16px;">
<span style="color:#9ca3af;cursor:pointer;font-size:16px;">&#8592;</span>
<span style="font-size:16px;font-weight:600;">order-service</span>
<span style="font-family:monospace;font-size:11px;color:#6b7280;margin-left:8px;">v14 · Updated 3 min ago</span>
<div style="margin-left:auto;display:flex;gap:8px;">
<span style="color:#9ca3af;cursor:pointer;font-size:14px;" title="Edit">&#9998;</span>
</div>
</div>
<!-- ═══ EXISTING: Logging Section ═══ -->
<div style="background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;padding:16px;margin-bottom:12px;">
<div style="font-size:12px;font-weight:600;margin-bottom:10px;color:#9ca3af;text-transform:uppercase;letter-spacing:0.5px;">Logging</div>
<div style="display:flex;gap:24px;">
<div>
<div style="font-size:11px;color:#6b7280;margin-bottom:3px;">Log Forwarding Level</div>
<span style="background:#1e3a5f;color:#7dd3fc;padding:2px 10px;border-radius:4px;font-size:11px;">INFO</span>
</div>
</div>
</div>
<!-- ═══ EXISTING: Observability Section ═══ -->
<div style="background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;padding:16px;margin-bottom:12px;">
<div style="font-size:12px;font-weight:600;margin-bottom:10px;color:#9ca3af;text-transform:uppercase;letter-spacing:0.5px;">Observability</div>
<div style="display:flex;gap:24px;flex-wrap:wrap;">
<div>
<div style="font-size:11px;color:#6b7280;margin-bottom:3px;">Engine Level</div>
<span style="background:#1e3a5f;color:#7dd3fc;padding:2px 10px;border-radius:4px;font-size:11px;">REGULAR</span>
</div>
<div>
<div style="font-size:11px;color:#6b7280;margin-bottom:3px;">Payload Capture</div>
<span style="background:#2d1f3b;color:#d8b4fe;padding:2px 10px;border-radius:4px;font-size:11px;">BOTH</span>
</div>
<div>
<div style="font-size:11px;color:#6b7280;margin-bottom:3px;">Metrics</div>
<span style="background:#1a3a2a;color:#86efac;padding:2px 10px;border-radius:4px;font-size:11px;">ON</span>
</div>
<div>
<div style="font-size:11px;color:#6b7280;margin-bottom:3px;">Sampling Rate</div>
<span style="font-family:monospace;font-size:12px;color:#e0e0e0;">1.0</span>
</div>
<div>
<div style="font-size:11px;color:#6b7280;margin-bottom:3px;">Compress Success</div>
<span style="background:#3b2f1f;color:#fcd34d;padding:2px 10px;border-radius:4px;font-size:11px;">OFF</span>
</div>
</div>
</div>
<!-- ═══ EXISTING: Traced Processors ═══ -->
<div style="background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;padding:16px;margin-bottom:12px;">
<div style="font-size:12px;font-weight:600;margin-bottom:10px;color:#9ca3af;text-transform:uppercase;letter-spacing:0.5px;">Traced Processors</div>
<div style="font-size:11px;color:#6b7280;margin-bottom:8px;">2 processors with custom capture modes</div>
<table style="width:100%;border-collapse:collapse;font-size:12px;">
<thead>
<tr style="border-bottom:1px solid #2d2d50;">
<th style="text-align:left;padding:6px 8px;color:#9ca3af;font-size:11px;font-weight:500;">Processor ID</th>
<th style="text-align:left;padding:6px 8px;color:#9ca3af;font-size:11px;font-weight:500;">Capture Mode</th>
</tr>
</thead>
<tbody>
<tr style="border-bottom:1px solid #161630;">
<td style="padding:6px 8px;font-family:monospace;font-size:11px;">unmarshal1</td>
<td style="padding:6px 8px;"><span style="background:#2d1f3b;color:#d8b4fe;padding:1px 8px;border-radius:4px;font-size:10px;">BOTH</span></td>
</tr>
<tr>
<td style="padding:6px 8px;font-family:monospace;font-size:11px;">toDatabase</td>
<td style="padding:6px 8px;"><span style="background:#1e3a5f;color:#7dd3fc;padding:1px 8px;border-radius:4px;font-size:10px;">INPUT</span></td>
</tr>
</tbody>
</table>
</div>
<!-- ═══ NEW: Data Extraction Taps ═══ -->
<div style="background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;padding:16px;margin-bottom:12px;">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;">
<div style="font-size:12px;font-weight:600;color:#9ca3af;text-transform:uppercase;letter-spacing:0.5px;">Data Extraction Taps</div>
<span style="font-size:11px;color:#6b7280;">3 taps · manage on route pages</span>
</div>
<table style="width:100%;border-collapse:collapse;font-size:12px;">
<thead>
<tr style="border-bottom:1px solid #2d2d50;">
<th style="text-align:left;padding:6px 8px;color:#9ca3af;font-size:11px;font-weight:500;">Attribute</th>
<th style="text-align:left;padding:6px 8px;color:#9ca3af;font-size:11px;font-weight:500;">Processor</th>
<th style="text-align:left;padding:6px 8px;color:#9ca3af;font-size:11px;font-weight:500;">Expression</th>
<th style="text-align:left;padding:6px 8px;color:#9ca3af;font-size:11px;font-weight:500;">Language</th>
<th style="text-align:center;padding:6px 8px;color:#9ca3af;font-size:11px;font-weight:500;">Enabled</th>
</tr>
</thead>
<tbody>
<tr style="border-bottom:1px solid #161630;">
<td style="padding:6px 8px;font-weight:500;">orderId</td>
<td style="padding:6px 8px;font-family:monospace;font-size:11px;color:#60a5fa;">unmarshal1</td>
<td style="padding:6px 8px;font-family:monospace;font-size:11px;"><span style="background:#161630;padding:1px 6px;border-radius:4px;">${body.orderId}</span></td>
<td style="padding:6px 8px;"><span style="background:#1e3a5f;color:#7dd3fc;padding:1px 6px;border-radius:4px;font-size:10px;">simple</span></td>
<td style="padding:6px 8px;text-align:center;"><span style="color:#4ade80;">&#10003;</span></td>
</tr>
<tr style="border-bottom:1px solid #161630;">
<td style="padding:6px 8px;font-weight:500;">customerId</td>
<td style="padding:6px 8px;font-family:monospace;font-size:11px;color:#60a5fa;">unmarshal1</td>
<td style="padding:6px 8px;font-family:monospace;font-size:11px;"><span style="background:#161630;padding:1px 6px;border-radius:4px;">${body.customer.id}</span></td>
<td style="padding:6px 8px;"><span style="background:#1e3a5f;color:#7dd3fc;padding:1px 6px;border-radius:4px;font-size:10px;">simple</span></td>
<td style="padding:6px 8px;text-align:center;"><span style="color:#4ade80;">&#10003;</span></td>
</tr>
<tr>
<td style="padding:6px 8px;font-weight:500;">orderTotal</td>
<td style="padding:6px 8px;font-family:monospace;font-size:11px;color:#60a5fa;">enrichPrice</td>
<td style="padding:6px 8px;font-family:monospace;font-size:11px;"><span style="background:#161630;padding:1px 6px;border-radius:4px;">$.total</span></td>
<td style="padding:6px 8px;"><span style="background:#3b2f1f;color:#fcd34d;padding:1px 6px;border-radius:4px;font-size:10px;">jsonpath</span></td>
<td style="padding:6px 8px;text-align:center;"><span style="color:#6b7280;">&#10007;</span></td>
</tr>
</tbody>
</table>
</div>
<!-- ═══ NEW: Route Recording ═══ -->
<div style="background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;padding:16px;margin-bottom:12px;">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;">
<div style="font-size:12px;font-weight:600;color:#9ca3af;text-transform:uppercase;letter-spacing:0.5px;">Route Recording</div>
<span style="font-size:11px;color:#6b7280;">4 of 5 routes recording</span>
</div>
<table style="width:100%;border-collapse:collapse;font-size:12px;">
<thead>
<tr style="border-bottom:1px solid #2d2d50;">
<th style="text-align:left;padding:6px 8px;color:#9ca3af;font-size:11px;font-weight:500;">Route</th>
<th style="text-align:center;padding:6px 8px;color:#9ca3af;font-size:11px;font-weight:500;">Recording</th>
</tr>
</thead>
<tbody>
<tr style="border-bottom:1px solid #161630;">
<td style="padding:6px 8px;font-family:monospace;font-size:11px;">processOrder</td>
<td style="padding:6px 8px;text-align:center;">
<div style="width:32px;height:18px;background:#3b82f6;border-radius:9px;position:relative;margin:0 auto;cursor:pointer;">
<div style="width:14px;height:14px;background:white;border-radius:50%;position:absolute;top:2px;right:2px;"></div>
</div>
</td>
</tr>
<tr style="border-bottom:1px solid #161630;">
<td style="padding:6px 8px;font-family:monospace;font-size:11px;">processPayment</td>
<td style="padding:6px 8px;text-align:center;">
<div style="width:32px;height:18px;background:#3b82f6;border-radius:9px;position:relative;margin:0 auto;cursor:pointer;">
<div style="width:14px;height:14px;background:white;border-radius:50%;position:absolute;top:2px;right:2px;"></div>
</div>
</td>
</tr>
<tr style="border-bottom:1px solid #161630;">
<td style="padding:6px 8px;font-family:monospace;font-size:11px;">sendNotification</td>
<td style="padding:6px 8px;text-align:center;">
<div style="width:32px;height:18px;background:#3b82f6;border-radius:9px;position:relative;margin:0 auto;cursor:pointer;">
<div style="width:14px;height:14px;background:white;border-radius:50%;position:absolute;top:2px;right:2px;"></div>
</div>
</td>
</tr>
<tr style="border-bottom:1px solid #161630;">
<td style="padding:6px 8px;font-family:monospace;font-size:11px;">handleRefund</td>
<td style="padding:6px 8px;text-align:center;">
<div style="width:32px;height:18px;background:#4b5563;border-radius:9px;position:relative;margin:0 auto;cursor:pointer;">
<div style="width:14px;height:14px;background:white;border-radius:50%;position:absolute;top:2px;left:2px;"></div>
</div>
</td>
</tr>
<tr>
<td style="padding:6px 8px;font-family:monospace;font-size:11px;">healthCheck</td>
<td style="padding:6px 8px;text-align:center;">
<div style="width:32px;height:18px;background:#3b82f6;border-radius:9px;position:relative;margin:0 auto;cursor:pointer;">
<div style="width:14px;height:14px;background:white;border-radius:50%;position:absolute;top:2px;right:2px;"></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>

View File

@@ -0,0 +1,149 @@
<h2>AppConfigDetailPage — Final Layout</h2>
<p class="subtitle">Three clean sections: Settings, Traces & Taps, Route Recording</p>
<div class="mockup">
<div class="mockup-header">AppConfigDetailPage — Complete</div>
<div class="mockup-body" style="background:#1a1a2e;color:#e0e0e0;padding:20px;font-family:system-ui,-apple-system,sans-serif;font-size:13px;">
<!-- Back + Header -->
<div style="display:flex;align-items:center;gap:8px;margin-bottom:16px;">
<span style="color:#9ca3af;cursor:pointer;font-size:16px;">&#8592;</span>
<span style="font-size:16px;font-weight:600;">order-service</span>
<span style="font-family:monospace;font-size:11px;color:#6b7280;margin-left:8px;">v14 · Updated 3 min ago</span>
<div style="margin-left:auto;display:flex;gap:8px;">
<span style="color:#9ca3af;cursor:pointer;font-size:14px;" title="Edit">&#9998;</span>
</div>
</div>
<!-- ═══ Section 1: Settings ═══ -->
<div style="background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;padding:16px;margin-bottom:12px;">
<div style="font-size:12px;font-weight:600;margin-bottom:12px;color:#9ca3af;text-transform:uppercase;letter-spacing:0.5px;">Settings</div>
<div style="display:flex;gap:28px;flex-wrap:wrap;">
<div>
<div style="font-size:11px;color:#6b7280;margin-bottom:3px;">Log Forwarding</div>
<span style="background:#1e3a5f;color:#7dd3fc;padding:2px 10px;border-radius:4px;font-size:11px;">INFO</span>
</div>
<div>
<div style="font-size:11px;color:#6b7280;margin-bottom:3px;">Engine Level</div>
<span style="background:#1e3a5f;color:#7dd3fc;padding:2px 10px;border-radius:4px;font-size:11px;">REGULAR</span>
</div>
<div>
<div style="font-size:11px;color:#6b7280;margin-bottom:3px;">Payload Capture</div>
<span style="background:#2d1f3b;color:#d8b4fe;padding:2px 10px;border-radius:4px;font-size:11px;">BOTH</span>
</div>
<div>
<div style="font-size:11px;color:#6b7280;margin-bottom:3px;">Metrics</div>
<span style="background:#1a3a2a;color:#86efac;padding:2px 10px;border-radius:4px;font-size:11px;">ON</span>
</div>
<div>
<div style="font-size:11px;color:#6b7280;margin-bottom:3px;">Sampling Rate</div>
<span style="font-family:monospace;font-size:12px;color:#e0e0e0;">1.0</span>
</div>
<div>
<div style="font-size:11px;color:#6b7280;margin-bottom:3px;">Compress Success</div>
<span style="background:#3b2f1f;color:#fcd34d;padding:2px 10px;border-radius:4px;font-size:11px;">OFF</span>
</div>
</div>
</div>
<!-- ═══ Section 2: Traces & Taps ═══ -->
<div style="background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;padding:16px;margin-bottom:12px;">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;">
<div style="font-size:12px;font-weight:600;color:#9ca3af;text-transform:uppercase;letter-spacing:0.5px;">Traces & Taps</div>
<span style="font-size:11px;color:#6b7280;">2 traced · 3 taps · manage taps on route pages</span>
</div>
<table style="width:100%;border-collapse:collapse;font-size:12px;">
<thead>
<tr style="border-bottom:1px solid #2d2d50;">
<th style="text-align:left;padding:8px;color:#9ca3af;font-size:11px;font-weight:500;">Processor</th>
<th style="text-align:left;padding:8px;color:#9ca3af;font-size:11px;font-weight:500;">Capture</th>
<th style="text-align:left;padding:8px;color:#9ca3af;font-size:11px;font-weight:500;">Taps</th>
</tr>
</thead>
<tbody>
<tr style="border-bottom:1px solid #161630;">
<td style="padding:8px;font-family:monospace;font-size:11px;color:#60a5fa;">unmarshal1</td>
<td style="padding:8px;"><span style="background:#2d1f3b;color:#d8b4fe;padding:1px 8px;border-radius:4px;font-size:10px;">BOTH</span></td>
<td style="padding:8px;">
<div style="display:flex;gap:6px;flex-wrap:wrap;">
<span style="background:#1e3a5f;color:#7dd3fc;padding:1px 8px;border-radius:10px;font-size:10px;font-family:monospace;">orderId <span style="color:#4ade80;margin-left:2px;">&#10003;</span></span>
<span style="background:#1e3a5f;color:#7dd3fc;padding:1px 8px;border-radius:10px;font-size:10px;font-family:monospace;">customerId <span style="color:#4ade80;margin-left:2px;">&#10003;</span></span>
</div>
</td>
</tr>
<tr style="border-bottom:1px solid #161630;">
<td style="padding:8px;font-family:monospace;font-size:11px;color:#60a5fa;">toDatabase</td>
<td style="padding:8px;"><span style="background:#1e3a5f;color:#7dd3fc;padding:1px 8px;border-radius:4px;font-size:10px;">INPUT</span></td>
<td style="padding:8px;"><span style="color:#6b7280;font-size:11px;"></span></td>
</tr>
<tr>
<td style="padding:8px;font-family:monospace;font-size:11px;color:#60a5fa;">enrichPrice</td>
<td style="padding:8px;"><span style="color:#6b7280;font-size:11px;"></span></td>
<td style="padding:8px;">
<span style="background:#3b2f1f;color:#fcd34d;padding:1px 8px;border-radius:10px;font-size:10px;font-family:monospace;">orderTotal <span style="color:#6b7280;margin-left:2px;">&#10007;</span></span>
</td>
</tr>
</tbody>
</table>
</div>
<!-- ═══ Section 3: Route Recording ═══ -->
<div style="background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;padding:16px;">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;">
<div style="font-size:12px;font-weight:600;color:#9ca3af;text-transform:uppercase;letter-spacing:0.5px;">Route Recording</div>
<span style="font-size:11px;color:#6b7280;">4 of 5 routes recording</span>
</div>
<table style="width:100%;border-collapse:collapse;font-size:12px;">
<thead>
<tr style="border-bottom:1px solid #2d2d50;">
<th style="text-align:left;padding:6px 8px;color:#9ca3af;font-size:11px;font-weight:500;">Route</th>
<th style="text-align:center;padding:6px 8px;color:#9ca3af;font-size:11px;font-weight:500;width:80px;">Recording</th>
</tr>
</thead>
<tbody>
<tr style="border-bottom:1px solid #161630;">
<td style="padding:6px 8px;font-family:monospace;font-size:11px;">processOrder</td>
<td style="padding:6px 8px;text-align:center;">
<div style="width:32px;height:18px;background:#3b82f6;border-radius:9px;position:relative;margin:0 auto;">
<div style="width:14px;height:14px;background:white;border-radius:50%;position:absolute;top:2px;right:2px;"></div>
</div>
</td>
</tr>
<tr style="border-bottom:1px solid #161630;">
<td style="padding:6px 8px;font-family:monospace;font-size:11px;">processPayment</td>
<td style="padding:6px 8px;text-align:center;">
<div style="width:32px;height:18px;background:#3b82f6;border-radius:9px;position:relative;margin:0 auto;">
<div style="width:14px;height:14px;background:white;border-radius:50%;position:absolute;top:2px;right:2px;"></div>
</div>
</td>
</tr>
<tr style="border-bottom:1px solid #161630;">
<td style="padding:6px 8px;font-family:monospace;font-size:11px;">sendNotification</td>
<td style="padding:6px 8px;text-align:center;">
<div style="width:32px;height:18px;background:#3b82f6;border-radius:9px;position:relative;margin:0 auto;">
<div style="width:14px;height:14px;background:white;border-radius:50%;position:absolute;top:2px;right:2px;"></div>
</div>
</td>
</tr>
<tr style="border-bottom:1px solid #161630;">
<td style="padding:6px 8px;font-family:monospace;font-size:11px;">handleRefund</td>
<td style="padding:6px 8px;text-align:center;">
<div style="width:32px;height:18px;background:#4b5563;border-radius:9px;position:relative;margin:0 auto;">
<div style="width:14px;height:14px;background:white;border-radius:50%;position:absolute;top:2px;left:2px;"></div>
</div>
</td>
</tr>
<tr>
<td style="padding:6px 8px;font-family:monospace;font-size:11px;">healthCheck</td>
<td style="padding:6px 8px;text-align:center;">
<div style="width:32px;height:18px;background:#3b82f6;border-radius:9px;position:relative;margin:0 auto;">
<div style="width:14px;height:14px;background:white;border-radius:50%;position:absolute;top:2px;right:2px;"></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>

View File

@@ -0,0 +1,77 @@
<h2>AppConfigDetailPage — Merged "Traces & Taps" Section</h2>
<p class="subtitle">Single table combining traced processors and data extraction taps</p>
<div class="mockup">
<div class="mockup-header">Traces & Taps — Merged Table</div>
<div class="mockup-body" style="background:#1a1a2e;color:#e0e0e0;padding:20px;font-family:system-ui,-apple-system,sans-serif;font-size:13px;">
<div style="background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;padding:16px;">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;">
<div style="font-size:12px;font-weight:600;color:#9ca3af;text-transform:uppercase;letter-spacing:0.5px;">Traces & Taps</div>
<span style="font-size:11px;color:#6b7280;">2 traced · 3 taps · manage taps on route pages</span>
</div>
<table style="width:100%;border-collapse:collapse;font-size:12px;">
<thead>
<tr style="border-bottom:1px solid #2d2d50;">
<th style="text-align:left;padding:8px;color:#9ca3af;font-size:11px;font-weight:500;">Processor</th>
<th style="text-align:left;padding:8px;color:#9ca3af;font-size:11px;font-weight:500;">Capture</th>
<th style="text-align:left;padding:8px;color:#9ca3af;font-size:11px;font-weight:500;">Taps</th>
</tr>
</thead>
<tbody>
<!-- Processor with both trace + taps -->
<tr style="border-bottom:1px solid #161630;">
<td style="padding:8px;font-family:monospace;font-size:11px;color:#60a5fa;">unmarshal1</td>
<td style="padding:8px;">
<span style="background:#2d1f3b;color:#d8b4fe;padding:1px 8px;border-radius:4px;font-size:10px;">BOTH</span>
</td>
<td style="padding:8px;">
<div style="display:flex;gap:6px;flex-wrap:wrap;">
<span style="background:#1e3a5f;color:#7dd3fc;padding:1px 8px;border-radius:10px;font-size:10px;font-family:monospace;">orderId <span style="color:#4ade80;margin-left:2px;">&#10003;</span></span>
<span style="background:#1e3a5f;color:#7dd3fc;padding:1px 8px;border-radius:10px;font-size:10px;font-family:monospace;">customerId <span style="color:#4ade80;margin-left:2px;">&#10003;</span></span>
</div>
</td>
</tr>
<!-- Processor with trace only -->
<tr style="border-bottom:1px solid #161630;">
<td style="padding:8px;font-family:monospace;font-size:11px;color:#60a5fa;">toDatabase</td>
<td style="padding:8px;">
<span style="background:#1e3a5f;color:#7dd3fc;padding:1px 8px;border-radius:4px;font-size:10px;">INPUT</span>
</td>
<td style="padding:8px;">
<span style="color:#6b7280;font-size:11px;"></span>
</td>
</tr>
<!-- Processor with tap only (no trace override) -->
<tr>
<td style="padding:8px;font-family:monospace;font-size:11px;color:#60a5fa;">enrichPrice</td>
<td style="padding:8px;">
<span style="color:#6b7280;font-size:11px;"></span>
</td>
<td style="padding:8px;">
<div style="display:flex;gap:6px;flex-wrap:wrap;">
<span style="background:#3b2f1f;color:#fcd34d;padding:1px 8px;border-radius:10px;font-size:10px;font-family:monospace;">orderTotal <span style="color:#6b7280;margin-left:2px;">&#10007;</span></span>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div style="margin-top:16px;"></div>
<div class="section">
<h3>Design Notes</h3>
<ul style="font-size:14px;line-height:1.8;">
<li><strong>One row per processor</strong> that has either a capture override or taps (or both)</li>
<li><strong>Capture column:</strong> shows the trace capture mode badge, or em-dash if default</li>
<li><strong>Taps column:</strong> attribute name badges with enabled/disabled indicator (&#10003; / &#10007;), or em-dash if none</li>
<li><strong>Tap badges color-coded by language:</strong> blue = simple, yellow = jsonpath (matches RouteDetail tap table)</li>
<li><strong>Edit mode:</strong> capture column becomes a dropdown, taps remain read-only (manage on route pages)</li>
<li><strong>Empty state:</strong> "No processor-specific traces or taps configured" with link to route pages</li>
</ul>
</div>

View File

@@ -0,0 +1,150 @@
<h2>ExchangeDetail — Business Attributes & Replay</h2>
<p class="subtitle">New elements added to the existing exchange detail page</p>
<div class="mockup">
<div class="mockup-header">Exchange Detail Page — Header Card (enhanced)</div>
<div class="mockup-body" style="background:#1a1a2e;color:#e0e0e0;padding:20px;font-family:system-ui,-apple-system,sans-serif;font-size:13px;">
<!-- Exchange Header -->
<div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:16px;">
<div>
<div style="display:flex;align-items:center;gap:10px;margin-bottom:8px;">
<span style="width:10px;height:10px;border-radius:50%;background:#4ade80;display:inline-block;"></span>
<span style="font-family:monospace;font-size:15px;font-weight:600;">a1b2c3d4-e5f6-7890-abcd-ef1234567890</span>
<span style="background:#065f46;color:#6ee7b7;padding:2px 8px;border-radius:4px;font-size:11px;font-weight:600;">COMPLETED</span>
</div>
<div style="display:flex;gap:16px;font-size:12px;color:#9ca3af;">
<span>Route: <span style="color:#60a5fa;">processOrder</span></span>
<span>App: <span style="font-family:monospace;">order-service</span></span>
<span>Correlation: <span style="font-family:monospace;">corr-abc123</span></span>
</div>
</div>
<div style="display:flex;gap:8px;align-items:center;">
<!-- REPLAY BUTTON (NEW) -->
<button style="background:#3b82f6;color:white;border:none;padding:6px 14px;border-radius:6px;font-size:12px;font-weight:600;cursor:pointer;display:flex;align-items:center;gap:6px;">
&#x21bb; Replay
</button>
</div>
</div>
<!-- Business Attributes Strip (NEW) -->
<div style="display:flex;gap:8px;flex-wrap:wrap;padding:10px 14px;background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;margin-bottom:16px;">
<span style="font-size:11px;color:#9ca3af;margin-right:4px;line-height:24px;">Attributes</span>
<span style="background:#1e3a5f;color:#7dd3fc;padding:2px 10px;border-radius:12px;font-size:11px;font-family:monospace;">orderId: ORD-2024-78542</span>
<span style="background:#3b1f4b;color:#d8b4fe;padding:2px 10px;border-radius:12px;font-size:11px;font-family:monospace;">customerId: CUST-1234</span>
<span style="background:#1a3a2a;color:#86efac;padding:2px 10px;border-radius:12px;font-size:11px;font-family:monospace;">orderTotal: €149.90</span>
<span style="background:#3b2f1f;color:#fcd34d;padding:2px 10px;border-radius:12px;font-size:11px;font-family:monospace;">region: EU-WEST</span>
</div>
<!-- Stat boxes row -->
<div style="display:flex;gap:12px;">
<div style="flex:1;background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;padding:10px 14px;">
<div style="font-size:10px;color:#9ca3af;text-transform:uppercase;letter-spacing:0.5px;">Duration</div>
<div style="font-size:18px;font-weight:600;color:#4ade80;">245ms</div>
</div>
<div style="flex:1;background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;padding:10px 14px;">
<div style="font-size:10px;color:#9ca3af;text-transform:uppercase;letter-spacing:0.5px;">Agent</div>
<div style="font-size:14px;font-family:monospace;color:#e0e0e0;">order-svc-01</div>
</div>
<div style="flex:1;background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;padding:10px 14px;">
<div style="font-size:10px;color:#9ca3af;text-transform:uppercase;letter-spacing:0.5px;">Started</div>
<div style="font-size:14px;font-family:monospace;color:#e0e0e0;">14:23:45.123</div>
</div>
<div style="flex:1;background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;padding:10px 14px;">
<div style="font-size:10px;color:#9ca3af;text-transform:uppercase;letter-spacing:0.5px;">Processors</div>
<div style="font-size:18px;font-weight:600;color:#e0e0e0;">12</div>
</div>
</div>
</div>
</div>
<div style="margin-top:24px;"></div>
<div class="mockup">
<div class="mockup-header">Replay Confirmation Dialog</div>
<div class="mockup-body" style="background:#1a1a2e;color:#e0e0e0;padding:40px;display:flex;justify-content:center;">
<div style="background:#1e1e3a;border:1px solid #2d2d50;border-radius:12px;padding:24px;width:480px;box-shadow:0 20px 60px rgba(0,0,0,0.5);">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;">
<span style="font-size:15px;font-weight:600;">Replay Exchange</span>
<span style="color:#9ca3af;cursor:pointer;"></span>
</div>
<div style="font-size:12px;color:#9ca3af;margin-bottom:16px;">
This will re-execute the exchange on the target agent. The original exchange data will be used as input.
</div>
<div style="margin-bottom:12px;">
<div style="font-size:11px;color:#9ca3af;margin-bottom:4px;">Original Exchange</div>
<div style="font-family:monospace;font-size:12px;background:#161630;border:1px solid #2d2d50;border-radius:6px;padding:8px 12px;">a1b2c3d4-e5f6-7890-abcd-ef1234567890</div>
</div>
<div style="margin-bottom:12px;">
<div style="font-size:11px;color:#9ca3af;margin-bottom:4px;">Target Agent</div>
<div style="background:#161630;border:1px solid #2d2d50;border-radius:6px;padding:8px 12px;font-size:12px;display:flex;justify-content:space-between;align-items:center;">
<span style="font-family:monospace;">order-svc-01</span>
<span style="color:#9ca3af;font-size:10px;"></span>
</div>
</div>
<div style="margin-bottom:16px;">
<div style="font-size:11px;color:#9ca3af;margin-bottom:4px;">Route</div>
<div style="font-family:monospace;font-size:12px;background:#161630;border:1px solid #2d2d50;border-radius:6px;padding:8px 12px;">processOrder</div>
</div>
<div style="display:flex;gap:8px;justify-content:flex-end;">
<button style="background:transparent;color:#9ca3af;border:1px solid #2d2d50;padding:6px 16px;border-radius:6px;font-size:12px;cursor:pointer;">Cancel</button>
<button style="background:#3b82f6;color:white;border:none;padding:6px 16px;border-radius:6px;font-size:12px;font-weight:600;cursor:pointer;">&#x21bb; Replay</button>
</div>
</div>
</div>
</div>
<div style="margin-top:24px;"></div>
<div class="mockup">
<div class="mockup-header">Dashboard — Exchanges Table (with business attributes)</div>
<div class="mockup-body" style="background:#1a1a2e;color:#e0e0e0;padding:16px;font-family:system-ui,-apple-system,sans-serif;font-size:12px;">
<table style="width:100%;border-collapse:collapse;">
<thead>
<tr style="border-bottom:1px solid #2d2d50;text-align:left;">
<th style="padding:8px 12px;color:#9ca3af;font-size:11px;font-weight:500;">Status</th>
<th style="padding:8px 12px;color:#9ca3af;font-size:11px;font-weight:500;">Route</th>
<th style="padding:8px 12px;color:#9ca3af;font-size:11px;font-weight:500;">App</th>
<th style="padding:8px 12px;color:#9ca3af;font-size:11px;font-weight:500;">Attributes</th>
<th style="padding:8px 12px;color:#9ca3af;font-size:11px;font-weight:500;">Exchange ID</th>
<th style="padding:8px 12px;color:#9ca3af;font-size:11px;font-weight:500;">Duration</th>
</tr>
</thead>
<tbody>
<tr style="border-bottom:1px solid #1e1e3a;">
<td style="padding:8px 12px;"><span style="width:8px;height:8px;border-radius:50%;background:#4ade80;display:inline-block;"></span> <span style="color:#6ee7b7;font-size:11px;">OK</span></td>
<td style="padding:8px 12px;color:#60a5fa;">processOrder</td>
<td style="padding:8px 12px;font-family:monospace;">order-svc</td>
<td style="padding:8px 12px;">
<span style="background:#1e3a5f;color:#7dd3fc;padding:1px 6px;border-radius:8px;font-size:10px;font-family:monospace;">ORD-78542</span>
<span style="background:#3b1f4b;color:#d8b4fe;padding:1px 6px;border-radius:8px;font-size:10px;font-family:monospace;">CUST-1234</span>
</td>
<td style="padding:8px 12px;font-family:monospace;font-size:11px;">a1b2c3d4-e5f6…</td>
<td style="padding:8px 12px;font-family:monospace;color:#4ade80;">245ms</td>
</tr>
<tr style="border-bottom:1px solid #1e1e3a;">
<td style="padding:8px 12px;"><span style="width:8px;height:8px;border-radius:50%;background:#f87171;display:inline-block;"></span> <span style="color:#fca5a5;font-size:11px;">ERR</span></td>
<td style="padding:8px 12px;color:#60a5fa;">processPayment</td>
<td style="padding:8px 12px;font-family:monospace;">payment-svc</td>
<td style="padding:8px 12px;">
<span style="background:#1e3a5f;color:#7dd3fc;padding:1px 6px;border-radius:8px;font-size:10px;font-family:monospace;">PAY-91023</span>
<span style="color:#6b7280;font-size:10px;">+2</span>
</td>
<td style="padding:8px 12px;font-family:monospace;font-size:11px;">f8e7d6c5-b4a3…</td>
<td style="padding:8px 12px;font-family:monospace;color:#f87171;">1,234ms</td>
</tr>
<tr style="border-bottom:1px solid #1e1e3a;">
<td style="padding:8px 12px;"><span style="width:8px;height:8px;border-radius:50%;background:#4ade80;display:inline-block;"></span> <span style="color:#6ee7b7;font-size:11px;">OK</span></td>
<td style="padding:8px 12px;color:#60a5fa;">sendNotification</td>
<td style="padding:8px 12px;font-family:monospace;">notif-svc</td>
<td style="padding:8px 12px;"><span style="color:#6b7280;font-size:10px;font-style:italic;"></span></td>
<td style="padding:8px 12px;font-family:monospace;font-size:11px;">12345678-abcd…</td>
<td style="padding:8px 12px;font-family:monospace;color:#4ade80;">89ms</td>
</tr>
</tbody>
</table>
<div style="margin-top:12px;font-size:11px;color:#6b7280;">
Note: Attributes column shows first 2 values as compact badges, "+N" overflow indicator when more exist. Em-dash when no attributes extracted.
</div>
</div>
</div>

View File

@@ -0,0 +1,138 @@
<h2>Replay Dialog — Revised</h2>
<p class="subtitle">Target agent selection + editable payload and headers</p>
<div class="mockup">
<div class="mockup-header">Replay Exchange Dialog (large modal)</div>
<div class="mockup-body" style="background:#1a1a2e;color:#e0e0e0;padding:40px;display:flex;justify-content:center;">
<div style="background:#1e1e3a;border:1px solid #2d2d50;border-radius:12px;padding:0;width:640px;box-shadow:0 20px 60px rgba(0,0,0,0.5);overflow:hidden;">
<!-- Dialog header -->
<div style="display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid #2d2d50;">
<span style="font-size:15px;font-weight:600;">Replay Exchange</span>
<span style="color:#9ca3af;cursor:pointer;font-size:18px;"></span>
</div>
<div style="padding:20px;">
<!-- Warning -->
<div style="font-size:12px;color:#fbbf24;background:#3b2f1f;border:1px solid #854d0e;border-radius:6px;padding:8px 12px;margin-bottom:16px;display:flex;align-items:center;gap:8px;">
<span></span> This will re-execute the exchange on the selected agent.
</div>
<!-- Target Agent -->
<div style="margin-bottom:16px;">
<div style="font-size:11px;color:#9ca3af;margin-bottom:4px;font-weight:500;">Target Agent</div>
<div style="background:#161630;border:1px solid #2d2d50;border-radius:6px;padding:8px 12px;font-size:12px;display:flex;justify-content:space-between;align-items:center;">
<span style="font-family:monospace;">order-svc-01</span>
<span style="color:#9ca3af;font-size:10px;"></span>
</div>
<div style="font-size:10px;color:#6b7280;margin-top:4px;">Only LIVE agents for this application are shown</div>
</div>
<!-- Tabs: Headers / Body -->
<div style="display:flex;gap:0;margin-bottom:0;border-bottom:1px solid #2d2d50;">
<div style="padding:8px 16px;font-size:12px;font-weight:600;color:#60a5fa;border-bottom:2px solid #3b82f6;cursor:pointer;">Headers</div>
<div style="padding:8px 16px;font-size:12px;color:#9ca3af;cursor:pointer;">Body</div>
</div>
<!-- Headers tab content -->
<div style="background:#161630;border:1px solid #2d2d50;border-top:none;border-radius:0 0 6px 6px;padding:12px;margin-bottom:16px;">
<table style="width:100%;border-collapse:collapse;font-size:11px;">
<thead>
<tr style="border-bottom:1px solid #2d2d50;">
<th style="text-align:left;padding:4px 8px;color:#9ca3af;font-weight:500;width:35%;">Key</th>
<th style="text-align:left;padding:4px 8px;color:#9ca3af;font-weight:500;">Value</th>
<th style="width:32px;"></th>
</tr>
</thead>
<tbody>
<tr style="border-bottom:1px solid #1e1e3a;">
<td style="padding:4px 8px;"><input style="background:#1a1a2e;border:1px solid #2d2d50;border-radius:4px;color:#e0e0e0;padding:4px 8px;width:100%;font-family:monospace;font-size:11px;box-sizing:border-box;" value="Content-Type" /></td>
<td style="padding:4px 8px;"><input style="background:#1a1a2e;border:1px solid #2d2d50;border-radius:4px;color:#e0e0e0;padding:4px 8px;width:100%;font-family:monospace;font-size:11px;box-sizing:border-box;" value="application/json" /></td>
<td style="padding:4px 8px;text-align:center;"><span style="color:#f87171;cursor:pointer;font-size:14px;"></span></td>
</tr>
<tr style="border-bottom:1px solid #1e1e3a;">
<td style="padding:4px 8px;"><input style="background:#1a1a2e;border:1px solid #2d2d50;border-radius:4px;color:#e0e0e0;padding:4px 8px;width:100%;font-family:monospace;font-size:11px;box-sizing:border-box;" value="X-Correlation-Id" /></td>
<td style="padding:4px 8px;"><input style="background:#1a1a2e;border:1px solid #2d2d50;border-radius:4px;color:#e0e0e0;padding:4px 8px;width:100%;font-family:monospace;font-size:11px;box-sizing:border-box;" value="corr-abc123" /></td>
<td style="padding:4px 8px;text-align:center;"><span style="color:#f87171;cursor:pointer;font-size:14px;"></span></td>
</tr>
<tr>
<td colspan="3" style="padding:6px 8px;">
<span style="color:#3b82f6;cursor:pointer;font-size:11px;">+ Add header</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Footer -->
<div style="display:flex;gap:8px;justify-content:flex-end;padding:12px 20px;border-top:1px solid #2d2d50;background:#1a1a30;">
<button style="background:transparent;color:#9ca3af;border:1px solid #2d2d50;padding:6px 16px;border-radius:6px;font-size:12px;cursor:pointer;">Cancel</button>
<button style="background:#3b82f6;color:white;border:none;padding:6px 16px;border-radius:6px;font-size:12px;font-weight:600;cursor:pointer;">&#x21bb; Replay</button>
</div>
</div>
</div>
</div>
<div style="margin-top:24px;"></div>
<div class="mockup">
<div class="mockup-header">Replay Dialog — Body Tab</div>
<div class="mockup-body" style="background:#1a1a2e;color:#e0e0e0;padding:40px;display:flex;justify-content:center;">
<div style="background:#1e1e3a;border:1px solid #2d2d50;border-radius:12px;padding:0;width:640px;box-shadow:0 20px 60px rgba(0,0,0,0.5);overflow:hidden;">
<!-- Dialog header -->
<div style="display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid #2d2d50;">
<span style="font-size:15px;font-weight:600;">Replay Exchange</span>
<span style="color:#9ca3af;cursor:pointer;font-size:18px;"></span>
</div>
<div style="padding:20px;">
<!-- Warning -->
<div style="font-size:12px;color:#fbbf24;background:#3b2f1f;border:1px solid #854d0e;border-radius:6px;padding:8px 12px;margin-bottom:16px;display:flex;align-items:center;gap:8px;">
<span></span> This will re-execute the exchange on the selected agent.
</div>
<!-- Target Agent (collapsed) -->
<div style="margin-bottom:16px;">
<div style="font-size:11px;color:#9ca3af;margin-bottom:4px;font-weight:500;">Target Agent</div>
<div style="background:#161630;border:1px solid #2d2d50;border-radius:6px;padding:8px 12px;font-size:12px;display:flex;justify-content:space-between;align-items:center;">
<span style="font-family:monospace;">order-svc-01</span>
<span style="color:#9ca3af;font-size:10px;"></span>
</div>
</div>
<!-- Tabs: Headers / Body -->
<div style="display:flex;gap:0;margin-bottom:0;border-bottom:1px solid #2d2d50;">
<div style="padding:8px 16px;font-size:12px;color:#9ca3af;cursor:pointer;">Headers</div>
<div style="padding:8px 16px;font-size:12px;font-weight:600;color:#60a5fa;border-bottom:2px solid #3b82f6;cursor:pointer;">Body</div>
</div>
<!-- Body tab content — editable code area -->
<div style="background:#161630;border:1px solid #2d2d50;border-top:none;border-radius:0 0 6px 6px;padding:0;margin-bottom:16px;position:relative;">
<div style="display:flex;justify-content:flex-end;padding:6px 8px;border-bottom:1px solid #2d2d50;">
<span style="font-size:10px;color:#6b7280;background:#1a1a2e;padding:2px 8px;border-radius:4px;">JSON</span>
</div>
<pre style="margin:0;padding:12px;font-family:monospace;font-size:11px;line-height:1.6;color:#e0e0e0;min-height:160px;overflow:auto;white-space:pre;"><span style="color:#9ca3af;">{</span>
<span style="color:#7dd3fc;">"orderId"</span><span style="color:#9ca3af;">:</span> <span style="color:#fcd34d;">"ORD-2024-78542"</span><span style="color:#9ca3af;">,</span>
<span style="color:#7dd3fc;">"customerId"</span><span style="color:#9ca3af;">:</span> <span style="color:#fcd34d;">"CUST-1234"</span><span style="color:#9ca3af;">,</span>
<span style="color:#7dd3fc;">"items"</span><span style="color:#9ca3af;">:</span> <span style="color:#9ca3af;">[</span>
<span style="color:#9ca3af;">{</span>
<span style="color:#7dd3fc;">"sku"</span><span style="color:#9ca3af;">:</span> <span style="color:#fcd34d;">"WIDGET-001"</span><span style="color:#9ca3af;">,</span>
<span style="color:#7dd3fc;">"qty"</span><span style="color:#9ca3af;">:</span> <span style="color:#c4b5fd;">3</span><span style="color:#9ca3af;">,</span>
<span style="color:#7dd3fc;">"price"</span><span style="color:#9ca3af;">:</span> <span style="color:#c4b5fd;">49.97</span>
<span style="color:#9ca3af;">}</span>
<span style="color:#9ca3af;">],</span>
<span style="color:#7dd3fc;">"total"</span><span style="color:#9ca3af;">:</span> <span style="color:#c4b5fd;">149.90</span>
<span style="color:#9ca3af;">}</span></pre>
</div>
</div>
<!-- Footer -->
<div style="display:flex;gap:8px;justify-content:flex-end;padding:12px 20px;border-top:1px solid #2d2d50;background:#1a1a30;">
<button style="background:transparent;color:#9ca3af;border:1px solid #2d2d50;padding:6px 16px;border-radius:6px;font-size:12px;cursor:pointer;">Cancel</button>
<button style="background:#3b82f6;color:white;border:none;padding:6px 16px;border-radius:6px;font-size:12px;font-weight:600;cursor:pointer;">&#x21bb; Replay</button>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,221 @@
<h2>RouteDetail — Tap Management & Recording Toggle</h2>
<p class="subtitle">New "Taps" tab on RouteDetail + recording toggle in header</p>
<div class="mockup">
<div class="mockup-header">RouteDetail Page — Header with Recording Toggle</div>
<div class="mockup-body" style="background:#1a1a2e;color:#e0e0e0;padding:20px;font-family:system-ui,-apple-system,sans-serif;font-size:13px;">
<!-- Route header -->
<div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:16px;">
<div>
<div style="font-size:16px;font-weight:600;margin-bottom:4px;">processOrder</div>
<div style="font-size:12px;color:#9ca3af;">
<span style="font-family:monospace;">order-service</span>
<span style="margin:0 8px;color:#2d2d50;">|</span>
<span style="color:#4ade80;">99.2% success</span>
<span style="margin:0 8px;color:#2d2d50;">|</span>
<span>245ms avg</span>
</div>
</div>
<div style="display:flex;align-items:center;gap:12px;">
<!-- Recording toggle -->
<div style="display:flex;align-items:center;gap:8px;background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;padding:6px 12px;">
<span style="font-size:11px;color:#9ca3af;">Recording</span>
<div style="width:36px;height:20px;background:#3b82f6;border-radius:10px;position:relative;cursor:pointer;">
<div style="width:16px;height:16px;background:white;border-radius:50%;position:absolute;top:2px;right:2px;transition:all 0.2s;"></div>
</div>
</div>
</div>
</div>
<!-- KPI strip (abbreviated) -->
<div style="display:flex;gap:10px;margin-bottom:16px;">
<div style="flex:1;background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;padding:8px 12px;">
<div style="font-size:10px;color:#9ca3af;">Success Rate</div>
<div style="font-size:16px;font-weight:600;color:#4ade80;">99.2%</div>
</div>
<div style="flex:1;background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;padding:8px 12px;">
<div style="font-size:10px;color:#9ca3af;">Avg Duration</div>
<div style="font-size:16px;font-weight:600;">245ms</div>
</div>
<div style="flex:1;background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;padding:8px 12px;">
<div style="font-size:10px;color:#9ca3af;">Total</div>
<div style="font-size:16px;font-weight:600;">12,482</div>
</div>
<div style="flex:1;background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;padding:8px 12px;">
<div style="font-size:10px;color:#9ca3af;">Active Taps</div>
<div style="font-size:16px;font-weight:600;color:#60a5fa;">3</div>
</div>
</div>
<!-- Tabs -->
<div style="display:flex;gap:0;border-bottom:1px solid #2d2d50;margin-bottom:16px;">
<div style="padding:8px 16px;font-size:12px;color:#9ca3af;cursor:pointer;">Overview</div>
<div style="padding:8px 16px;font-size:12px;color:#9ca3af;cursor:pointer;">Processors</div>
<div style="padding:8px 16px;font-size:12px;color:#9ca3af;cursor:pointer;">Errors</div>
<div style="padding:8px 16px;font-size:12px;color:#9ca3af;cursor:pointer;">Executions</div>
<div style="padding:8px 16px;font-size:12px;font-weight:600;color:#60a5fa;border-bottom:2px solid #3b82f6;cursor:pointer;">Taps</div>
</div>
<!-- Taps tab content -->
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
<div style="font-size:13px;font-weight:600;">Data Extraction Taps</div>
<button style="background:#3b82f6;color:white;border:none;padding:6px 14px;border-radius:6px;font-size:12px;font-weight:500;cursor:pointer;display:flex;align-items:center;gap:4px;">+ Add Tap</button>
</div>
<!-- Taps table -->
<div style="background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;overflow:hidden;">
<table style="width:100%;border-collapse:collapse;font-size:12px;">
<thead>
<tr style="border-bottom:1px solid #2d2d50;">
<th style="text-align:left;padding:10px 12px;color:#9ca3af;font-size:11px;font-weight:500;">Attribute</th>
<th style="text-align:left;padding:10px 12px;color:#9ca3af;font-size:11px;font-weight:500;">Processor</th>
<th style="text-align:left;padding:10px 12px;color:#9ca3af;font-size:11px;font-weight:500;">Expression</th>
<th style="text-align:left;padding:10px 12px;color:#9ca3af;font-size:11px;font-weight:500;">Language</th>
<th style="text-align:left;padding:10px 12px;color:#9ca3af;font-size:11px;font-weight:500;">Target</th>
<th style="text-align:left;padding:10px 12px;color:#9ca3af;font-size:11px;font-weight:500;">Type</th>
<th style="text-align:center;padding:10px 12px;color:#9ca3af;font-size:11px;font-weight:500;">Enabled</th>
<th style="width:60px;"></th>
</tr>
</thead>
<tbody>
<tr style="border-bottom:1px solid #161630;">
<td style="padding:8px 12px;font-weight:500;">orderId</td>
<td style="padding:8px 12px;font-family:monospace;font-size:11px;color:#60a5fa;">unmarshal1</td>
<td style="padding:8px 12px;font-family:monospace;font-size:11px;"><span style="background:#161630;padding:2px 6px;border-radius:4px;">${body.orderId}</span></td>
<td style="padding:8px 12px;"><span style="background:#1e3a5f;color:#7dd3fc;padding:1px 8px;border-radius:4px;font-size:10px;">simple</span></td>
<td style="padding:8px 12px;"><span style="background:#2d1f3b;color:#d8b4fe;padding:1px 8px;border-radius:4px;font-size:10px;">OUTPUT</span></td>
<td style="padding:8px 12px;"><span style="background:#1a3a2a;color:#86efac;padding:1px 8px;border-radius:4px;font-size:10px;">BUSINESS</span></td>
<td style="padding:8px 12px;text-align:center;">
<div style="width:32px;height:18px;background:#3b82f6;border-radius:9px;position:relative;margin:0 auto;cursor:pointer;">
<div style="width:14px;height:14px;background:white;border-radius:50%;position:absolute;top:2px;right:2px;"></div>
</div>
</td>
<td style="padding:8px 12px;text-align:center;">
<span style="color:#9ca3af;cursor:pointer;font-size:14px;" title="Edit">&#9998;</span>
<span style="color:#f87171;cursor:pointer;font-size:14px;margin-left:6px;" title="Delete">&#x2715;</span>
</td>
</tr>
<tr style="border-bottom:1px solid #161630;">
<td style="padding:8px 12px;font-weight:500;">customerId</td>
<td style="padding:8px 12px;font-family:monospace;font-size:11px;color:#60a5fa;">unmarshal1</td>
<td style="padding:8px 12px;font-family:monospace;font-size:11px;"><span style="background:#161630;padding:2px 6px;border-radius:4px;">${body.customer.id}</span></td>
<td style="padding:8px 12px;"><span style="background:#1e3a5f;color:#7dd3fc;padding:1px 8px;border-radius:4px;font-size:10px;">simple</span></td>
<td style="padding:8px 12px;"><span style="background:#2d1f3b;color:#d8b4fe;padding:1px 8px;border-radius:4px;font-size:10px;">OUTPUT</span></td>
<td style="padding:8px 12px;"><span style="background:#1a3a2a;color:#86efac;padding:1px 8px;border-radius:4px;font-size:10px;">CORRELATION</span></td>
<td style="padding:8px 12px;text-align:center;">
<div style="width:32px;height:18px;background:#3b82f6;border-radius:9px;position:relative;margin:0 auto;cursor:pointer;">
<div style="width:14px;height:14px;background:white;border-radius:50%;position:absolute;top:2px;right:2px;"></div>
</div>
</td>
<td style="padding:8px 12px;text-align:center;">
<span style="color:#9ca3af;cursor:pointer;font-size:14px;" title="Edit">&#9998;</span>
<span style="color:#f87171;cursor:pointer;font-size:14px;margin-left:6px;" title="Delete">&#x2715;</span>
</td>
</tr>
<tr>
<td style="padding:8px 12px;font-weight:500;">orderTotal</td>
<td style="padding:8px 12px;font-family:monospace;font-size:11px;color:#60a5fa;">enrichPrice</td>
<td style="padding:8px 12px;font-family:monospace;font-size:11px;"><span style="background:#161630;padding:2px 6px;border-radius:4px;">$.total</span></td>
<td style="padding:8px 12px;"><span style="background:#3b2f1f;color:#fcd34d;padding:1px 8px;border-radius:4px;font-size:10px;">jsonpath</span></td>
<td style="padding:8px 12px;"><span style="background:#2d1f3b;color:#d8b4fe;padding:1px 8px;border-radius:4px;font-size:10px;">OUTPUT</span></td>
<td style="padding:8px 12px;"><span style="background:#1a3a2a;color:#86efac;padding:1px 8px;border-radius:4px;font-size:10px;">BUSINESS</span></td>
<td style="padding:8px 12px;text-align:center;">
<div style="width:32px;height:18px;background:#4b5563;border-radius:9px;position:relative;margin:0 auto;cursor:pointer;">
<div style="width:14px;height:14px;background:white;border-radius:50%;position:absolute;top:2px;left:2px;"></div>
</div>
</td>
<td style="padding:8px 12px;text-align:center;">
<span style="color:#9ca3af;cursor:pointer;font-size:14px;" title="Edit">&#9998;</span>
<span style="color:#f87171;cursor:pointer;font-size:14px;margin-left:6px;" title="Delete">&#x2715;</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div style="margin-top:24px;"></div>
<div class="mockup">
<div class="mockup-header">Add/Edit Tap — Modal Dialog</div>
<div class="mockup-body" style="background:#1a1a2e;color:#e0e0e0;padding:40px;display:flex;justify-content:center;">
<div style="background:#1e1e3a;border:1px solid #2d2d50;border-radius:12px;padding:0;width:520px;box-shadow:0 20px 60px rgba(0,0,0,0.5);overflow:hidden;">
<!-- Header -->
<div style="display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid #2d2d50;">
<span style="font-size:15px;font-weight:600;">Add Tap</span>
<span style="color:#9ca3af;cursor:pointer;font-size:18px;"></span>
</div>
<div style="padding:20px;">
<!-- Attribute Name -->
<div style="margin-bottom:14px;">
<div style="font-size:11px;color:#9ca3af;margin-bottom:4px;font-weight:500;">Attribute Name <span style="color:#f87171;">*</span></div>
<input style="background:#161630;border:1px solid #2d2d50;border-radius:6px;color:#e0e0e0;padding:8px 12px;width:100%;font-size:12px;box-sizing:border-box;" placeholder="e.g. orderId, customerId" />
</div>
<!-- Processor -->
<div style="margin-bottom:14px;">
<div style="font-size:11px;color:#9ca3af;margin-bottom:4px;font-weight:500;">Processor <span style="color:#f87171;">*</span></div>
<div style="background:#161630;border:1px solid #2d2d50;border-radius:6px;padding:8px 12px;font-size:12px;display:flex;justify-content:space-between;align-items:center;">
<span style="color:#6b7280;">Select processor…</span>
<span style="color:#9ca3af;font-size:10px;"></span>
</div>
<div style="font-size:10px;color:#6b7280;margin-top:3px;">Processors from this route's diagram</div>
</div>
<!-- Two columns: Language + Target -->
<div style="display:flex;gap:12px;margin-bottom:14px;">
<div style="flex:1;">
<div style="font-size:11px;color:#9ca3af;margin-bottom:4px;font-weight:500;">Language <span style="color:#f87171;">*</span></div>
<div style="background:#161630;border:1px solid #2d2d50;border-radius:6px;padding:8px 12px;font-size:12px;display:flex;justify-content:space-between;align-items:center;">
<span>simple</span>
<span style="color:#9ca3af;font-size:10px;"></span>
</div>
</div>
<div style="flex:1;">
<div style="font-size:11px;color:#9ca3af;margin-bottom:4px;font-weight:500;">Target <span style="color:#f87171;">*</span></div>
<div style="background:#161630;border:1px solid #2d2d50;border-radius:6px;padding:8px 12px;font-size:12px;display:flex;justify-content:space-between;align-items:center;">
<span>OUTPUT</span>
<span style="color:#9ca3af;font-size:10px;"></span>
</div>
</div>
</div>
<!-- Expression -->
<div style="margin-bottom:14px;">
<div style="font-size:11px;color:#9ca3af;margin-bottom:4px;font-weight:500;">Expression <span style="color:#f87171;">*</span></div>
<textarea style="background:#161630;border:1px solid #2d2d50;border-radius:6px;color:#e0e0e0;padding:8px 12px;width:100%;font-family:monospace;font-size:12px;box-sizing:border-box;resize:vertical;min-height:48px;" placeholder="e.g. ${body.orderId} or $.customer.id">${body.orderId}</textarea>
<div style="font-size:10px;color:#6b7280;margin-top:3px;">Camel expression — evaluated at the selected processor</div>
</div>
<!-- Attribute Type -->
<div style="margin-bottom:14px;">
<div style="font-size:11px;color:#9ca3af;margin-bottom:4px;font-weight:500;">Attribute Type</div>
<div style="display:flex;gap:8px;">
<div style="background:#1e3a5f;color:#7dd3fc;padding:4px 12px;border-radius:6px;font-size:11px;cursor:pointer;border:1px solid #3b82f6;">BUSINESS_OBJECT</div>
<div style="background:#161630;color:#9ca3af;padding:4px 12px;border-radius:6px;font-size:11px;cursor:pointer;border:1px solid #2d2d50;">CORRELATION</div>
<div style="background:#161630;color:#9ca3af;padding:4px 12px;border-radius:6px;font-size:11px;cursor:pointer;border:1px solid #2d2d50;">EVENT</div>
<div style="background:#161630;color:#9ca3af;padding:4px 12px;border-radius:6px;font-size:11px;cursor:pointer;border:1px solid #2d2d50;">CUSTOM</div>
</div>
</div>
<!-- Enabled -->
<div style="display:flex;align-items:center;gap:8px;margin-bottom:4px;">
<div style="width:36px;height:20px;background:#3b82f6;border-radius:10px;position:relative;cursor:pointer;">
<div style="width:16px;height:16px;background:white;border-radius:50%;position:absolute;top:2px;right:2px;"></div>
</div>
<span style="font-size:12px;color:#e0e0e0;">Enabled</span>
</div>
</div>
<!-- Footer -->
<div style="display:flex;gap:8px;justify-content:flex-end;padding:12px 20px;border-top:1px solid #2d2d50;background:#1a1a30;">
<button style="background:transparent;color:#9ca3af;border:1px solid #2d2d50;padding:6px 16px;border-radius:6px;font-size:12px;cursor:pointer;">Cancel</button>
<button style="background:#3b82f6;color:white;border:none;padding:6px 16px;border-radius:6px;font-size:12px;font-weight:600;cursor:pointer;">Save Tap</button>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,175 @@
<h2>Add Tap — With Expression Testing</h2>
<p class="subtitle">Collapsible test section at bottom of the tap modal</p>
<div class="mockup">
<div class="mockup-header">Add Tap Modal — Test Expression (Recent Exchange)</div>
<div class="mockup-body" style="background:#1a1a2e;color:#e0e0e0;padding:40px;display:flex;justify-content:center;">
<div style="background:#1e1e3a;border:1px solid #2d2d50;border-radius:12px;padding:0;width:560px;box-shadow:0 20px 60px rgba(0,0,0,0.5);overflow:hidden;">
<!-- Header -->
<div style="display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid #2d2d50;">
<span style="font-size:15px;font-weight:600;">Add Tap</span>
<span style="color:#9ca3af;cursor:pointer;font-size:18px;"></span>
</div>
<div style="padding:20px;max-height:70vh;overflow-y:auto;">
<!-- Form fields (collapsed for brevity) -->
<div style="margin-bottom:14px;">
<div style="font-size:11px;color:#9ca3af;margin-bottom:4px;font-weight:500;">Attribute Name <span style="color:#f87171;">*</span></div>
<input style="background:#161630;border:1px solid #2d2d50;border-radius:6px;color:#e0e0e0;padding:8px 12px;width:100%;font-size:12px;box-sizing:border-box;" value="orderId" />
</div>
<div style="margin-bottom:14px;">
<div style="font-size:11px;color:#9ca3af;margin-bottom:4px;font-weight:500;">Processor <span style="color:#f87171;">*</span></div>
<div style="background:#161630;border:1px solid #2d2d50;border-radius:6px;padding:8px 12px;font-size:12px;display:flex;justify-content:space-between;align-items:center;">
<span style="font-family:monospace;">unmarshal1</span>
<span style="color:#9ca3af;font-size:10px;"></span>
</div>
</div>
<div style="display:flex;gap:12px;margin-bottom:14px;">
<div style="flex:1;">
<div style="font-size:11px;color:#9ca3af;margin-bottom:4px;font-weight:500;">Language</div>
<div style="background:#161630;border:1px solid #2d2d50;border-radius:6px;padding:8px 12px;font-size:12px;display:flex;justify-content:space-between;align-items:center;">
<span>simple</span><span style="color:#9ca3af;font-size:10px;"></span>
</div>
</div>
<div style="flex:1;">
<div style="font-size:11px;color:#9ca3af;margin-bottom:4px;font-weight:500;">Target</div>
<div style="background:#161630;border:1px solid #2d2d50;border-radius:6px;padding:8px 12px;font-size:12px;display:flex;justify-content:space-between;align-items:center;">
<span>OUTPUT</span><span style="color:#9ca3af;font-size:10px;"></span>
</div>
</div>
</div>
<div style="margin-bottom:14px;">
<div style="font-size:11px;color:#9ca3af;margin-bottom:4px;font-weight:500;">Expression <span style="color:#f87171;">*</span></div>
<textarea style="background:#161630;border:1px solid #2d2d50;border-radius:6px;color:#e0e0e0;padding:8px 12px;width:100%;font-family:monospace;font-size:12px;box-sizing:border-box;resize:vertical;min-height:40px;">${body.orderId}</textarea>
</div>
<div style="margin-bottom:14px;">
<div style="font-size:11px;color:#9ca3af;margin-bottom:4px;font-weight:500;">Attribute Type</div>
<div style="display:flex;gap:8px;">
<div style="background:#1e3a5f;color:#7dd3fc;padding:4px 12px;border-radius:6px;font-size:11px;cursor:pointer;border:1px solid #3b82f6;">BUSINESS_OBJECT</div>
<div style="background:#161630;color:#9ca3af;padding:4px 12px;border-radius:6px;font-size:11px;cursor:pointer;border:1px solid #2d2d50;">CORRELATION</div>
<div style="background:#161630;color:#9ca3af;padding:4px 12px;border-radius:6px;font-size:11px;cursor:pointer;border:1px solid #2d2d50;">EVENT</div>
<div style="background:#161630;color:#9ca3af;padding:4px 12px;border-radius:6px;font-size:11px;cursor:pointer;border:1px solid #2d2d50;">CUSTOM</div>
</div>
</div>
<!-- ═══ TEST EXPRESSION SECTION ═══ -->
<div style="border-top:1px solid #2d2d50;margin-top:8px;padding-top:14px;">
<div style="display:flex;align-items:center;gap:6px;margin-bottom:12px;cursor:pointer;">
<span style="color:#60a5fa;font-size:10px;">&#9660;</span>
<span style="font-size:12px;font-weight:600;color:#60a5fa;">Test Expression</span>
</div>
<!-- Data source tabs -->
<div style="display:flex;gap:0;margin-bottom:0;border-bottom:1px solid #2d2d50;">
<div style="padding:6px 14px;font-size:11px;font-weight:600;color:#60a5fa;border-bottom:2px solid #3b82f6;cursor:pointer;">Recent Exchange</div>
<div style="padding:6px 14px;font-size:11px;color:#9ca3af;cursor:pointer;">Custom Payload</div>
</div>
<!-- Recent exchange picker -->
<div style="background:#161630;border:1px solid #2d2d50;border-top:none;border-radius:0 0 6px 6px;padding:12px;">
<div style="margin-bottom:10px;">
<div style="background:#1a1a2e;border:1px solid #2d2d50;border-radius:6px;padding:8px 12px;font-size:11px;display:flex;justify-content:space-between;align-items:center;">
<div style="display:flex;align-items:center;gap:8px;">
<span style="width:7px;height:7px;border-radius:50%;background:#4ade80;display:inline-block;"></span>
<span style="font-family:monospace;color:#e0e0e0;">a1b2c3d4-e5f6-7890</span>
<span style="color:#6b7280;">·</span>
<span style="color:#6b7280;">245ms</span>
<span style="color:#6b7280;">·</span>
<span style="color:#6b7280;">2 min ago</span>
</div>
<span style="color:#9ca3af;font-size:10px;"></span>
</div>
</div>
<!-- Test button + result -->
<div style="display:flex;gap:8px;align-items:flex-start;">
<button style="background:#3b82f6;color:white;border:none;padding:6px 14px;border-radius:6px;font-size:11px;font-weight:600;cursor:pointer;white-space:nowrap;">&#9654; Test</button>
<div style="flex:1;background:#0f2a1a;border:1px solid #166534;border-radius:6px;padding:8px 12px;">
<div style="font-size:10px;color:#6b7280;margin-bottom:2px;">Result</div>
<div style="font-family:monospace;font-size:12px;color:#4ade80;">ORD-2024-78542</div>
</div>
</div>
</div>
</div>
</div>
<!-- Footer -->
<div style="display:flex;gap:8px;justify-content:flex-end;padding:12px 20px;border-top:1px solid #2d2d50;background:#1a1a30;">
<button style="background:transparent;color:#9ca3af;border:1px solid #2d2d50;padding:6px 16px;border-radius:6px;font-size:12px;cursor:pointer;">Cancel</button>
<button style="background:#3b82f6;color:white;border:none;padding:6px 16px;border-radius:6px;font-size:12px;font-weight:600;cursor:pointer;">Save Tap</button>
</div>
</div>
</div>
</div>
<div style="margin-top:24px;"></div>
<div class="mockup">
<div class="mockup-header">Test Expression — Custom Payload Mode</div>
<div class="mockup-body" style="background:#1a1a2e;color:#e0e0e0;padding:40px;display:flex;justify-content:center;">
<div style="background:#1e1e3a;border:1px solid #2d2d50;border-radius:12px;padding:0;width:560px;box-shadow:0 20px 60px rgba(0,0,0,0.5);overflow:hidden;">
<!-- Header -->
<div style="display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid #2d2d50;">
<span style="font-size:15px;font-weight:600;">Add Tap</span>
<span style="color:#9ca3af;cursor:pointer;font-size:18px;"></span>
</div>
<div style="padding:20px;">
<!-- Form fields abbreviated -->
<div style="text-align:center;padding:8px;font-size:11px;color:#6b7280;border:1px dashed #2d2d50;border-radius:6px;margin-bottom:14px;">
⬆ Form fields above (attribute name, processor, language, target, expression, type)
</div>
<!-- ═══ TEST EXPRESSION SECTION ═══ -->
<div style="border-top:1px solid #2d2d50;padding-top:14px;">
<div style="display:flex;align-items:center;gap:6px;margin-bottom:12px;cursor:pointer;">
<span style="color:#60a5fa;font-size:10px;">&#9660;</span>
<span style="font-size:12px;font-weight:600;color:#60a5fa;">Test Expression</span>
</div>
<!-- Data source tabs -->
<div style="display:flex;gap:0;margin-bottom:0;border-bottom:1px solid #2d2d50;">
<div style="padding:6px 14px;font-size:11px;color:#9ca3af;cursor:pointer;">Recent Exchange</div>
<div style="padding:6px 14px;font-size:11px;font-weight:600;color:#60a5fa;border-bottom:2px solid #3b82f6;cursor:pointer;">Custom Payload</div>
</div>
<!-- Custom payload editor -->
<div style="background:#161630;border:1px solid #2d2d50;border-top:none;border-radius:0 0 6px 6px;padding:12px;">
<div style="margin-bottom:10px;">
<textarea style="background:#1a1a2e;border:1px solid #2d2d50;border-radius:6px;color:#e0e0e0;padding:8px 12px;width:100%;font-family:monospace;font-size:11px;box-sizing:border-box;resize:vertical;min-height:100px;line-height:1.5;">{
"orderId": "ORD-2024-78542",
"customer": {
"id": "CUST-1234",
"name": "Acme Corp"
},
"total": 149.90
}</textarea>
</div>
<!-- Test button + error result -->
<div style="display:flex;gap:8px;align-items:flex-start;">
<button style="background:#3b82f6;color:white;border:none;padding:6px 14px;border-radius:6px;font-size:11px;font-weight:600;cursor:pointer;white-space:nowrap;">&#9654; Test</button>
<div style="flex:1;background:#2a0f0f;border:1px solid #991b1b;border-radius:6px;padding:8px 12px;">
<div style="font-size:10px;color:#6b7280;margin-bottom:2px;">Error</div>
<div style="font-family:monospace;font-size:11px;color:#f87171;">Expression evaluation timed out (50ms limit)</div>
</div>
</div>
<div style="font-size:10px;color:#6b7280;margin-top:8px;">Evaluated by agent <span style="font-family:monospace;">order-svc-01</span> using Camel's <span style="font-family:monospace;">simple</span> language</div>
</div>
</div>
</div>
<!-- Footer -->
<div style="display:flex;gap:8px;justify-content:flex-end;padding:12px 20px;border-top:1px solid #2d2d50;background:#1a1a30;">
<button style="background:transparent;color:#9ca3af;border:1px solid #2d2d50;padding:6px 16px;border-radius:6px;font-size:12px;cursor:pointer;">Cancel</button>
<button style="background:#3b82f6;color:white;border:none;padding:6px 16px;border-radius:6px;font-size:12px;font-weight:600;cursor:pointer;">Save Tap</button>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,61 @@
<h2>Traces & Taps — With Route Column</h2>
<p class="subtitle">Route column added to prevent ambiguity across routes</p>
<div class="mockup">
<div class="mockup-header">Traces & Taps — Updated</div>
<div class="mockup-body" style="background:#1a1a2e;color:#e0e0e0;padding:20px;font-family:system-ui,-apple-system,sans-serif;font-size:13px;">
<div style="background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;padding:16px;">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;">
<div style="font-size:12px;font-weight:600;color:#9ca3af;text-transform:uppercase;letter-spacing:0.5px;">Traces & Taps</div>
<span style="font-size:11px;color:#6b7280;">3 traced · 4 taps · manage taps on route pages</span>
</div>
<table style="width:100%;border-collapse:collapse;font-size:12px;">
<thead>
<tr style="border-bottom:1px solid #2d2d50;">
<th style="text-align:left;padding:8px;color:#9ca3af;font-size:11px;font-weight:500;">Route</th>
<th style="text-align:left;padding:8px;color:#9ca3af;font-size:11px;font-weight:500;">Processor</th>
<th style="text-align:left;padding:8px;color:#9ca3af;font-size:11px;font-weight:500;">Capture</th>
<th style="text-align:left;padding:8px;color:#9ca3af;font-size:11px;font-weight:500;">Taps</th>
</tr>
</thead>
<tbody>
<tr style="border-bottom:1px solid #161630;">
<td style="padding:8px;color:#60a5fa;font-size:11px;">processOrder</td>
<td style="padding:8px;font-family:monospace;font-size:11px;">unmarshal1</td>
<td style="padding:8px;"><span style="background:#2d1f3b;color:#d8b4fe;padding:1px 8px;border-radius:4px;font-size:10px;">BOTH</span></td>
<td style="padding:8px;">
<div style="display:flex;gap:6px;flex-wrap:wrap;">
<span style="background:#1e3a5f;color:#7dd3fc;padding:1px 8px;border-radius:10px;font-size:10px;font-family:monospace;">orderId <span style="color:#4ade80;margin-left:2px;">&#10003;</span></span>
<span style="background:#1e3a5f;color:#7dd3fc;padding:1px 8px;border-radius:10px;font-size:10px;font-family:monospace;">customerId <span style="color:#4ade80;margin-left:2px;">&#10003;</span></span>
</div>
</td>
</tr>
<tr style="border-bottom:1px solid #161630;">
<td style="padding:8px;color:#60a5fa;font-size:11px;">processOrder</td>
<td style="padding:8px;font-family:monospace;font-size:11px;">enrichPrice</td>
<td style="padding:8px;"><span style="color:#6b7280;font-size:11px;"></span></td>
<td style="padding:8px;">
<span style="background:#3b2f1f;color:#fcd34d;padding:1px 8px;border-radius:10px;font-size:10px;font-family:monospace;">orderTotal <span style="color:#6b7280;margin-left:2px;">&#10007;</span></span>
</td>
</tr>
<tr style="border-bottom:1px solid #161630;">
<td style="padding:8px;color:#60a5fa;font-size:11px;">processPayment</td>
<td style="padding:8px;font-family:monospace;font-size:11px;">toDatabase</td>
<td style="padding:8px;"><span style="background:#1e3a5f;color:#7dd3fc;padding:1px 8px;border-radius:4px;font-size:10px;">INPUT</span></td>
<td style="padding:8px;"><span style="color:#6b7280;font-size:11px;"></span></td>
</tr>
<tr>
<td style="padding:8px;color:#60a5fa;font-size:11px;">processPayment</td>
<td style="padding:8px;font-family:monospace;font-size:11px;">validate1</td>
<td style="padding:8px;"><span style="color:#6b7280;font-size:11px;"></span></td>
<td style="padding:8px;">
<span style="background:#1e3a5f;color:#7dd3fc;padding:1px 8px;border-radius:10px;font-size:10px;font-family:monospace;">paymentRef <span style="color:#4ade80;margin-left:2px;">&#10003;</span></span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>

View File

@@ -0,0 +1,3 @@
<div style="display:flex;align-items:center;justify-content:center;min-height:60vh">
<p class="subtitle">Continuing in terminal...</p>
</div>

View File

@@ -0,0 +1 @@
{"reason":"idle timeout","timestamp":1774552065018}

View File

@@ -0,0 +1 @@
2048

101
AGENTS.md Normal file
View File

@@ -0,0 +1,101 @@
<!-- gitnexus:start -->
# GitNexus — Code Intelligence
This project is indexed by GitNexus as **cameleer-server** (6306 symbols, 15892 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.
## Always Do
- **MUST run impact analysis before editing any symbol.** Before modifying a function, class, or method, run `gitnexus_impact({target: "symbolName", direction: "upstream"})` and report the blast radius (direct callers, affected processes, risk level) to the user.
- **MUST run `gitnexus_detect_changes()` before committing** to verify your changes only affect expected symbols and execution flows.
- **MUST warn the user** if impact analysis returns HIGH or CRITICAL risk before proceeding with edits.
- When exploring unfamiliar code, use `gitnexus_query({query: "concept"})` to find execution flows instead of grepping. It returns process-grouped results ranked by relevance.
- When you need full context on a specific symbol — callers, callees, which execution flows it participates in — use `gitnexus_context({name: "symbolName"})`.
## When Debugging
1. `gitnexus_query({query: "<error or symptom>"})` — find execution flows related to the issue
2. `gitnexus_context({name: "<suspect function>"})` — see all callers, callees, and process participation
3. `READ gitnexus://repo/cameleer-server/process/{processName}` — trace the full execution flow step by step
4. For regressions: `gitnexus_detect_changes({scope: "compare", base_ref: "main"})` — see what your branch changed
## When Refactoring
- **Renaming**: MUST use `gitnexus_rename({symbol_name: "old", new_name: "new", dry_run: true})` first. Review the preview — graph edits are safe, text_search edits need manual review. Then run with `dry_run: false`.
- **Extracting/Splitting**: MUST run `gitnexus_context({name: "target"})` to see all incoming/outgoing refs, then `gitnexus_impact({target: "target", direction: "upstream"})` to find all external callers before moving code.
- After any refactor: run `gitnexus_detect_changes({scope: "all"})` to verify only expected files changed.
## Never Do
- NEVER edit a function, class, or method without first running `gitnexus_impact` on it.
- NEVER ignore HIGH or CRITICAL risk warnings from impact analysis.
- NEVER rename symbols with find-and-replace — use `gitnexus_rename` which understands the call graph.
- NEVER commit changes without running `gitnexus_detect_changes()` to check affected scope.
## Tools Quick Reference
| Tool | When to use | Command |
|------|-------------|---------|
| `query` | Find code by concept | `gitnexus_query({query: "auth validation"})` |
| `context` | 360-degree view of one symbol | `gitnexus_context({name: "validateUser"})` |
| `impact` | Blast radius before editing | `gitnexus_impact({target: "X", direction: "upstream"})` |
| `detect_changes` | Pre-commit scope check | `gitnexus_detect_changes({scope: "staged"})` |
| `rename` | Safe multi-file rename | `gitnexus_rename({symbol_name: "old", new_name: "new", dry_run: true})` |
| `cypher` | Custom graph queries | `gitnexus_cypher({query: "MATCH ..."})` |
## Impact Risk Levels
| Depth | Meaning | Action |
|-------|---------|--------|
| d=1 | WILL BREAK — direct callers/importers | MUST update these |
| d=2 | LIKELY AFFECTED — indirect deps | Should test |
| d=3 | MAY NEED TESTING — transitive | Test if critical path |
## Resources
| Resource | Use for |
|----------|---------|
| `gitnexus://repo/cameleer-server/context` | Codebase overview, check index freshness |
| `gitnexus://repo/cameleer-server/clusters` | All functional areas |
| `gitnexus://repo/cameleer-server/processes` | All execution flows |
| `gitnexus://repo/cameleer-server/process/{name}` | Step-by-step execution trace |
## Self-Check Before Finishing
Before completing any code modification task, verify:
1. `gitnexus_impact` was run for all modified symbols
2. No HIGH/CRITICAL risk warnings were ignored
3. `gitnexus_detect_changes()` confirms changes match expected scope
4. All d=1 (WILL BREAK) dependents were updated
## Keeping the Index Fresh
After committing code changes, the GitNexus index becomes stale. Re-run analyze to update it:
```bash
npx gitnexus analyze
```
If the index previously included embeddings, preserve them by adding `--embeddings`:
```bash
npx gitnexus analyze --embeddings
```
To check whether embeddings exist, inspect `.gitnexus/meta.json` — the `stats.embeddings` field shows the count (0 means no embeddings). **Running analyze without `--embeddings` will delete any previously generated embeddings.**
> Claude Code users: A PostToolUse hook handles this automatically after `git commit` and `git merge`.
## CLI
| Task | Read this skill file |
|------|---------------------|
| Understand architecture / "How does X work?" | `.claude/skills/gitnexus/gitnexus-exploring/SKILL.md` |
| Blast radius / "What breaks if I change X?" | `.claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md` |
| Trace bugs / "Why is X failing?" | `.claude/skills/gitnexus/gitnexus-debugging/SKILL.md` |
| Rename / extract / split / refactor | `.claude/skills/gitnexus/gitnexus-refactoring/SKILL.md` |
| Tools, resources, schema reference | `.claude/skills/gitnexus/gitnexus-guide/SKILL.md` |
| Index, status, clean, wiki CLI commands | `.claude/skills/gitnexus/gitnexus-cli/SKILL.md` |
<!-- gitnexus:end -->

166
CLAUDE.md
View File

@@ -4,18 +4,18 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Project
Cameleer3 Server — observability server that receives, stores, and serves Camel route execution data and route diagrams from Cameleer3 agents. Pushes config and commands to agents via SSE.
Cameleer Server — observability server that receives, stores, and serves Camel route execution data and route diagrams from Cameleer agents. Pushes config and commands to agents via SSE. Also orchestrates Docker container deployments when running under cameleer-saas.
## Related Project
- **cameleer3** (`https://gitea.siegeln.net/cameleer/cameleer3`) — the Java agent that instruments Camel applications
- Protocol defined in `cameleer3-common/PROTOCOL.md` in the agent repo
- This server depends on `com.cameleer3:cameleer3-common` (shared models and graph API)
- **cameleer** (`https://gitea.siegeln.net/cameleer/cameleer`) — the Java agent that instruments Camel applications
- Protocol defined in `cameleer-common/PROTOCOL.md` in the agent repo
- This server depends on `com.cameleer:cameleer-common` (shared models and graph API)
## Modules
- `cameleer3-server-core` — domain logic, storage, agent registry
- `cameleer3-server-app` — Spring Boot web app, REST controllers, SSE, static resources
- `cameleer-server-core` — domain logic, storage interfaces, services (no Spring dependencies)
- `cameleer-server-app` — Spring Boot web app, REST controllers, SSE, persistence, Docker orchestration
## Build Commands
@@ -27,36 +27,152 @@ mvn clean verify # Full build with tests
## Run
```bash
java -jar cameleer3-server-app/target/cameleer3-server-app-1.0-SNAPSHOT.jar
java -jar cameleer-server-app/target/cameleer-server-app-1.0-SNAPSHOT.jar
```
## Key Conventions
- Java 17+ required
- Spring Boot 3.4.3 parent POM
- Depends on `com.cameleer3:cameleer3-common` from Gitea Maven registry
- Depends on `com.cameleer:cameleer-common` from Gitea Maven registry
- Jackson `JavaTimeModule` for `Instant` deserialization
- Communication: receives HTTP POST data from agents, serves SSE event streams for config push/commands
- Maintains agent instance registry with states: LIVE → STALE → DEAD
- Storage: PostgreSQL (TimescaleDB) for structured data, OpenSearch for full-text search
- Security: JWT auth with RBAC (AGENT/VIEWER/OPERATOR/ADMIN roles), Ed25519 config signing, bootstrap token for registration
- OIDC: Optional external identity provider support (token exchange pattern). Configured via admin API, stored in database (`server_config` table)
- Communication: receives HTTP POST data from agents (executions, diagrams, metrics, logs), serves SSE event streams for config push/commands (config-update, deep-trace, replay, route-control)
- Environment filtering: all data queries filter by the selected environment. All commands target only agents in the selected environment. Backend endpoints accept optional `environment` query parameter; null = all environments (backward compatible).
- Maintains agent instance registry (in-memory) with states: LIVE -> STALE -> DEAD. Auto-heals from JWT `env` claim + heartbeat body on heartbeat/SSE after server restart (priority: heartbeat `environmentId` > JWT `env` claim > `"default"`). Capabilities and route states updated on every heartbeat (protocol v2). Route catalog merges three sources: in-memory agent registry, persistent `route_catalog` table (ClickHouse), and `stats_1m_route` execution stats. The persistent catalog tracks `first_seen`/`last_seen` per route per environment, updated on every registration and heartbeat. Routes appear in the sidebar when their lifecycle overlaps the selected time window (`first_seen <= to AND last_seen >= from`), so historical routes remain visible even after being dropped from newer app versions.
- Multi-tenancy: each server instance serves one tenant (configured via `CAMELEER_SERVER_TENANT_ID`, default: `"default"`). Environments (dev/staging/prod) are first-class. PostgreSQL isolated via schema-per-tenant (`?currentSchema=tenant_{id}`) and `ApplicationName=tenant_{id}` on the JDBC URL. ClickHouse shared DB with `tenant_id` + `environment` columns, partitioned by `(tenant_id, toYYYYMM(timestamp))`.
- Storage: PostgreSQL for RBAC, config, and audit; ClickHouse for all observability data (executions, search, logs, metrics, stats, diagrams). ClickHouse schema migrations in `clickhouse/*.sql`, run idempotently on startup by `ClickHouseSchemaInitializer`. Use `IF NOT EXISTS` for CREATE and ADD PROJECTION.
- Log exchange correlation: `ClickHouseLogStore` extracts `exchange_id` from log entry MDC, preferring `cameleer.exchangeId` over `camel.exchangeId` (fallback for older agents). For `ON_COMPLETION` exchange copies, the agent sets `cameleer.exchangeId` to the parent's exchange ID via `CORRELATION_ID`.
- Log processor correlation: The agent sets `cameleer.processorId` in MDC, identifying which processor node emitted a log line.
- Logging: ClickHouse JDBC set to INFO (`com.clickhouse`), HTTP client to WARN (`org.apache.hc.client5`) in application.yml
- Security: JWT auth with RBAC (AGENT/VIEWER/OPERATOR/ADMIN roles), Ed25519 config signing (key derived deterministically from JWT secret via HMAC-SHA256), bootstrap token for registration. CORS: `CAMELEER_SERVER_SECURITY_CORSALLOWEDORIGINS` (comma-separated) overrides `CAMELEER_SERVER_SECURITY_UIORIGIN` for multi-origin setups. Infrastructure access: `CAMELEER_SERVER_SECURITY_INFRASTRUCTUREENDPOINTS=false` disables Database and ClickHouse admin endpoints. Last-ADMIN guard: system prevents removal of the last ADMIN role (409 Conflict). Password policy: min 12 chars, 3-of-4 character classes, no username match. Brute-force protection: 5 failed attempts -> 15 min lockout. Token revocation: `token_revoked_before` column on users, checked in `JwtAuthenticationFilter`, set on password change.
- OIDC: Optional external identity provider support (token exchange pattern). Configured via admin API/UI, stored in database (`server_config` table). Resource server mode: accepts external access tokens (Logto M2M) via JWKS validation when `CAMELEER_SERVER_SECURITY_OIDCISSUERURI` is set. Scope-based role mapping via `SystemRole.normalizeScope()`. System roles synced on every OIDC login via `applyClaimMappings()` in `OidcAuthController` (calls `clearManagedAssignments` + `assignManagedRole` on `RbacService`) — always overwrites managed role assignments; uses managed assignment origin to avoid touching group-inherited or directly-assigned roles. Supports ES384, ES256, RS256.
- OIDC role extraction: `OidcTokenExchanger` reads roles from the **access_token** first (JWT with `at+jwt` type), then falls back to id_token. `OidcConfig` includes `audience` (RFC 8707 resource indicator) and `additionalScopes`. All provider-specific configuration is external — no provider-specific code in the server.
- Sensitive keys: Global enforced baseline for masking sensitive data in agent payloads. Merge rule: `final = global UNION per-app` (case-insensitive dedup, per-app can only add, never remove global keys).
- User persistence: PostgreSQL `users` table, admin CRUD at `/api/v1/admin/users`
- Usage analytics: ClickHouse `usage_events` table tracks authenticated UI requests, flushed every 5s
## CI/CD & Deployment
## Database Migrations
- CI workflow: `.gitea/workflows/ci.yml` — build → docker → deploy on push to main or feature branches
- Build step skips integration tests (`-DskipITs`) — Testcontainers needs Docker daemon
- Docker: multi-stage build (`Dockerfile`), `$BUILDPLATFORM` for native Maven on ARM64 runner, amd64 runtime
- `REGISTRY_TOKEN` build arg required for `cameleer3-common` dependency resolution
- Registry: `gitea.siegeln.net/cameleer/cameleer3-server` (container images)
- K8s manifests in `deploy/` — Kustomize base + overlays (main/feature), shared infra (PostgreSQL, OpenSearch, Authentik) as top-level manifests
- Deployment target: k3s at 192.168.50.86, namespace `cameleer` (main), `cam-<slug>` (feature branches)
- Feature branches: isolated namespace, PG schema, OpenSearch index prefix; Traefik Ingress at `<slug>-api.cameleer.siegeln.net`
- Secrets managed in CI deploy step (idempotent `--dry-run=client | kubectl apply`): `cameleer-auth`, `postgres-credentials`, `opensearch-credentials`
- K8s probes: server uses `/api/v1/health`, PostgreSQL uses `pg_isready`, OpenSearch uses `/_cluster/health`
- Docker build uses buildx registry cache + `--provenance=false` for Gitea compatibility
PostgreSQL (Flyway): `cameleer-server-app/src/main/resources/db/migration/`
- V1 — RBAC (users, roles, groups, audit_log)
- V2 — Claim mappings (OIDC)
- V3 — Runtime management (apps, environments, deployments, app_versions)
- V4 — Environment config (default_container_config JSONB)
- V5 — App container config (container_config JSONB on apps)
- V6 — JAR retention policy (jar_retention_count on environments)
- V7 — Deployment orchestration (target_state, deployment_strategy, replica_states JSONB, deploy_stage)
- V8 — Deployment active config (resolved_config JSONB on deployments)
- V9 — Password hardening (failed_login_attempts, locked_until, token_revoked_before on users)
- V10 — Runtime type detection (detected_runtime_type, detected_main_class on app_versions)
ClickHouse: `cameleer-server-app/src/main/resources/clickhouse/init.sql` (run idempotently on startup)
## Maintaining .claude/rules/
When adding, removing, or renaming classes, controllers, endpoints, UI components, or metrics, update the corresponding `.claude/rules/` file as part of the same change. The rule files are the class/API map that future sessions rely on — stale rules cause wrong assumptions. Treat rule file updates like updating an import: part of the change, not a separate task.
## Disabled Skills
- Do NOT use any `gsd:*` skills in this project. This includes all `/gsd:` prefixed commands.
<!-- gitnexus:start -->
# GitNexus — Code Intelligence
This project is indexed by GitNexus as **cameleer-server** (6281 symbols, 15871 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.
## Always Do
- **MUST run impact analysis before editing any symbol.** Before modifying a function, class, or method, run `gitnexus_impact({target: "symbolName", direction: "upstream"})` and report the blast radius (direct callers, affected processes, risk level) to the user.
- **MUST run `gitnexus_detect_changes()` before committing** to verify your changes only affect expected symbols and execution flows.
- **MUST warn the user** if impact analysis returns HIGH or CRITICAL risk before proceeding with edits.
- When exploring unfamiliar code, use `gitnexus_query({query: "concept"})` to find execution flows instead of grepping. It returns process-grouped results ranked by relevance.
- When you need full context on a specific symbol — callers, callees, which execution flows it participates in — use `gitnexus_context({name: "symbolName"})`.
## When Debugging
1. `gitnexus_query({query: "<error or symptom>"})` — find execution flows related to the issue
2. `gitnexus_context({name: "<suspect function>"})` — see all callers, callees, and process participation
3. `READ gitnexus://repo/cameleer-server/process/{processName}` — trace the full execution flow step by step
4. For regressions: `gitnexus_detect_changes({scope: "compare", base_ref: "main"})` — see what your branch changed
## When Refactoring
- **Renaming**: MUST use `gitnexus_rename({symbol_name: "old", new_name: "new", dry_run: true})` first. Review the preview — graph edits are safe, text_search edits need manual review. Then run with `dry_run: false`.
- **Extracting/Splitting**: MUST run `gitnexus_context({name: "target"})` to see all incoming/outgoing refs, then `gitnexus_impact({target: "target", direction: "upstream"})` to find all external callers before moving code.
- After any refactor: run `gitnexus_detect_changes({scope: "all"})` to verify only expected files changed.
## Never Do
- NEVER edit a function, class, or method without first running `gitnexus_impact` on it.
- NEVER ignore HIGH or CRITICAL risk warnings from impact analysis.
- NEVER rename symbols with find-and-replace — use `gitnexus_rename` which understands the call graph.
- NEVER commit changes without running `gitnexus_detect_changes()` to check affected scope.
## Tools Quick Reference
| Tool | When to use | Command |
|------|-------------|---------|
| `query` | Find code by concept | `gitnexus_query({query: "auth validation"})` |
| `context` | 360-degree view of one symbol | `gitnexus_context({name: "validateUser"})` |
| `impact` | Blast radius before editing | `gitnexus_impact({target: "X", direction: "upstream"})` |
| `detect_changes` | Pre-commit scope check | `gitnexus_detect_changes({scope: "staged"})` |
| `rename` | Safe multi-file rename | `gitnexus_rename({symbol_name: "old", new_name: "new", dry_run: true})` |
| `cypher` | Custom graph queries | `gitnexus_cypher({query: "MATCH ..."})` |
## Impact Risk Levels
| Depth | Meaning | Action |
|-------|---------|--------|
| d=1 | WILL BREAK — direct callers/importers | MUST update these |
| d=2 | LIKELY AFFECTED — indirect deps | Should test |
| d=3 | MAY NEED TESTING — transitive | Test if critical path |
## Resources
| Resource | Use for |
|----------|---------|
| `gitnexus://repo/cameleer-server/context` | Codebase overview, check index freshness |
| `gitnexus://repo/cameleer-server/clusters` | All functional areas |
| `gitnexus://repo/cameleer-server/processes` | All execution flows |
| `gitnexus://repo/cameleer-server/process/{name}` | Step-by-step execution trace |
## Self-Check Before Finishing
Before completing any code modification task, verify:
1. `gitnexus_impact` was run for all modified symbols
2. No HIGH/CRITICAL risk warnings were ignored
3. `gitnexus_detect_changes()` confirms changes match expected scope
4. All d=1 (WILL BREAK) dependents were updated
## Keeping the Index Fresh
After committing code changes, the GitNexus index becomes stale. Re-run analyze to update it:
```bash
npx gitnexus analyze
```
If the index previously included embeddings, preserve them by adding `--embeddings`:
```bash
npx gitnexus analyze --embeddings
```
To check whether embeddings exist, inspect `.gitnexus/meta.json` — the `stats.embeddings` field shows the count (0 means no embeddings). **Running analyze without `--embeddings` will delete any previously generated embeddings.**
> Claude Code users: A PostToolUse hook handles this automatically after `git commit` and `git merge`.
## CLI
| Task | Read this skill file |
|------|---------------------|
| Understand architecture / "How does X work?" | `.claude/skills/gitnexus/gitnexus-exploring/SKILL.md` |
| Blast radius / "What breaks if I change X?" | `.claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md` |
| Trace bugs / "Why is X failing?" | `.claude/skills/gitnexus/gitnexus-debugging/SKILL.md` |
| Rename / extract / split / refactor | `.claude/skills/gitnexus/gitnexus-refactoring/SKILL.md` |
| Tools, resources, schema reference | `.claude/skills/gitnexus/gitnexus-guide/SKILL.md` |
| Index, status, clean, wiki CLI commands | `.claude/skills/gitnexus/gitnexus-cli/SKILL.md` |
<!-- gitnexus:end -->

View File

@@ -1,27 +1,25 @@
FROM --platform=$BUILDPLATFORM maven:3.9-eclipse-temurin-17 AS build
WORKDIR /build
# Configure Gitea Maven Registry for cameleer3-common dependency
# Configure Gitea Maven Registry for cameleer-common dependency
ARG REGISTRY_TOKEN
RUN mkdir -p ~/.m2 && \
echo '<settings><servers><server><id>gitea</id><username>cameleer</username><password>'${REGISTRY_TOKEN}'</password></server></servers></settings>' > ~/.m2/settings.xml
COPY pom.xml .
COPY cameleer3-server-core/pom.xml cameleer3-server-core/
COPY cameleer3-server-app/pom.xml cameleer3-server-app/
COPY cameleer-server-core/pom.xml cameleer-server-core/
COPY cameleer-server-app/pom.xml cameleer-server-app/
# Cache deps — only re-downloaded when POMs change
RUN mvn dependency:go-offline -B || true
COPY . .
RUN mvn clean package -DskipTests -B
RUN mvn clean package -DskipTests -U -B
FROM eclipse-temurin:17-jre
WORKDIR /app
COPY --from=build /build/cameleer3-server-app/target/cameleer3-server-app-*.jar /app/server.jar
ENV SPRING_DATASOURCE_URL=jdbc:postgresql://postgres:5432/cameleer3
ENV SPRING_DATASOURCE_USERNAME=cameleer
ENV SPRING_DATASOURCE_PASSWORD=cameleer_dev
ENV OPENSEARCH_URL=http://opensearch:9200
COPY --from=build /build/cameleer-server-app/target/cameleer-server-app-*.jar /app/server.jar
COPY docker-entrypoint.sh /app/
RUN chmod +x /app/docker-entrypoint.sh
EXPOSE 8081
ENTRYPOINT exec java -jar /app/server.jar
ENV TZ=UTC
ENTRYPOINT ["/app/docker-entrypoint.sh"]

259
HOWTO.md
View File

@@ -1,4 +1,4 @@
# HOWTO — Cameleer3 Server
# HOWTO — Cameleer Server
## Prerequisites
@@ -6,7 +6,7 @@
- Maven 3.9+
- Node.js 22+ and npm
- Docker & Docker Compose
- Access to the Gitea Maven registry (for `cameleer3-common` dependency)
- Access to the Gitea Maven registry (for `cameleer-common` dependency)
## Build
@@ -21,31 +21,36 @@ mvn clean verify # compile + run all tests (needs Docker for integrati
## Infrastructure Setup
Start PostgreSQL and OpenSearch:
Start PostgreSQL:
```bash
docker compose up -d
```
This starts TimescaleDB (PostgreSQL 16) and OpenSearch 2.19. The database schema is applied automatically via Flyway migrations on server startup.
This starts PostgreSQL 16. The database schema is applied automatically via Flyway migrations on server startup. ClickHouse tables are created by the schema initializer on startup.
| Service | Port | Purpose |
|------------|------|----------------------|
| PostgreSQL | 5432 | JDBC (Spring JDBC) |
| OpenSearch | 9200 | REST API (full-text) |
PostgreSQL credentials: `cameleer` / `cameleer_dev`, database `cameleer3`.
PostgreSQL credentials: `cameleer` / `cameleer_dev`, database `cameleer`.
## Run the Server
```bash
mvn clean package -DskipTests
CAMELEER_AUTH_TOKEN=my-secret-token java -jar cameleer3-server-app/target/cameleer3-server-app-1.0-SNAPSHOT.jar
SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/cameleer \
SPRING_DATASOURCE_USERNAME=cameleer \
SPRING_DATASOURCE_PASSWORD=cameleer_dev \
CAMELEER_SERVER_SECURITY_BOOTSTRAPTOKEN=my-secret-token \
java -jar cameleer-server-app/target/cameleer-server-app-1.0-SNAPSHOT.jar
```
The server starts on **port 8081**. The `CAMELEER_AUTH_TOKEN` environment variable is **required** — the server fails fast on startup if it is not set.
> **Note:** The Docker image no longer includes default database credentials. When running via `docker run`, pass `-e SPRING_DATASOURCE_URL=...` etc. The docker-compose setup provides these automatically.
For token rotation without downtime, set `CAMELEER_AUTH_TOKEN_PREVIOUS` to the old token while rolling out the new one. The server accepts both during the overlap window.
The server starts on **port 8081**. The `CAMELEER_SERVER_SECURITY_BOOTSTRAPTOKEN` environment variable is **required** — the server fails fast on startup if it is not set.
For token rotation without downtime, set `CAMELEER_SERVER_SECURITY_BOOTSTRAPTOKENPREVIOUS` to the old token while rolling out the new one. The server accepts both during the overlap window.
## API Endpoints
@@ -84,7 +89,7 @@ curl -s -X POST http://localhost:8081/api/v1/auth/refresh \
-d '{"refreshToken":"<refreshToken>"}'
```
UI credentials are configured via `CAMELEER_UI_USER` / `CAMELEER_UI_PASSWORD` env vars (default: `admin` / `admin`).
UI credentials are configured via `CAMELEER_SERVER_SECURITY_UIUSER` / `CAMELEER_SERVER_SECURITY_UIPASSWORD` env vars (default: `admin` / `admin`).
**Public endpoints (no JWT required):** `GET /api/v1/health`, `POST /api/v1/agents/register` (uses bootstrap token), `POST /api/v1/auth/**`, OpenAPI/Swagger docs.
@@ -100,13 +105,15 @@ JWTs carry a `roles` claim. Endpoints are restricted by role:
| Role | Access |
|------|--------|
| `AGENT` | Data ingestion (`/data/**`), heartbeat, SSE events, command ack |
| `VIEWER` | Search, execution detail, diagrams, agent list |
| `OPERATOR` | VIEWER + send commands to agents |
| `ADMIN` | OPERATOR + user management (`/admin/**`) |
| `AGENT` | Data ingestion (`/data/**` — executions, diagrams, metrics, logs), heartbeat, SSE events, command ack |
| `VIEWER` | Search, execution detail, diagrams, agent list, app config (read-only) |
| `OPERATOR` | VIEWER + send commands to agents, route control, replay, edit app config |
| `ADMIN` | OPERATOR + user management, audit log, OIDC config, database admin (`/admin/**`) |
The env-var local user gets `ADMIN` role. Agents get `AGENT` role at registration.
**UI role gating:** The sidebar hides the Admin section for non-ADMIN users. Admin routes (`/admin/*`) redirect to `/` for non-admin. The diagram node toolbar and route control bar are hidden for VIEWER. Config is a main tab (`/config` shows all apps, `/config/:appId` filters to one app with detail panel; sidebar clicks stay on config tab, route clicks resolve to parent app). VIEWER sees read-only, OPERATOR+ can edit.
### OIDC Login (Optional)
OIDC configuration is stored in PostgreSQL and managed via the admin API or UI. The SPA checks if OIDC is available:
@@ -139,7 +146,7 @@ curl -s -X PUT http://localhost:8081/api/v1/admin/oidc \
-H "Authorization: Bearer $TOKEN" \
-d '{
"enabled": true,
"issuerUri": "http://authentik:9000/application/o/cameleer/",
"issuerUri": "http://cameleer-logto:3001/oidc",
"clientId": "your-client-id",
"clientSecret": "your-client-secret",
"rolesClaim": "realm_access.roles",
@@ -155,30 +162,44 @@ curl -s -X DELETE http://localhost:8081/api/v1/admin/oidc \
-H "Authorization: Bearer $TOKEN"
```
**Initial provisioning**: OIDC can also be seeded from `CAMELEER_OIDC_*` env vars on first startup (when DB is empty). After that, the admin API takes over.
**Initial provisioning**: OIDC can also be seeded from `CAMELEER_SERVER_SECURITY_OIDC*` env vars on first startup (when DB is empty). After that, the admin API takes over.
### Authentik Setup (OIDC Provider)
### Logto Setup (OIDC Provider)
Authentik is deployed alongside the Cameleer stack. After first deployment:
Logto is deployed alongside the Cameleer stack. After first deployment:
1. **Initial setup**: Open `http://192.168.50.86:30950/if/flow/initial-setup/` and create the admin account
2. **Create provider**: Admin Interface → Providers → Create → OAuth2/OpenID Provider
- Name: `Cameleer`
- Authorization flow: `default-provider-authorization-explicit-consent`
- Client type: `Confidential`
- Redirect URIs: `http://192.168.50.86:30090/callback` (or your UI URL)
Logto is proxy-aware via `TRUST_PROXY_HEADER=1`. The `LOGTO_ENDPOINT` and `LOGTO_ADMIN_ENDPOINT` secrets define the public-facing URLs that Logto uses for OIDC discovery, issuer URI, and redirect URLs. When behind a reverse proxy (e.g., Traefik), set these to the external URLs (e.g., `https://auth.cameleer.my.domain`). Logto needs its own subdomain — it cannot be path-prefixed under another app.
1. **Initial setup**: Open the Logto admin console (the `LOGTO_ADMIN_ENDPOINT` URL) and create the admin account
2. **Create SPA application**: Applications → Create → Single Page App
- Name: `Cameleer UI`
- Redirect URI: your UI URL + `/oidc/callback`
- Note the **Client ID**
3. **Create API Resource**: API Resources → Create
- Name: `Cameleer Server API`
- Indicator: your API URL (e.g., `https://cameleer.siegeln.net/api`)
- Add permissions: `server:admin`, `server:operator`, `server:viewer`
4. **Create M2M application** (for SaaS platform): Applications → Create → Machine-to-Machine
- Name: `Cameleer SaaS`
- Assign the API Resource created above with `server:admin` scope
- Note the **Client ID** and **Client Secret**
3. **Create application**: Admin Interface → Applications → Create
- Name: `Cameleer`
- Provider: select `Cameleer` (created above)
4. **Configure roles** (optional): Create groups in Authentik and map them to Cameleer roles via the `roles-claim` config. Default claim path is `realm_access.roles`. For Authentik, you may need to customize the OIDC scope to include group claims.
5. **Configure Cameleer**: Use the admin API (`PUT /api/v1/admin/oidc`) or set env vars for initial seeding:
5. **Configure Cameleer OIDC login**: Use the admin API (`PUT /api/v1/admin/oidc`) or the admin UI. OIDC login configuration is stored in the database — no env vars needed for the SPA OIDC flow.
6. **Configure resource server** (for M2M token validation):
```
CAMELEER_OIDC_ENABLED=true
CAMELEER_OIDC_ISSUER=http://authentik:9000/application/o/cameleer/
CAMELEER_OIDC_CLIENT_ID=<client-id-from-step-2>
CAMELEER_OIDC_CLIENT_SECRET=<client-secret-from-step-2>
CAMELEER_SERVER_SECURITY_OIDCISSUERURI=<LOGTO_ENDPOINT>/oidc
CAMELEER_SERVER_SECURITY_OIDCJWKSETURI=http://cameleer-logto:3001/oidc/jwks
CAMELEER_SERVER_SECURITY_OIDCAUDIENCE=<api-resource-indicator-from-step-3>
CAMELEER_SERVER_SECURITY_OIDCTLSSKIPVERIFY=true # optional — skip cert verification for self-signed CAs
```
`OIDCJWKSETURI` is needed when the public issuer URL isn't reachable from inside containers — it fetches JWKS directly from the internal Logto service. `OIDCTLSSKIPVERIFY` disables certificate verification for all OIDC HTTP calls (discovery, token exchange, JWKS); use only when the provider has a self-signed CA.
### SSO Behavior
When OIDC is configured and enabled, the UI automatically redirects to the OIDC provider for silent SSO (`prompt=none`). Users with an active provider session are signed in without seeing a login form. On first login, the provider may show a consent screen (scopes), after which subsequent logins are seamless. If auto-signup is enabled, new users are automatically provisioned with the configured default roles.
- **Bypass SSO**: Navigate to `/login?local` to see the local login form
- **Subpath deployments**: The OIDC redirect_uri respects `BASE_PATH` (e.g., `https://host/server/oidc/callback`)
- **Role sync**: System roles (ADMIN/OPERATOR/VIEWER) are synced from OIDC scopes on every login — revoking a scope in the provider takes effect on next login. Manually assigned group memberships are preserved.
### User Management (ADMIN only)
@@ -220,6 +241,19 @@ curl -s -X POST http://localhost:8081/api/v1/data/metrics \
-H "X-Protocol-Version: 1" \
-H "Authorization: Bearer $TOKEN" \
-d '[{"agentId":"agent-1","metricName":"cpu","value":42.0,"timestamp":"2026-03-11T00:00:00Z","tags":{}}]'
# Post application log entries (raw JSON array — no wrapper)
curl -s -X POST http://localhost:8081/api/v1/data/logs \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '[{
"timestamp": "2026-03-25T10:00:00Z",
"level": "INFO",
"loggerName": "com.acme.MyService",
"message": "Processing order #12345",
"threadName": "main",
"source": "app"
}]'
```
**Note:** The `X-Protocol-Version: 1` header is required on all `/api/v1/data/**` endpoints. Missing or wrong version returns 400.
@@ -311,6 +345,12 @@ curl -s -X POST http://localhost:8081/api/v1/agents/groups/order-service-prod/co
-H "Authorization: Bearer $TOKEN" \
-d '{"type":"deep-trace","payload":{"routeId":"route-1","durationSeconds":60}}'
# Send route control command to agent group (start/stop/suspend/resume)
curl -s -X POST http://localhost:8081/api/v1/agents/groups/order-service-prod/commands \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"type":"route-control","payload":{"routeId":"route-1","action":"stop","nonce":"unique-uuid"}}'
# Broadcast command to all live agents
curl -s -X POST http://localhost:8081/api/v1/agents/commands \
-H "Content-Type: application/json" \
@@ -324,43 +364,98 @@ curl -s -X POST http://localhost:8081/api/v1/agents/agent-1/commands/{commandId}
**Agent lifecycle:** LIVE (heartbeat within 90s) → STALE (missed 3 heartbeats) → DEAD (5min after STALE). DEAD agents kept indefinitely.
**SSE events:** `config-update`, `deep-trace`, `replay` commands pushed in real time. Server sends ping keepalive every 15s.
**Server restart resilience:** The agent registry is in-memory and lost on server restart. Agents auto-re-register on their next heartbeat or SSE connection — the server reconstructs registry entries from JWT claims (subject, application). Route catalog uses ClickHouse execution data as fallback until agents re-register with full route IDs. Agents should also handle 404 on heartbeat by triggering a full re-registration.
**SSE events:** `config-update`, `deep-trace`, `replay`, `route-control` commands pushed in real time. Server sends ping keepalive every 15s.
**Command expiry:** Unacknowledged commands expire after 60 seconds.
**Route control responses:** Route control commands return `CommandGroupResponse` with per-agent status, response count, and timed-out agent IDs.
### Backpressure
When the write buffer is full (default capacity: 50,000), ingestion endpoints return **503 Service Unavailable**. Already-buffered data is not lost.
## Configuration
Key settings in `cameleer3-server-app/src/main/resources/application.yml`:
Key settings in `cameleer-server-app/src/main/resources/application.yml`. All custom properties live under `cameleer.server.*`. Env vars are a mechanical 1:1 mapping (dots to underscores, uppercase).
| Setting | Default | Description |
|---------|---------|-------------|
| `server.port` | 8081 | Server port |
| `ingestion.buffer-capacity` | 50000 | Max items in write buffer |
| `ingestion.batch-size` | 5000 | Items per batch insert |
| `ingestion.flush-interval-ms` | 1000 | Buffer flush interval (ms) |
| `agent-registry.heartbeat-interval-seconds` | 30 | Expected heartbeat interval |
| `agent-registry.stale-threshold-seconds` | 90 | Time before agent marked STALE |
| `agent-registry.dead-threshold-seconds` | 300 | Time after STALE before DEAD |
| `agent-registry.command-expiry-seconds` | 60 | Pending command TTL |
| `agent-registry.keepalive-interval-seconds` | 15 | SSE ping keepalive interval |
| `security.access-token-expiry-ms` | 3600000 | JWT access token lifetime (1h) |
| `security.refresh-token-expiry-ms` | 604800000 | Refresh token lifetime (7d) |
| `security.bootstrap-token` | `${CAMELEER_AUTH_TOKEN}` | Bootstrap token for agent registration (required) |
| `security.bootstrap-token-previous` | `${CAMELEER_AUTH_TOKEN_PREVIOUS}` | Previous bootstrap token for rotation (optional) |
| `security.ui-user` | `admin` | UI login username (`CAMELEER_UI_USER` env var) |
| `security.ui-password` | `admin` | UI login password (`CAMELEER_UI_PASSWORD` env var) |
| `security.ui-origin` | `http://localhost:5173` | CORS allowed origin for UI (`CAMELEER_UI_ORIGIN` env var) |
| `security.jwt-secret` | *(random)* | HMAC secret for JWT signing (`CAMELEER_JWT_SECRET`). If set, tokens survive restarts |
| `security.oidc.enabled` | `false` | Enable OIDC login (`CAMELEER_OIDC_ENABLED`) |
| `security.oidc.issuer-uri` | | OIDC provider issuer URL (`CAMELEER_OIDC_ISSUER`) |
| `security.oidc.client-id` | | OAuth2 client ID (`CAMELEER_OIDC_CLIENT_ID`) |
| `security.oidc.client-secret` | | OAuth2 client secret (`CAMELEER_OIDC_CLIENT_SECRET`) |
| `security.oidc.roles-claim` | `realm_access.roles` | JSONPath to roles in OIDC id_token (`CAMELEER_OIDC_ROLES_CLAIM`) |
| `security.oidc.default-roles` | `VIEWER` | Default roles for new OIDC users (`CAMELEER_OIDC_DEFAULT_ROLES`) |
**Security** (`cameleer.server.security.*`):
| Setting | Default | Env var | Description |
|---------|---------|---------|-------------|
| `cameleer.server.security.bootstraptoken` | *(required)* | `CAMELEER_SERVER_SECURITY_BOOTSTRAPTOKEN` | Bootstrap token for agent registration |
| `cameleer.server.security.bootstraptokenprevious` | *(empty)* | `CAMELEER_SERVER_SECURITY_BOOTSTRAPTOKENPREVIOUS` | Previous bootstrap token for rotation |
| `cameleer.server.security.uiuser` | `admin` | `CAMELEER_SERVER_SECURITY_UIUSER` | UI login username |
| `cameleer.server.security.uipassword` | `admin` | `CAMELEER_SERVER_SECURITY_UIPASSWORD` | UI login password |
| `cameleer.server.security.uiorigin` | `http://localhost:5173` | `CAMELEER_SERVER_SECURITY_UIORIGIN` | CORS allowed origin for UI |
| `cameleer.server.security.corsallowedorigins` | *(empty)* | `CAMELEER_SERVER_SECURITY_CORSALLOWEDORIGINS` | Comma-separated CORS origins — overrides `uiorigin` when set |
| `cameleer.server.security.jwtsecret` | *(random)* | `CAMELEER_SERVER_SECURITY_JWTSECRET` | HMAC secret for JWT signing. If set, tokens survive restarts |
| `cameleer.server.security.accesstokenexpiryms` | `3600000` | `CAMELEER_SERVER_SECURITY_ACCESSTOKENEXPIRYMS` | JWT access token lifetime (1h) |
| `cameleer.server.security.refreshtokenexpiryms` | `604800000` | `CAMELEER_SERVER_SECURITY_REFRESHTOKENEXPIRYMS` | Refresh token lifetime (7d) |
| `cameleer.server.security.infrastructureendpoints` | `true` | `CAMELEER_SERVER_SECURITY_INFRASTRUCTUREENDPOINTS` | Show DB/ClickHouse admin endpoints. Set `false` in SaaS-managed mode |
**OIDC resource server** (`cameleer.server.security.oidc.*`):
| Setting | Default | Env var | Description |
|---------|---------|---------|-------------|
| `cameleer.server.security.oidc.issueruri` | *(empty)* | `CAMELEER_SERVER_SECURITY_OIDC_ISSUERURI` | OIDC issuer URI — enables resource server mode |
| `cameleer.server.security.oidc.jwkseturi` | *(empty)* | `CAMELEER_SERVER_SECURITY_OIDC_JWKSETURI` | Direct JWKS URL — bypasses OIDC discovery |
| `cameleer.server.security.oidc.audience` | *(empty)* | `CAMELEER_SERVER_SECURITY_OIDC_AUDIENCE` | Expected JWT audience |
| `cameleer.server.security.oidc.tlsskipverify` | `false` | `CAMELEER_SERVER_SECURITY_OIDC_TLSSKIPVERIFY` | Skip TLS cert verification for OIDC calls |
**Note:** OIDC *login* configuration (issuer, client ID, client secret, roles claim, default roles) is stored in the database and managed via the admin API (`PUT /api/v1/admin/oidc`) or admin UI. The env vars above are for resource server mode (M2M token validation) only.
**Ingestion** (`cameleer.server.ingestion.*`):
| Setting | Default | Env var | Description |
|---------|---------|---------|-------------|
| `cameleer.server.ingestion.buffercapacity` | `50000` | `CAMELEER_SERVER_INGESTION_BUFFERCAPACITY` | Max items in write buffer |
| `cameleer.server.ingestion.batchsize` | `5000` | `CAMELEER_SERVER_INGESTION_BATCHSIZE` | Items per batch insert |
| `cameleer.server.ingestion.flushintervalms` | `5000` | `CAMELEER_SERVER_INGESTION_FLUSHINTERVALMS` | Buffer flush interval (ms) |
| `cameleer.server.ingestion.bodysizelimit` | `16384` | `CAMELEER_SERVER_INGESTION_BODYSIZELIMIT` | Max body size per execution (bytes) |
**Agent registry** (`cameleer.server.agentregistry.*`):
| Setting | Default | Env var | Description |
|---------|---------|---------|-------------|
| `cameleer.server.agentregistry.heartbeatintervalms` | `30000` | `CAMELEER_SERVER_AGENTREGISTRY_HEARTBEATINTERVALMS` | Expected heartbeat interval (ms) |
| `cameleer.server.agentregistry.stalethresholdms` | `90000` | `CAMELEER_SERVER_AGENTREGISTRY_STALETHRESHOLDMS` | Time before agent marked STALE (ms) |
| `cameleer.server.agentregistry.deadthresholdms` | `300000` | `CAMELEER_SERVER_AGENTREGISTRY_DEADTHRESHOLDMS` | Time after STALE before DEAD (ms) |
| `cameleer.server.agentregistry.pingintervalms` | `15000` | `CAMELEER_SERVER_AGENTREGISTRY_PINGINTERVALMS` | SSE ping keepalive interval (ms) |
| `cameleer.server.agentregistry.commandexpiryms` | `60000` | `CAMELEER_SERVER_AGENTREGISTRY_COMMANDEXPIRYMS` | Pending command TTL (ms) |
| `cameleer.server.agentregistry.lifecyclecheckintervalms` | `10000` | `CAMELEER_SERVER_AGENTREGISTRY_LIFECYCLECHECKINTERVALMS` | Lifecycle monitor interval (ms) |
**Runtime** (`cameleer.server.runtime.*`):
| Setting | Default | Env var | Description |
|---------|---------|---------|-------------|
| `cameleer.server.runtime.enabled` | `true` | `CAMELEER_SERVER_RUNTIME_ENABLED` | Enable Docker orchestration |
| `cameleer.server.runtime.baseimage` | `cameleer-runtime-base:latest` | `CAMELEER_SERVER_RUNTIME_BASEIMAGE` | Base Docker image for app containers |
| `cameleer.server.runtime.dockernetwork` | `cameleer` | `CAMELEER_SERVER_RUNTIME_DOCKERNETWORK` | Primary Docker network |
| `cameleer.server.runtime.jarstoragepath` | `/data/jars` | `CAMELEER_SERVER_RUNTIME_JARSTORAGEPATH` | JAR file storage directory |
| `cameleer.server.runtime.jardockervolume` | *(empty)* | `CAMELEER_SERVER_RUNTIME_JARDOCKERVOLUME` | Docker volume for JAR sharing |
| `cameleer.server.runtime.routingmode` | `path` | `CAMELEER_SERVER_RUNTIME_ROUTINGMODE` | `path` or `subdomain` Traefik routing |
| `cameleer.server.runtime.routingdomain` | `localhost` | `CAMELEER_SERVER_RUNTIME_ROUTINGDOMAIN` | Domain for Traefik routing labels |
| `cameleer.server.runtime.serverurl` | *(empty)* | `CAMELEER_SERVER_RUNTIME_SERVERURL` | Server URL injected into app containers |
| `cameleer.server.runtime.agenthealthport` | `9464` | `CAMELEER_SERVER_RUNTIME_AGENTHEALTHPORT` | Agent health check port |
| `cameleer.server.runtime.healthchecktimeout` | `60` | `CAMELEER_SERVER_RUNTIME_HEALTHCHECKTIMEOUT` | Health check timeout (seconds) |
| `cameleer.server.runtime.container.memorylimit` | `512m` | `CAMELEER_SERVER_RUNTIME_CONTAINER_MEMORYLIMIT` | Default memory limit for app containers |
| `cameleer.server.runtime.container.cpushares` | `512` | `CAMELEER_SERVER_RUNTIME_CONTAINER_CPUSHARES` | Default CPU shares for app containers |
**Other** (`cameleer.server.*`):
| Setting | Default | Env var | Description |
|---------|---------|---------|-------------|
| `cameleer.server.catalog.discoveryttldays` | `7` | `CAMELEER_SERVER_CATALOG_DISCOVERYTTLDAYS` | Days before stale discovered apps auto-hide from sidebar |
| `cameleer.server.tenant.id` | `default` | `CAMELEER_SERVER_TENANT_ID` | Tenant identifier |
| `cameleer.server.indexer.debouncems` | `2000` | `CAMELEER_SERVER_INDEXER_DEBOUNCEMS` | Search indexer debounce delay (ms) |
| `cameleer.server.indexer.queuesize` | `10000` | `CAMELEER_SERVER_INDEXER_QUEUESIZE` | Search indexer queue capacity |
| `cameleer.server.license.token` | *(empty)* | `CAMELEER_SERVER_LICENSE_TOKEN` | License token |
| `cameleer.server.license.publickey` | *(empty)* | `CAMELEER_SERVER_LICENSE_PUBLICKEY` | License verification public key |
| `cameleer.server.clickhouse.url` | `jdbc:clickhouse://localhost:8123/cameleer` | `CAMELEER_SERVER_CLICKHOUSE_URL` | ClickHouse JDBC URL |
| `cameleer.server.clickhouse.username` | `default` | `CAMELEER_SERVER_CLICKHOUSE_USERNAME` | ClickHouse user |
| `cameleer.server.clickhouse.password` | *(empty)* | `CAMELEER_SERVER_CLICKHOUSE_PASSWORD` | ClickHouse password |
## Web UI Development
@@ -371,7 +466,7 @@ npm run dev # Vite dev server on http://localhost:5173 (proxies /api to
npm run build # Production build to ui/dist/
```
Login with `admin` / `admin` (or whatever `CAMELEER_UI_USER` / `CAMELEER_UI_PASSWORD` are set to).
Login with `admin` / `admin` (or whatever `CAMELEER_SERVER_SECURITY_UIUSER` / `CAMELEER_SERVER_SECURITY_UIPASSWORD` are set to).
The UI uses runtime configuration via `public/config.js`. In Kubernetes, a ConfigMap overrides this file to set the correct API base URL.
@@ -385,17 +480,17 @@ npm run generate-api # Requires backend running on :8081
## Running Tests
Integration tests use Testcontainers (starts PostgreSQL and OpenSearch automatically — requires Docker):
Integration tests use Testcontainers (starts PostgreSQL automatically — requires Docker):
```bash
# All tests
mvn verify
# Unit tests only (no Docker needed)
mvn test -pl cameleer3-server-core
mvn test -pl cameleer-server-core
# Specific integration test
mvn test -pl cameleer3-server-app -Dtest=ExecutionControllerIT
mvn test -pl cameleer-server-app -Dtest=ExecutionControllerIT
```
## Verify Database Data
@@ -403,7 +498,7 @@ mvn test -pl cameleer3-server-app -Dtest=ExecutionControllerIT
After posting data and waiting for the flush interval (1s default):
```bash
docker exec -it cameleer3-server-postgres-1 psql -U cameleer -d cameleer3 \
docker exec -it cameleer-server-postgres-1 psql -U cameleer -d cameleer \
-c "SELECT count(*) FROM route_executions"
```
@@ -415,14 +510,16 @@ The full stack is deployed to k3s via CI/CD on push to `main`. K8s manifests are
```
cameleer namespace:
PostgreSQL (StatefulSet, 10Gi PVC) ← postgres:5432 (ClusterIP)
OpenSearch (StatefulSet, 10Gi PVC) ← opensearch:9200 (ClusterIP)
cameleer3-server (Deployment) ← NodePort 30081
cameleer3-ui (Deployment, Nginx) ← NodePort 30090
Authentik Server (Deployment) ← NodePort 30950
Authentik Worker (Deployment)
Authentik PostgreSQL (StatefulSet, 1Gi) ← ClusterIP
Authentik Redis (Deployment) ← ClusterIP
PostgreSQL (StatefulSet, 10Gi PVC) ← cameleer-postgres:5432 (ClusterIP)
ClickHouse (StatefulSet, 10Gi PVC) ← cameleer-clickhouse:8123 (ClusterIP)
cameleer-server (Deployment) ← NodePort 30081
cameleer-ui (Deployment, Nginx) ← NodePort 30090
cameleer-deploy-demo (Deployment) ← NodePort 30092
Logto Server (Deployment) ← NodePort 30951/30952
Logto PostgreSQL (StatefulSet, 1Gi) ← ClusterIP
cameleer-demo namespace:
(deployed Camel applications — managed by cameleer-deploy-demo)
```
### Access (from your network)
@@ -432,13 +529,15 @@ cameleer namespace:
| Web UI | `http://192.168.50.86:30090` |
| Server API | `http://192.168.50.86:30081/api/v1/health` |
| Swagger UI | `http://192.168.50.86:30081/api/v1/swagger-ui.html` |
| Authentik | `http://192.168.50.86:30950` |
| Deploy Demo | `http://192.168.50.86:30092` |
| Logto API | `LOGTO_ENDPOINT` secret (NodePort 30951 direct, or behind reverse proxy) |
| Logto Admin | `LOGTO_ADMIN_ENDPOINT` secret (NodePort 30952 direct, or behind reverse proxy) |
### CI/CD Pipeline
Push to `main` triggers: **build** (UI npm + Maven, unit tests) → **docker** (buildx amd64 for server + UI, push to Gitea registry) → **deploy** (kubectl apply + rolling update).
Required Gitea org secrets: `REGISTRY_TOKEN`, `KUBECONFIG_BASE64`, `CAMELEER_AUTH_TOKEN`, `CAMELEER_JWT_SECRET`, `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB`, `OPENSEARCH_USER`, `OPENSEARCH_PASSWORD`, `CAMELEER_UI_USER` (optional), `CAMELEER_UI_PASSWORD` (optional), `AUTHENTIK_PG_USER`, `AUTHENTIK_PG_PASSWORD`, `AUTHENTIK_SECRET_KEY`, `CAMELEER_OIDC_ENABLED`, `CAMELEER_OIDC_ISSUER`, `CAMELEER_OIDC_CLIENT_ID`, `CAMELEER_OIDC_CLIENT_SECRET`.
Required Gitea org secrets: `REGISTRY_TOKEN`, `KUBECONFIG_BASE64`, `CAMELEER_SERVER_SECURITY_BOOTSTRAPTOKEN`, `CAMELEER_SERVER_SECURITY_JWTSECRET`, `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB`, `CLICKHOUSE_USER`, `CLICKHOUSE_PASSWORD`, `CAMELEER_SERVER_SECURITY_UIUSER` (optional), `CAMELEER_SERVER_SECURITY_UIPASSWORD` (optional), `LOGTO_PG_USER`, `LOGTO_PG_PASSWORD`, `LOGTO_ENDPOINT` (public-facing Logto URL, e.g., `https://auth.cameleer.my.domain`), `LOGTO_ADMIN_ENDPOINT` (admin console URL), `CAMELEER_SERVER_SECURITY_OIDCISSUERURI` (optional, for resource server M2M token validation), `CAMELEER_SERVER_SECURITY_OIDCAUDIENCE` (optional, API resource indicator), `CAMELEER_SERVER_SECURITY_OIDCTLSSKIPVERIFY` (optional, skip TLS cert verification for self-signed CAs).
### Manual K8s Commands
@@ -447,14 +546,14 @@ Required Gitea org secrets: `REGISTRY_TOKEN`, `KUBECONFIG_BASE64`, `CAMELEER_AUT
kubectl -n cameleer get pods
# View server logs
kubectl -n cameleer logs -f deploy/cameleer3-server
kubectl -n cameleer logs -f deploy/cameleer-server
# View PostgreSQL logs
kubectl -n cameleer logs -f statefulset/postgres
kubectl -n cameleer logs -f statefulset/cameleer-postgres
# View OpenSearch logs
kubectl -n cameleer logs -f statefulset/opensearch
# View ClickHouse logs
kubectl -n cameleer logs -f statefulset/cameleer-clickhouse
# Restart server
kubectl -n cameleer rollout restart deployment/cameleer3-server
kubectl -n cameleer rollout restart deployment/cameleer-server
```

259
UI-CONSISTENCY-AUDIT.md Normal file
View File

@@ -0,0 +1,259 @@
> **Status: RESOLVED** — All phases (1-5) executed on 2026-04-09. Remaining: responsive design (separate initiative).
# UI Consistency Audit — cameleer-server
**Date:** 2026-04-09
**Scope:** All files under `ui/src/` (26 CSS modules, ~45 TSX components, ~15 pages)
**Verdict:** ~55% design system adoption for interactive UI. Significant duplication and inline style debt.
---
## Executive Summary
| Dimension | Score | Key Issue |
|-----------|-------|-----------|
| Design system component adoption | 55% | 32 raw `<button>`, 12 raw `<select>`, 8 raw `<input>` should use DS |
| Color consistency | Poor | ~140 violations: 45 hardcoded hex in TSX, 13 naked hex in CSS, ~55 fallback hex in `var()` |
| Inline styles | Poor | 55 RED (static inline styles), 8 YELLOW, 14 GREEN (justified) |
| Layout consistency | Mixed | 3 different page padding values, mixed gap/margin approaches |
| CSS module duplication | 22% | ~135 of 618 classes are copy-pasted across files |
| Responsive design | None | Zero `@media` queries in entire UI |
---
## 1. Critical: Hardcoded Colors (CLAUDE.md violation)
The project rule states: *"Always use `@cameleer/design-system` CSS variables for colors — never hardcode hex values."*
### Worst offenders
| File | Violations | Severity |
|------|-----------|----------|
| `ProcessDiagram/DiagramNode.tsx` | ~20 hex values in SVG fill/stroke | Critical |
| `ExecutionDiagram/ExecutionDiagram.module.css` | 17 naked hex + ~40 hex fallbacks in `var()` | Critical |
| `ProcessDiagram/CompoundNode.tsx` | 8 hex values | Critical |
| `ProcessDiagram/DiagramEdge.tsx` | 3 hex values | High |
| `ProcessDiagram/ConfigBadge.tsx` | 3 hex values | High |
| `ProcessDiagram/ErrorSection.tsx` | 2 hex values | High |
| `ProcessDiagram/NodeToolbar.tsx` | 2 hex values | High |
| `ProcessDiagram/Minimap.tsx` | 3 hex values | High |
| `Dashboard/Dashboard.module.css` | `#5db866` (not even a DS color) | High |
| `AppsTab/AppsTab.module.css` | `var(--accent, #6c7aff)` (undefined DS variable) | Medium |
### Undefined CSS variables (not in design system)
| Variable | Files | Should be |
|----------|-------|-----------|
| `--accent` | EnvironmentSelector, AppsTab | `--amber` (or define in DS) |
| `--bg-base` | LoginPage | `--bg-body` |
| `--surface` | ContentTabs, ExchangeHeader | `--bg-surface` |
| `--bg-surface-raised` | AgentHealth | `--bg-raised` |
### Missing DS tokens needed
Several tint/background colors are used repeatedly but have no DS variable:
- `--error-bg` (used as `#FDF2F0`, `#F9E0DC`)
- `--success-bg` (used as `#F0F9F1`)
- `--amber-bg` / `--warning-bg` (used as `#FFF8F0`)
- `--bg-inverse` / `--text-inverse` (used as `#1A1612` / `#E4DFD8`)
---
## 2. Critical: CSS Module Duplication (~22%)
~135 of 618 class definitions are copy-pasted across files.
### Table section pattern — 5 files, ~35 duplicate classes
`.tableSection`, `.tableHeader`, `.tableTitle`, `.tableMeta`, `.tableRight` are **identical** in:
- `DashboardTab.module.css`
- `AuditLogPage.module.css`
- `ClickHouseAdminPage.module.css`
- `RoutesMetrics.module.css`
- `RouteDetail.module.css`
### Log viewer panel — 2 files, ~50 lines identical
`.logCard`, `.logHeader`, `.logToolbar`, `.logSearchWrap`, `.logSearchInput`, `.logSearchClear`, `.logClearFilters`, `.logEmpty`, `.sortBtn`, `.refreshBtn`, `.headerActions` — byte-for-byte identical in `AgentHealth.module.css` and `AgentInstance.module.css`.
### Tap modal form — 2 files, ~40 lines identical
`.typeSelector`, `.typeOption`, `.typeOptionActive`, `.testSection`, `.testTabs`, `.testTabBtn`, `.testTabBtnActive`, `.testBody`, `.testResult`, `.testSuccess`, `.testError` — identical in `TapConfigModal.module.css` and `RouteDetail.module.css`.
### Other duplicates
| Pattern | Files | Lines |
|---------|-------|-------|
| Rate color classes (`.rateGood/.rateWarn/.rateBad/.rateNeutral`) | DashboardTab, RouteDetail, RoutesMetrics | ~12 each |
| Refresh indicator + `@keyframes pulse` | DashboardTab, RoutesMetrics | ~15 each |
| Chart card (`.chartCard`) | AgentInstance, RouteDetail | ~6 each |
| Section card (`.section`) | AppConfigDetailPage, OidcConfigPage | ~7 each |
| Meta grid (`.metaGrid/.metaLabel/.metaValue`) | AboutMeDialog, UserManagement | ~9 each |
---
## 3. High: Inline Styles (55 RED violations)
### Files with zero CSS modules (all inline)
| File | Issue |
|------|-------|
| `pages/Admin/AdminLayout.tsx` | Entire layout wrapper is inline styled |
| `pages/Admin/DatabaseAdminPage.tsx` | All layout, typography, spacing inline — no CSS module |
| `auth/OidcCallback.tsx` | Full-page layout inline — no CSS module |
### Most inline violations
| File | RED count | Primary patterns |
|------|-----------|-----------------|
| `pages/AppsTab/AppsTab.tsx` | ~25 | Fixed-width inputs (`width: 50-90px` x18), visually-hidden pattern x2, table cell layouts |
| `components/LayoutShell.tsx` | 6 | StarredList sub-component, sidebar layout |
| `pages/Admin/EnvironmentsPage.tsx` | 8 | Raw `<select>` fully styled inline, save/cancel button rows |
| `pages/Routes/RouteDetail.tsx` | 5 | Heading styles, tab panel margins |
### Repeated inline patterns that need extraction
| Pattern | Occurrences | Fix |
|---------|-------------|-----|
| `style={{ display: 'flex', justifyContent: 'center', padding: '4rem' }}` (loading fallback) | 3 files | Create shared `<PageLoader>` |
| `style={{ position: 'absolute', width: 1, height: 1, clip: 'rect(0,0,0,0)' }}` (visually hidden) | 2 in AppsTab | Create `.visuallyHidden` utility class |
| `style={{ width: N }}` on `<Input>`/`<Select>` (fixed widths) | 18+ in AppsTab | Size classes or CSS module rules |
| `style={{ marginTop: 8, display: 'flex', gap: 8, justifyContent: 'flex-end' }}` (action row) | 3+ in EnvironmentsPage | Shared `.editActions` class |
---
## 4. High: Design System Component Adoption Gaps
### Native HTML that should use DS components
| Element | Instances | Files | DS Replacement |
|---------|-----------|-------|---------------|
| `<button>` | 32 | 8 files | `Button`, `SegmentedTabs` |
| `<select>` | 12 | 4 files | `Select` |
| `<input>` | 8 | 4 files | `Input`, `Toggle`, `Checkbox` |
| `<label>` | 9 | 2 files | `FormField`, `Label` |
| `<table>` (data) | 2 | 2 files | `DataTable`, `LogViewer` |
### Highest-priority replacements
1. **`EnvironmentSelector.tsx`** — zero DS imports, entire component is a bare `<select>`. Used globally in sidebar.
2. **`ExecutionDiagram/tabs/LogTab.tsx`** — reimplements LogViewer from scratch (raw table + input + button). AgentInstance and AgentHealth already use DS `LogViewer` correctly.
3. **`AppsTab.tsx` sub-tabs** — 3 instances of homegrown `<button>` tab bars. DS provides `SegmentedTabs` and `Tabs`.
4. **`AppConfigDetailPage.tsx`** — 4x `<select>`, 4x `<label>`, 2x `<input type="checkbox">`, 4x `<button>` — all have DS equivalents already used elsewhere.
5. **`AgentHealth.tsx`** — config bar uses `Toggle` (correct) alongside raw `<select>` and `<button>` (incorrect).
### Cross-page inconsistencies
| Pattern | Correct usage | Incorrect usage |
|---------|--------------|-----------------|
| Log viewer | AgentInstance, AgentHealth use DS `LogViewer` | LogTab rebuilds from scratch |
| Config edit form | Both pages render same 4 fields | AgentHealth uses `Toggle`, AppConfigDetail uses `<input type="checkbox">` |
| Sub-tabs | RbacPage uses DS `Tabs` | AppsTab uses homegrown `<button>` tabs with non-DS `--accent` color |
| Select dropdowns | AppsTab uses DS `Select` for some fields | Same file uses raw `<select>` for other fields |
---
## 5. Medium: Layout Inconsistencies
### Page padding (3 different values)
| Pages | Padding |
|-------|---------|
| AgentHealth, AgentInstance, AdminLayout | `20px 24px 40px` |
| AppsTab | `16px` (all sides) |
| DashboardTab, Dashboard | No padding (full-bleed) |
### Section gap spacing (mixed approaches)
| Approach | Pages |
|----------|-------|
| CSS `gap: 20px` on flex container | DashboardTab, RoutesMetrics |
| `margin-bottom: 20px` | AgentInstance |
| Mixed `margin-bottom: 16px` and `20px` on same page | AgentHealth, ClickHouseAdminPage |
### Typography inconsistencies
| Issue | Details |
|-------|---------|
| Card title weight | Most use `font-weight: 600`, RouteDetail `.paneTitle` uses `700` |
| Chart title style | RouteDetail: `12px/700/uppercase`, AgentHealth: `12px/600/uppercase` |
| Font units | ExchangeHeader + TabKpis use `rem`, everything else uses `px` |
| Raw headings | DatabaseAdminPage uses `<h2>`/`<h3>` with inline styles; all others use DS `SectionHeader` or CSS classes |
| Table header padding | Most: `12px 16px`, Dashboard: `8px 12px`, AgentHealth eventCard: `10px 16px` |
### Stat strip layouts
| Page | Layout | Gap |
|------|--------|-----|
| AgentHealth, AgentInstance, RbacPage | CSS grid `repeat(N, 1fr)` | `10px` |
| ClickHouseAdminPage | Flexbox (unequal widths) | `10px` |
| DatabaseAdminPage | Inline flex | `1rem` (16px) |
### Empty state patterns (4 different approaches)
1. DS `<EmptyState>` component (AgentInstance — correct)
2. `EntityList emptyMessage` prop (EnvironmentsPage, RbacPage)
3. `.logEmpty` CSS class, `12px`, `var(--text-faint)` (AgentHealth, AgentInstance)
4. `.emptyNote` CSS class, `12px`, `italic` (AppsTab)
5. Inline `0.875rem`, `var(--text-muted)` (ExchangesPage)
### Loading state patterns (3 different approaches)
1. `<Spinner size="lg">` in flex div with inline `padding: 4rem` — copy-pasted 3 times
2. `<Spinner size="md">` returned directly, no centering (EnvironmentsPage)
3. No loading UI, data simply absent (DashboardL1/L2/L3)
---
## 6. Low: Other Findings
- **`!important`**: 1 use in `RouteControlBar.module.css` — works around specificity conflict
- **Zero responsive design**: no `@media` queries anywhere
- **Z-index**: only 4 uses, all in diagram components (5 and 10), consistent
- **Naming convention**: all camelCase — consistent, no issues
- **Unused CSS classes**: ~11 likely unused in AppsTab (old create-modal classes) and TapConfigModal
---
## Recommended Fix Order
### Phase 1: Design system tokens (unblocks everything else)
1. Add missing DS variables: `--error-bg`, `--success-bg`, `--amber-bg`, `--bg-inverse`, `--text-inverse`
2. Fix undefined variables: `--accent` -> `--amber`, `--bg-base` -> `--bg-body`, `--surface` -> `--bg-surface`
### Phase 2: Eliminate CSS duplication (~22% of all classes)
3. Extract shared `tableSection` pattern to shared CSS module (saves ~140 duplicate lines across 5 files)
4. Extract shared log viewer CSS to shared module (saves ~50 lines across 2 files)
5. Remove duplicate tap modal CSS from RouteDetail (saves ~40 lines)
6. Extract shared rate/refresh/chart patterns
### Phase 3: Fix hardcoded colors
7. Replace all hex in `ProcessDiagram/*.tsx` SVG components (~45 values)
8. Replace all hex in `ExecutionDiagram.module.css` (~17 naked + strip ~40 fallbacks)
9. Fix remaining CSS hex violations (Dashboard, AppsTab, AgentHealth)
### Phase 4: Replace native HTML with DS components
10. `EnvironmentSelector` -> DS `Select`
11. `LogTab` -> DS `LogViewer`
12. `AppsTab` sub-tabs -> DS `SegmentedTabs`
13. `AppConfigDetailPage` form elements -> DS `Select`/`Toggle`/`FormField`/`Button`
14. Remaining `<button>` -> DS `Button`
### Phase 5: Eliminate inline styles
15. Create CSS modules for AdminLayout, DatabaseAdminPage, OidcCallback
16. Extract shared `<PageLoader>` component
17. Move AppsTab fixed-width inputs to CSS module size classes
18. Move remaining inline margins/flex patterns to CSS classes
### Phase 6: Standardize layout patterns
19. Unify page padding to `20px 24px 40px`
20. Standardize section gaps to `gap: 20px` on flex containers
21. Normalize font units to `px` throughout
22. Standardize empty state to DS `<EmptyState>`
23. Standardize loading state to shared `<PageLoader>`

294
UI_FINDINGS.md Normal file
View File

@@ -0,0 +1,294 @@
# UI/UX Evaluation Report — Cameleer Server
**Date:** 2026-03-25
**Evaluated URL:** http://192.168.50.86:30090/
**Methodology:** Playwright-driven navigation of all major pages (14 screenshots), evaluated by 3 specialist agents: Visual Design, Information Architecture & Usability, Readability & Accessibility.
---
## Executive Summary
The Cameleer dashboard has a **distinctive, well-crafted warm amber design language** that stands out in the observability space. The core monitoring pages (Dashboard, Exchange Detail, Routes, Agents) are polished and consistent. The design system provides a solid foundation.
**Key strengths:** KPI strip pattern, command palette (Ctrl+K), agent card grouping, dark mode token system, cohesive brand identity.
**Critical gaps to address:**
1. **Font sizes too small** — pervasive 10-11px text for critical data impairs reading under stress
2. **Color contrast failures**`--text-muted` and `--text-faint` fail WCAG AA in both themes
3. **Status indicators rely on color alone** — not accessible for color-blind users
4. **Admin infrastructure pages lag in polish** — Database/OpenSearch use ad-hoc styling
5. **Dashboard is a monitoring display, not yet an incident response tool** — missing error highlighting, per-route error breakdowns, actionable status pages
**Overall Score: 7/10** — Strong foundation, needs targeted fixes for production readiness under stress.
---
## Pages Evaluated
| # | Page | Screenshot |
|---|------|-----------|
| 1 | Login | `screenshots/14-login-page.png` |
| 2 | Dashboard (light) | `screenshots/01-dashboard.png` |
| 3 | Dashboard + Detail Panel | `screenshots/02-dashboard-detail-panel.png` |
| 4 | Exchange Detail | `screenshots/03-exchange-detail.png` |
| 5 | Routes Metrics | `screenshots/04-routes-metrics.png` |
| 6 | Agent Health | `screenshots/05-agents.png` |
| 7 | Agent Instance | `screenshots/06-agent-instance.png` |
| 8 | Admin RBAC | `screenshots/07-admin-rbac.png` |
| 9 | Admin Audit Log | `screenshots/08-admin-audit.png` |
| 10 | Admin OIDC | `screenshots/09-admin-oidc.png` |
| 11 | Admin Database | `screenshots/10-admin-database.png` |
| 12 | Admin OpenSearch | `screenshots/11-admin-opensearch.png` |
| 13 | Command Palette | `screenshots/12-command-palette.png` |
| 14 | Dashboard (dark) | `screenshots/13-dashboard-dark-mode.png` |
---
## Page-by-Page Findings
### Login Page
- **[Important]** No brand identity — missing camel logo/icon from sidebar. First impression feels generic.
- **[Important]** Sign-in button color mismatch — uses washed-out gold, not the saturated `--amber` (#C6820E) used throughout the app.
- **[Important]** No SSO/OIDC button visible — system supports OIDC but login page only shows username/password.
- **[Important]** Subtitle text `--text-muted` (#9C9184) on white fails WCAG AA (~2.8:1, needs 4.5:1).
- **[Important]** White text on amber button fails WCAG AA for normal text (~3.2:1).
- **[Nice-to-have]** Card has no shadow/border against the `--bg-body` cream background — minimal separation.
### Dashboard
- **[Important]** Errors KPI card uses red/orange accent border even when errors = 0. Zero-error state should feel reassuring (green/neutral), not alarming. Creates false alarm fatigue.
- **[Important]** Table lacks visible sort indicators — no arrows showing current sort direction.
- **[Important]** Duration column uses color alone (`.durFast` green, `.durSlow` amber, `.durBreach` red) — not color-blind safe.
- **[Important]** Status dots are ~6px — too small to reliably identify, especially for color-blind users.
- **[Critical]** Table meta text at 11px with `--text-muted` is borderline illegible for fatigued users.
- **[Critical]** KPI stat labels at 10px with `--text-muted` — below recommended 12px minimum.
- **[Nice-to-have]** Exchange ID column too wide — truncate to 8 chars with copy-on-click.
### Dashboard — Detail Panel
- **[Important]** Panel lacks clear visual separation from main table — needs left border accent or different background.
- **[Important]** Processor timeline preview in panel is too small to read — adds visual noise without utility.
- **[Critical]** Overview labels at 10px with `--text-muted` — nearly invisible.
- **[Critical]** Panel section meta at 10px with `--text-faint` (#C4BAB0) on white — contrast ratio ~1.9:1, severely fails WCAG AA.
- **[Nice-to-have]** No quick actions (copy exchange ID, view logs, view route diagram).
### Exchange Detail
- **[Critical]** Processor timeline label column too narrow — processor names are truncated/illegible. This is the page's primary visualization.
- **[Critical]** No error highlighting in processor timeline — failed processors need red bars/icons. During incidents, engineers must instantly see WHICH processor failed.
- **[Important]** No linkage to route diagram — "View in Route Diagram" would overlay execution on the visual route graph.
- **[Important]** Long exchange ID in breadcrumb is visually heavy — truncate with copy button.
- **[Important]** Header stat labels at 10px uppercase with `--text-muted` — same contrast issue.
### Routes Metrics
- **[Important]** KPI number formatting inconsistent — Dashboard shows "11.742 ms" (decimal + space), Routes shows "11742ms" (no decimal, no space).
- **[Important]** No per-route error rate column — error rate in KPI strip but not broken down per route.
- **[Important]** Charts disconnected from table — clicking a route should filter/highlight its chart data.
- **[Nice-to-have]** No visual comparison between routes (bar chart or heatmap for quick identification of slowest).
### Agent Health
- **[Critical]** Stale/Dead agent visual distinction is too subtle — at 3am, the difference between LIVE and DEAD must scream. Dead agents should have prominent red background or strikethrough, not just `--text-muted`.
- **[Critical]** Agent state dots (green live, amber stale, gray dead) use color alone — no shape variation for color-blind users.
- **[Important]** "2/26" active routes KPI is ambiguous — unit and meaning need to be explicit.
- **[Nice-to-have]** Timeline at bottom takes significant space — consider making it collapsible.
### Agent Instance Detail
- **[Important]** Charts lack threshold/alert lines — CPU at 2% is fine, but where is "concerning"? Configurable thresholds (CPU > 80%, Memory > 90%) would make charts actionable.
- **[Important]** Chart axis labels appear too small.
- **[Nice-to-have]** GC Pauses uses area fill while others use line charts — minor inconsistency.
- **[Nice-to-have]** Six charts in 2x3 grid can create cognitive overload — consider collapsible groups.
### Admin — RBAC
- **[Important]** KPI strip for "Users: 1, Groups: 2, Roles: 4" has too much visual weight — these low-value numbers don't need full stat-card treatment.
- **[Important]** "ADMIN" role badge vs "ADMINS" group badge look identical — different badge styles needed (outlined for groups, filled for roles).
- **[Nice-to-have]** Empty detail panel ("Select a user to view details") needs icon/illustration.
### Admin — Audit Log
- **[Important]** "no data" empty state is uninformative — should explain "No audit events match your filters" with guidance.
- **[Important]** No export functionality — audit logs need CSV/JSON export for compliance.
- **[Important]** Date range filters use raw datetime inputs — inconsistent with dashboard's polished time range pills.
### Admin — OIDC Config
- **[Critical]** "Delete OIDC Configuration" is a destructive action without confirmation dialog — could lock out all SSO users.
- **[Important]** No inline validation — Issuer URL should validate format on blur, required fields need indicators.
- **[Nice-to-have]** No connection test result display area.
### Admin — Database
- **[Important]** Visual treatment inconsistent with rest of app — "Connected" status and pool stats use ad-hoc text, not design system components.
- **[Important]** Page title "Database Administration" implies actions, but page is read-only — rename to "Database Status" or add operations.
- **[Nice-to-have]** Table row counts should be right-aligned for numerical scanning.
### Admin — OpenSearch
- **[Critical]** "Disconnected" status displayed as plain text — needs error styling (red text, error badge, or status banner). Infrastructure disconnection is a critical state.
- **[Important]** "Yellow" cluster health displayed as plain text with no visual hierarchy — same size/weight as version number and node count.
- **[Important]** Indexing pipeline stats use ad-hoc inline format — should use consistent stat-card pattern.
- **[Important]** "Disconnected" + "Yellow" health shown simultaneously is contradictory — if disconnected, clarify whether data is stale.
### Command Palette
- **[Nice-to-have]** No visible keyboard navigation hint for currently selected item.
- **[Nice-to-have]** Empty palette should show recent/frequent items instead of requiring typing.
- Overall well-executed — categories, counts, keyboard hints in footer.
### Dark Mode
- **[Critical]** `--text-muted` (#7A7068) on `--bg-surface` (#242019) is ~2.9:1 — fails WCAG AA. Affects ALL muted labels across every page.
- **[Critical]** `--text-faint` (#4A4238) on `--bg-surface` (#242019) is ~1.4:1 — catastrophically fails WCAG AA. Essentially invisible.
- **[Important]** `--amber` (#D4941E) on `--bg-surface` (#242019) is ~3.6:1 — amber links/active text fail AA.
- **[Important]** KPI sparkline chart lines are harder to read — thin strokes need increased width or brightness.
- **[Important]** Sidebar boundary contrast drops significantly (`--sidebar-bg` #141210 vs `--bg-body` #1A1714 is only ~6 units apart).
- **[Important]** Table row alternation contrast near zero in dark mode.
- **[Nice-to-have]** Amber accent color shift from #C6820E to #D4941E is well-handled.
- **[Nice-to-have]** Semantic colors (success, error, warning) appropriately increase luminance.
---
## Cross-Cutting Issues
### 1. Color Contrast (WCAG AA Failures)
**Light Mode:**
| Element | Foreground | Background | Ratio | Required | Verdict |
|---------|-----------|------------|-------|----------|---------|
| StatCard labels, table meta, section headers | `--text-muted` #9C9184 | #FFFFFF | ~3.0:1 | 4.5:1 | **FAIL** |
| Panel meta, overview hints | `--text-faint` #C4BAB0 | #FFFFFF | ~1.9:1 | 4.5:1 | **FAIL** |
| Sign-in button text | #FFFFFF | `--amber` #C6820E | ~3.2:1 | 4.5:1 | **FAIL** |
| Sidebar muted text | #9C9184 | `--sidebar-bg` #2C2520 | ~3.1:1 | 4.5:1 | **FAIL** |
**Dark Mode:**
| Element | Foreground | Background | Ratio | Required | Verdict |
|---------|-----------|------------|-------|----------|---------|
| All muted labels | #7A7068 | #242019 | ~2.9:1 | 4.5:1 | **FAIL** |
| All faint hints | #4A4238 | #242019 | ~1.4:1 | 4.5:1 | **FAIL** |
| Amber links/active text | #D4941E | #242019 | ~3.6:1 | 4.5:1 | **FAIL** |
**Fix:** Change `--text-muted` to **#766A5E** (light) / **#9A9088** (dark). Restrict `--text-faint` to decorative use only or lighten dark variant to #6A6058.
### 2. Font Size Floor
10px text is used for: StatCard labels, overview labels, chain labels, section meta, error class names, detail labels, sidebar tree labels. 11px is used for: table meta, error messages, pagination, toggle buttons, chart titles.
**Fix:** Establish `--font-size-min: 12px` as a design system floor. Update all 10px instances to 12px, all 11px instances to 12px.
### 3. Number/Unit Formatting
Inconsistent across pages:
- Dashboard: "11.742 ms" (decimal + space)
- Routes: "11742ms" (no decimal, no space)
- Dashboard: "1.1 msg/s" vs Agent Instance: "0.1/s"
**Fix:** Create a shared formatting utility enforcing: consistent decimal precision, space before unit, consistent abbreviations.
### 4. KPI Strip Inconsistency
Used on Dashboard, Routes, Agents, Agent Instance (consistent). But RBAC uses oversized cards for trivial counts, and Database/OpenSearch use ad-hoc text rendering.
**Fix:** Admin infra pages should adopt KPI stat strip or a compact-stat component.
### 5. Empty States
Inconsistent handling:
- Audit Log: "no data" in plain gray
- RBAC detail: "Select a user to view details" in gray
- No consistent empty state component with icon + message + CTA
**Fix:** Design system EmptyState component with icon, message, and optional action.
### 6. Status Indicator Accessibility
Color-only status encoding throughout:
- Duration: green (fast), amber (slow), red (breach) — no icons
- Status dots: green (live), amber (stale), gray (dead) — no shapes
- Agent dead state uses `--text-muted` instead of `--error`
**Fix:** Add shape variation (checkmark/triangle/X), increase dot size to 10px minimum, always render text label alongside.
### 7. Sidebar Structure
Same apps listed 3x (under Applications, Agents, Routes) — triples sidebar length and scales poorly.
**Fix:** Unified application-centric tree where expanding an app shows its agents and routes as children.
---
## Prioritized Recommendations
### Critical (fix now)
| # | Recommendation | Impact |
|---|---------------|--------|
| 1 | **Bump `--text-muted` to WCAG AA compliance**#766A5E (light) / #9A9088 (dark). Single highest-impact fix across all pages. | Fixes majority of contrast failures |
| 2 | **Establish 12px minimum font size** — update all 10px and 11px instances. Especially StatCard labels, overview labels, table meta. | Readable under stress |
| 3 | **Add error highlighting to processor timeline** — red bars, error icons for failed processors. Core debugging view. | Incident response speed |
| 4 | **Make Stale/Dead agent states unmistakable** — full card background color (yellow stale, red dead), prominent badge. Change dead from `--text-muted` to `--error`. | Prevents missed outages |
| 5 | **Fix OpenSearch "Disconnected" status** — use error badge/banner, add "Reconnect" action, clarify stale data. | Actionable admin page |
| 6 | **Add confirmation dialog for OIDC deletion** — type-to-confirm to prevent locking out SSO users. | Prevents lockout |
| 7 | **Color Errors KPI card conditionally** — green/neutral at 0, red only when > 0. Prevents false alarm fatigue. | Reduces noise |
### Important (next sprint)
| # | Recommendation | Impact |
|---|---------------|--------|
| 8 | **Add secondary encoding to status indicators** — shapes (checkmark/triangle/X) alongside color dots. Increase dot size to 10px+. | Accessibility compliance |
| 9 | **Standardize number/unit formatting** — shared utility for decimals, spacing, unit abbreviations. | Visual consistency |
| 10 | **Add per-route error rate to Routes table** — essential for isolating failing routes. | Incident triage |
| 11 | **Add visible sort indicators to data tables** — arrows on column headers. | Data exploration |
| 12 | **Bring admin infra pages to design system quality** — replace ad-hoc text with KPI strips/stat cards. | Professional polish |
| 13 | **Fix login page brand identity** — add camel logo, use correct `--amber` for button, add SSO button when OIDC configured. | First impression |
| 14 | **Fix dark mode specifics** — increase sidebar boundary contrast (add 1px border), boost chart stroke width, fix amber link contrast. | Dark mode usability |
| 15 | **Widen processor timeline label column** — prevent name truncation, add tooltips for long names. | Core visualization |
| 16 | **Add detail panel visual separation** — 2px left border accent. | Layout clarity |
| 17 | **Pin Admin/API Docs to sidebar footer** — accessible without scrolling. | Navigation |
| 18 | **Audit log improvements** — informative empty state, CSV/JSON export, date picker consistent with dashboard. | Admin usability |
| 19 | **OIDC form validation** — inline URL validation, required field indicators, test result display. | Configuration safety |
| 20 | **Fix amber button text contrast** — darken button to #8B5A06 or use dark text on amber. | Accessibility |
### Nice-to-have (backlog)
| # | Recommendation | Impact |
|---|---------------|--------|
| 21 | Unify sidebar into single application-centric tree (Applications > agents + routes) | Scalability |
| 22 | Truncate Exchange IDs to 8 chars with copy-on-click | Table space |
| 23 | Add threshold/alert lines to agent metric charts | Actionable monitoring |
| 24 | Link charts to table selection on Routes Metrics | Data exploration |
| 25 | Add clickable KPI cards navigating to filtered views | Navigation shortcuts |
| 26 | Add `prefers-reduced-motion` support for StatusDot pulse animation | Accessibility |
| 27 | Add tooltips to sparkline charts showing value on hover | Data context |
| 28 | Replace hardcoded `#5db866` in Dashboard.module.css with `var(--success)` | Token compliance |
| 29 | Add keyboard navigation indicators to command palette (selected item highlight) | Power user UX |
| 30 | Show recent/frequent items in empty command palette | Discoverability |
| 31 | Consolidate duplicated table-header CSS into design system component | Maintainability |
| 32 | Login page card shadow for visual lift | Polish |
| 33 | Collapsible agent event timeline | Space efficiency |
| 34 | Dark mode `--text-faint` increase to #6A6058 for 3:1 minimum | Accessibility |
| 35 | Increase DataTable row height to 44px (touch target minimum) | Accessibility |
---
## Dark Mode Assessment
**Grade: Good foundation, specific contrast concerns.**
**What works well:**
- Token system remaps all semantic colors without introducing cold blue-grays — warm brand preserved
- Amber accent brightens appropriately (#C6820E#D4941E)
- Error/warning/success colors increase luminance correctly
- Shadows shift from warm semi-transparent to opaque — correct for dark backgrounds
**What needs fixing:**
- Sidebar contrast: `--sidebar-bg` #141210 vs `--bg-body` #1A1714 only ~6 units apart (was ~50 in light mode)
- Chart line visibility: thin 1-2px strokes need increased width
- Table row alternation: near-zero contrast between `--bg-surface` and `--bg-raised`
- `--text-faint`: essentially invisible at 1.4:1 contrast
- `--text-muted`: 2.9:1 — below AA minimum

BIN
audit/01-exchanges-list.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

Some files were not shown because too many files have changed in this diff Show More