diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 0fff8ce8..cb0ca941 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -45,11 +45,12 @@ Plans: 2. User can full-text search across message bodies, headers, error messages, and stack traces and find matching transactions 3. User can retrieve a transaction's detail view showing the nested processor execution tree 4. Route diagrams are stored with content-addressable versioning (identical definitions stored once), each transaction links to its active diagram version, and diagrams can be rendered from stored definitions -**Plans**: TBD +**Plans:** 3 plans Plans: -- [ ] 02-01: Transaction query engine (structured filters + full-text via ClickHouse skip indexes) -- [ ] 02-02: Transaction detail + diagram versioning, linking, and rendering +- [ ] 02-01-PLAN.md -- Schema extension, core domain types, ingestion updates for search/detail columns +- [ ] 02-02-PLAN.md -- Diagram rendering with ELK layout and JFreeSVG (SVG + JSON via content negotiation) +- [ ] 02-03-PLAN.md -- Search endpoints (GET + POST), transaction detail with tree reconstruction, integration tests ### Phase 3: Agent Registry + SSE Push **Goal**: Server tracks connected agents through their full lifecycle and can push configuration updates, deep-trace commands, and replay commands to specific agents in real time @@ -88,6 +89,6 @@ Note: Phases 2 and 3 both depend only on Phase 1 and could execute in parallel. | Phase | Plans Complete | Status | Completed | |-------|----------------|--------|-----------| | 1. Ingestion Pipeline + API Foundation | 2/3 | In Progress| | -| 2. Transaction Search + Diagrams | 0/2 | Not started | - | +| 2. Transaction Search + Diagrams | 0/3 | Not started | - | | 3. Agent Registry + SSE Push | 0/2 | Not started | - | | 4. Security | 0/1 | Not started | - | diff --git a/.planning/phases/02-transaction-search-diagrams/02-01-PLAN.md b/.planning/phases/02-transaction-search-diagrams/02-01-PLAN.md new file mode 100644 index 00000000..3595029e --- /dev/null +++ b/.planning/phases/02-transaction-search-diagrams/02-01-PLAN.md @@ -0,0 +1,260 @@ +--- +phase: 02-transaction-search-diagrams +plan: 01 +type: execute +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 +autonomous: true +requirements: + - SRCH-01 + - SRCH-02 + - SRCH-03 + - SRCH-04 + - SRCH-05 + - DIAG-01 + - DIAG-02 + +must_haves: + truths: + - "ClickHouse schema has columns for exchange bodies, headers, processor depths, parent indexes, diagram content hash" + - "Ingested route executions populate depth, parent index, exchange data, and diagram hash columns" + - "SearchEngine interface exists in core module for future OpenSearch swap" + - "SearchRequest supports all filter combinations: status, time range, duration range, correlationId, text, per-field text" + - "SearchResult envelope wraps paginated data with total, offset, limit" + artifacts: + - 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" + provides: "Search backend abstraction interface" + exports: ["SearchEngine"] + - path: "cameleer3-server-core/src/main/java/com/cameleer3/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" + 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" + to: "SearchEngine" + via: "constructor injection" + pattern: "SearchEngine" + - from: "cameleer3-server-app/src/main/java/com/cameleer3/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" +--- + + +Extend the ClickHouse schema and ingestion path for Phase 2 search capabilities, and create the core domain types and interfaces for the search/detail layer. + +Purpose: Phase 2 search and detail endpoints need additional columns in route_executions (exchange data, tree metadata, diagram hash) and a swappable search engine abstraction. This plan lays the foundation that Plans 02 and 03 build upon. + +Output: Schema migration SQL, updated ingestion INSERT with new columns, core search/detail domain types, SearchEngine interface. + + + +@C:/Users/Hendrik/.claude/get-shit-done/workflows/execute-plan.md +@C:/Users/Hendrik/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/02-transaction-search-diagrams/02-CONTEXT.md +@.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 + + + + +From cameleer3-server-core/.../storage/ExecutionRepository.java: +```java +public interface ExecutionRepository { + void insertBatch(List executions); +} +``` + +From cameleer3-server-core/.../storage/DiagramRepository.java: +```java +public interface DiagramRepository { + void store(RouteGraph graph); + Optional findByContentHash(String contentHash); + Optional findContentHashForRoute(String routeId, String agentId); +} +``` + +From cameleer3-common (decompiled — key fields): +```java +// RouteExecution: routeId, status (ExecutionStatus enum: COMPLETED/FAILED/RUNNING), +// startTime (Instant), endTime (Instant), durationMs (long), correlationId, exchangeId, +// errorMessage, errorStackTrace, processors (List), +// inputSnapshot (ExchangeSnapshot), outputSnapshot (ExchangeSnapshot) + +// ProcessorExecution: processorId, processorType, status, startTime, endTime, durationMs, +// children (List), diagramNodeId, +// inputSnapshot (ExchangeSnapshot), outputSnapshot (ExchangeSnapshot) + +// ExchangeSnapshot: body (String), headers (Map), properties (Map) + +// RouteGraph: routeId, nodes (List), edges (List), processorNodeMapping (Map) +// RouteNode: id, label, type (NodeType enum), properties (Map) +// RouteEdge: source, target, label +// NodeType enum: ENDPOINT, TO, TO_DYNAMIC, DIRECT, SEDA, PROCESSOR, BEAN, LOG, SET_HEADER, SET_BODY, +// TRANSFORM, MARSHAL, UNMARSHAL, CHOICE, WHEN, OTHERWISE, SPLIT, AGGREGATE, MULTICAST, +// FILTER, RECIPIENT_LIST, ROUTING_SLIP, DYNAMIC_ROUTER, LOAD_BALANCE, THROTTLE, DELAY, +// ERROR_HANDLER, ON_EXCEPTION, TRY_CATCH, DO_TRY, DO_CATCH, DO_FINALLY, WIRE_TAP, +// ENRICH, POLL_ENRICH, SORT, RESEQUENCE, IDEMPOTENT_CONSUMER, CIRCUIT_BREAKER, SAGA, LOOP +``` + +Existing ClickHouse schema (01-schema.sql): +```sql +-- route_executions: execution_id, route_id, agent_id, status, start_time, end_time, +-- duration_ms, correlation_id, exchange_id, error_message, error_stacktrace, +-- processor_ids, processor_types, processor_starts, processor_ends, +-- processor_durations, processor_statuses, server_received_at +-- ORDER BY (agent_id, status, start_time, execution_id) +-- PARTITION BY toYYYYMMDD(start_time) +-- Skip indexes: idx_correlation (bloom_filter), idx_error (tokenbf_v1) +``` + + + + + + + Task 1: Schema extension and core domain types + + 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 + + + 1. Create `clickhouse/init/02-search-columns.sql` with ALTER TABLE statements to add Phase 2 columns to route_executions: + - `exchange_bodies String DEFAULT ''` — concatenated searchable text of all exchange bodies + - `exchange_headers String DEFAULT ''` — concatenated searchable text of all exchange headers + - `processor_depths Array(UInt16) DEFAULT []` — depth of each processor in tree + - `processor_parent_indexes Array(Int32) DEFAULT []` — parent index (-1 for roots) for tree reconstruction + - `processor_error_messages Array(String) DEFAULT []` — per-processor error messages + - `processor_error_stacktraces Array(String) DEFAULT []` — per-processor error stack traces + - `processor_input_bodies Array(String) DEFAULT []` — per-processor input body snapshots + - `processor_output_bodies Array(String) DEFAULT []` — per-processor output body snapshots + - `processor_input_headers Array(String) DEFAULT []` — per-processor input headers (JSON string per element) + - `processor_output_headers Array(String) DEFAULT []` — per-processor output headers (JSON string per element) + - `processor_diagram_node_ids Array(String) DEFAULT []` — per-processor diagramNodeId for overlay linking + - `diagram_content_hash String DEFAULT ''` — links execution to its active diagram version (DIAG-02) + - 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`: + - `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` record: data (List), 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 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`: + - `ProcessorNode` record: processorId (String), processorType (String), status (String), startTime (Instant), endTime (Instant), durationMs (long), diagramNodeId (String), errorMessage (String), errorStackTrace (String), children (List). 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). This is the full detail response. + - `DetailService` class: plain class (no Spring annotations). Constructor takes ExecutionRepository. Method `getDetail(String executionId)` returns `Optional`. 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). + + 4. Extend `ExecutionRepository` interface with new query methods: + - `Optional findDetailById(String executionId)` — returns raw flat data for tree reconstruction (DetailService handles reconstruction) + + Actually, use a different approach per the layering: add a `findRawById(String executionId)` method that returns `Optional` — 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. + + + cd C:/Users/Hendrik/Documents/projects/cameleer3-server && mvn compile -pl cameleer3-server-core + + Schema migration SQL exists, all core domain types compile, SearchEngine interface and SearchService defined, ExecutionRepository extended with query method, DetailService has tree reconstruction logic + + + + Task 2: Update ingestion to populate new columns and verify with integration test + + 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 + + + - 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 + - Test: Processor depths are correct for a 3-level tree: root=0, child=1, grandchild=2 + - Test: Processor parent indexes correctly reference parent positions: root=-1, child=parentIdx, grandchild=childIdx + - Test: exchange_bodies contains concatenated body text from all processor snapshots (for LIKE search) + - Test: Insertions that omit exchange snapshot data (null snapshots) produce empty-string defaults without error + + + 1. Update `AbstractClickHouseIT.initSchema()` to also load `02-search-columns.sql` after `01-schema.sql`. Use the same path resolution pattern (check `clickhouse/init/` then `../clickhouse/init/`). + + 2. Update `ClickHouseExecutionRepository`: + - Extend INSERT_SQL to include all new columns: exchange_bodies, exchange_headers, processor_depths, processor_parent_indexes, processor_error_messages, processor_error_stacktraces, processor_input_bodies, processor_output_bodies, processor_input_headers, processor_output_headers, processor_diagram_node_ids, diagram_content_hash + - Refactor `flattenProcessors` to return a list of `FlatProcessor` records containing the original ProcessorExecution plus computed depth (int) and parentIndex (int). Use the recursive approach from the research: track depth and parent index during DFS traversal. + - In `setValues`: build parallel arrays for all new columns from FlatProcessor list. + - Build concatenated `exchange_bodies` string: join all processor input/output bodies plus route-level input/output snapshot bodies with space separators. Same for `exchange_headers` but serialize Map headers to JSON string using Jackson ObjectMapper (inject via constructor or create statically). + - For diagram_content_hash: leave as empty string for now (the ingestion endpoint does not yet resolve the active diagram hash — this is a query-time concern). Plan 03 wires this if needed, but DIAG-02 can also be satisfied by joining route_diagrams at query time. + - Handle null ExchangeSnapshot gracefully: empty string for bodies, empty JSON object for headers. + + 3. Create `IngestionSchemaIT` integration test that: + - Extends AbstractClickHouseIT + - Builds a RouteExecution with a 3-level processor tree where processors have ExchangeSnapshot data + - POSTs it to /api/v1/data/executions, waits for flush + - Queries ClickHouse directly via jdbcTemplate to verify all new columns have correct values + - Verifies processor_depths = [0, 1, 2] for a root->child->grandchild chain + - Verifies processor_parent_indexes = [-1, 0, 1] + - Verifies exchange_bodies contains the body text + - Verifies a second insertion with null snapshots succeeds with empty defaults + + + cd C:/Users/Hendrik/Documents/projects/cameleer3-server && mvn test -pl cameleer3-server-app -Dtest=IngestionSchemaIT + + 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 + + + + + +- `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) + + + +- ClickHouse schema extension SQL exists and is loaded by test infrastructure +- All 12+ new columns populated during ingestion with correct values +- Processor tree metadata (depth, parentIndex) correctly computed during DFS flattening +- Exchange snapshot data concatenated into searchable text columns +- SearchEngine interface exists in core module for future backend swap +- SearchRequest/SearchResult/ExecutionSummary records exist with all required fields +- DetailService can reconstruct a nested ProcessorNode tree from flat arrays +- All existing Phase 1 tests still pass + + + +After completion, create `.planning/phases/02-transaction-search-diagrams/02-01-SUMMARY.md` + diff --git a/.planning/phases/02-transaction-search-diagrams/02-02-PLAN.md b/.planning/phases/02-transaction-search-diagrams/02-02-PLAN.md new file mode 100644 index 00000000..722364a9 --- /dev/null +++ b/.planning/phases/02-transaction-search-diagrams/02-02-PLAN.md @@ -0,0 +1,261 @@ +--- +phase: 02-transaction-search-diagrams +plan: 02 +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 +autonomous: true +requirements: + - DIAG-03 + +must_haves: + truths: + - "GET /api/v1/diagrams/{hash} with Accept: image/svg+xml returns an SVG document with color-coded nodes" + - "GET /api/v1/diagrams/{hash} with Accept: application/json returns a JSON layout with node positions" + - "Nodes are laid out top-to-bottom using ELK layered algorithm" + - "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" + 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" + provides: "ELK + JFreeSVG implementation of DiagramRenderer" + min_lines: 100 + - path: "cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/DiagramRenderController.java" + provides: "GET /api/v1/diagrams/{hash} with content negotiation" + exports: ["DiagramRenderController"] + key_links: + - from: "DiagramRenderController" + to: "DiagramRepository" + via: "findByContentHash to load RouteGraph" + pattern: "findByContentHash" + - from: "DiagramRenderController" + to: "DiagramRenderer" + via: "renderSvg or layoutJson based on Accept header" + pattern: "renderSvg|layoutJson" + - from: "ElkDiagramRenderer" + to: "ELK RecursiveGraphLayoutEngine" + via: "layout computation" + pattern: "RecursiveGraphLayoutEngine" +--- + + +Implement route diagram rendering with Eclipse ELK for layout and JFreeSVG for SVG output, exposed via a REST endpoint with content negotiation. + +Purpose: Users need to visualize route diagrams from stored RouteGraph definitions. The server renders color-coded, top-to-bottom SVG diagrams or returns JSON layout data for client-side rendering. This is independent of the search work and can run in parallel. + +Output: DiagramRenderer interface in core, ElkDiagramRenderer implementation in app, DiagramRenderController with Accept header content negotiation, integration and unit tests. + + + +@C:/Users/Hendrik/.claude/get-shit-done/workflows/execute-plan.md +@C:/Users/Hendrik/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.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 + + + + +From cameleer3-server-core/.../storage/DiagramRepository.java: +```java +public interface DiagramRepository { + void store(RouteGraph graph); + Optional findByContentHash(String contentHash); + Optional findContentHashForRoute(String routeId, String agentId); +} +``` + +From cameleer3-common (decompiled — diagram models): +```java +// RouteGraph: routeId (String), nodes (List), edges (List), +// processorNodeMapping (Map) +// RouteNode: id (String), label (String), type (NodeType), properties (Map) +// RouteEdge: source (String), target (String), label (String) +// NodeType enum: ENDPOINT, TO, TO_DYNAMIC, DIRECT, SEDA, PROCESSOR, BEAN, LOG, +// SET_HEADER, SET_BODY, TRANSFORM, MARSHAL, UNMARSHAL, CHOICE, WHEN, OTHERWISE, +// SPLIT, AGGREGATE, MULTICAST, FILTER, RECIPIENT_LIST, ROUTING_SLIP, DYNAMIC_ROUTER, +// LOAD_BALANCE, THROTTLE, DELAY, ERROR_HANDLER, ON_EXCEPTION, TRY_CATCH, DO_TRY, +// DO_CATCH, DO_FINALLY, WIRE_TAP, ENRICH, POLL_ENRICH, SORT, RESEQUENCE, +// IDEMPOTENT_CONSUMER, CIRCUIT_BREAKER, SAGA, LOOP +``` + +NodeType color mapping (from CONTEXT.md, matching route-diagram-example.html): +- Blue (#3B82F6): ENDPOINT, TO, TO_DYNAMIC, DIRECT, SEDA (endpoints) +- Green (#22C55E): PROCESSOR, BEAN, LOG, SET_HEADER, SET_BODY, TRANSFORM, MARSHAL, UNMARSHAL (processors) +- Red (#EF4444): ERROR_HANDLER, ON_EXCEPTION, TRY_CATCH, DO_TRY, DO_CATCH, DO_FINALLY (error handling) +- Purple (#A855F7): CHOICE, WHEN, OTHERWISE, SPLIT, AGGREGATE, MULTICAST, FILTER, etc. (EIP patterns) +- Cyan (#06B6D4): WIRE_TAP, ENRICH, POLL_ENRICH (cross-route) + + + + + + + Task 1: Add ELK/JFreeSVG dependencies and create core diagram rendering interfaces + + 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 + + + 1. Add Maven dependencies to `cameleer3-server-app/pom.xml`: + ```xml + + org.eclipse.elk + org.eclipse.elk.core + 0.11.0 + + + org.eclipse.elk + org.eclipse.elk.alg.layered + 0.11.0 + + + org.jfree + org.jfree.svg + 5.0.7 + + ``` + + 2. Create core diagram rendering interfaces in `com.cameleer3.server.core.diagram`: + + - `PositionedNode` record: id (String), label (String), type (String — NodeType name), x (double), y (double), width (double), height (double), children (List — for compound/swimlane groups). JSON-serializable for the JSON layout response. + + - `PositionedEdge` record: sourceId (String), targetId (String), label (String), points (List — waypoints for edge routing). The points list contains [x,y] pairs from source to target. + + - `DiagramLayout` record: width (double), height (double), nodes (List), edges (List). This is the JSON layout response format. + + - `DiagramRenderer` interface with two methods: + - `String renderSvg(RouteGraph graph)` — returns SVG XML string + - `DiagramLayout layoutJson(RouteGraph graph)` — returns positioned layout data + Both methods take a RouteGraph and produce output. The interface lives in core so it can be swapped (e.g., for a different renderer). + + + cd C:/Users/Hendrik/Documents/projects/cameleer3-server && mvn compile -pl cameleer3-server-core && mvn dependency:resolve -pl cameleer3-server-app -q + + ELK and JFreeSVG dependencies resolve, DiagramRenderer interface and layout DTOs compile in core module + + + + Task 2: Implement ElkDiagramRenderer, DiagramRenderController, and integration tests + + 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 + + + - 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 + - Unit test: ElkDiagramRenderer.renderSvg produces SVG where endpoint nodes have blue fill (#3B82F6 or rgb equivalent) + - Unit test: ElkDiagramRenderer.layoutJson returns DiagramLayout with correct node count and positive coordinates + - Unit test: Nested processors (e.g., CHOICE with WHEN children) are laid out as compound nodes with children inside parent bounds + - Integration test: GET /api/v1/diagrams/{hash} with Accept: image/svg+xml returns 200 with content-type image/svg+xml and body starting with ' + + 1. Create `ElkDiagramRenderer` implementing `DiagramRenderer` in `com.cameleer3.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. + - For each RouteNode: create ElkNode with estimated width (based on label length * 8 + 32, min 80) and height (40). Set identifier to node.id. + - For compound/nesting nodes (CHOICE, SPLIT, TRY_CATCH, DO_TRY, LOOP, MULTICAST, AGGREGATE): create these as compound ElkNodes. Identify children by examining RouteEdge topology — nodes whose only incoming edge is from the compound node AND have no outgoing edge leaving the compound scope are children. Alternatively, use the NodeType hierarchy: WHEN/OTHERWISE are children of CHOICE, DO_CATCH/DO_FINALLY are children of DO_TRY. Create child ElkNodes inside the parent compound node. Set compound node padding (top: 30 for label, sides: 10). + - For each RouteEdge: create ElkEdge connecting source to target ElkNodes. + - Run layout: `new RecursiveGraphLayoutEngine().layout(rootNode, new BasicProgressMonitor())`. + - Extract positions from computed layout into DiagramLayout (nodes with x/y/w/h, edges with routed waypoints). + + **SVG rendering (renderSvg):** + - Run layout phase to get DiagramLayout. + - Create `SVGGraphics2D` with layout width + margins and layout height + margins (add 20px padding each side). + - Draw edges first (behind nodes): gray (#9CA3AF) lines with 2px stroke following edge waypoints. Draw arrowheads at endpoints. + - Draw nodes: rounded rectangles (corner radius 8) filled with type-based colors: + - Blue (#3B82F6): ENDPOINT, TO, TO_DYNAMIC, DIRECT, SEDA + - Green (#22C55E): PROCESSOR, BEAN, LOG, SET_HEADER, SET_BODY, TRANSFORM, MARSHAL, UNMARSHAL + - Red (#EF4444): ERROR_HANDLER, ON_EXCEPTION, TRY_CATCH, DO_TRY, DO_CATCH, DO_FINALLY + - Purple (#A855F7): CHOICE, WHEN, OTHERWISE, SPLIT, AGGREGATE, MULTICAST, FILTER, RECIPIENT_LIST, ROUTING_SLIP, DYNAMIC_ROUTER, LOAD_BALANCE, THROTTLE, DELAY, SORT, RESEQUENCE, IDEMPOTENT_CONSUMER, CIRCUIT_BREAKER, SAGA, LOOP + - Cyan (#06B6D4): WIRE_TAP, ENRICH, POLL_ENRICH + - Draw node labels: white text, centered horizontally, vertically positioned at node.y + 24. + - For compound nodes: draw a lighter-fill (alpha 0.15) rounded rectangle for the swimlane container with a label at the top. Draw child nodes inside. + - Return `g2.getSVGDocument()`. + + **JSON layout (layoutJson):** + - Run layout phase, return DiagramLayout directly. Jackson will serialize it to JSON. + + 2. Create `DiagramBeanConfig` in `com.cameleer3.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`: + - `GET /api/v1/diagrams/{contentHash}/render` — renders the diagram + - Inject DiagramRepository and DiagramRenderer. + - Look up RouteGraph via `diagramRepository.findByContentHash(contentHash)`. If empty, return 404. + - Content negotiation via Accept header: + - `image/svg+xml` or `*/*` or no Accept: call `renderer.renderSvg(graph)`, return ResponseEntity with content-type `image/svg+xml` and SVG body. + - `application/json`: call `renderer.layoutJson(graph)`, return ResponseEntity with content-type `application/json`. + - Use `@RequestMapping(produces = {...})` or manual Accept header parsing to handle content negotiation. Manual parsing is simpler: read `request.getHeader("Accept")`, check for "application/json", default to SVG. + + 4. Create `ElkDiagramRendererTest` (unit test, no Spring context): + - Build a simple RouteGraph with 3 nodes (from-endpoint, process-bean, to-endpoint) and 2 edges. + - Test renderSvg produces valid SVG string containing ` WHEN, OTHERWISE compound structure. Verify compound node layout has children. + + 5. Create `DiagramRenderControllerIT` (extends AbstractClickHouseIT): + - Seed a RouteGraph into ClickHouse via the /api/v1/data/diagrams endpoint, wait for flush. + - Look up the content hash (compute SHA-256 of the JSON-serialized RouteGraph, same as ClickHouseDiagramRepository.sha256Hex). + - GET /api/v1/diagrams/{hash}/render with Accept: image/svg+xml -> assert 200, content-type contains "svg", body contains " assert 200, body contains "nodes", "edges". + - GET /api/v1/diagrams/nonexistent/render -> assert 404. + - GET /api/v1/diagrams/{hash}/render with no Accept header -> assert SVG response (default). + + + cd C:/Users/Hendrik/Documents/projects/cameleer3-server && mvn test -pl cameleer3-server-app -Dtest="ElkDiagramRendererTest,DiagramRenderControllerIT" + + 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 + + + + + +- `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 clean verify` passes (all existing tests still green) +- SVG output contains color-coded nodes matching the NodeType color scheme + + + +- GET /api/v1/diagrams/{hash}/render returns SVG with color-coded nodes (blue endpoints, green processors, red error handlers, purple EIPs, cyan cross-route) +- GET /api/v1/diagrams/{hash}/render with Accept: application/json returns JSON layout with node positions +- Nodes laid out top-to-bottom via ELK layered algorithm +- Compound nodes group nested processors (CHOICE/WHEN, TRY/CATCH) in swimlane containers +- Non-existent hash returns 404 +- Default (no Accept header) returns SVG + + + +After completion, create `.planning/phases/02-transaction-search-diagrams/02-02-SUMMARY.md` + diff --git a/.planning/phases/02-transaction-search-diagrams/02-03-PLAN.md b/.planning/phases/02-transaction-search-diagrams/02-03-PLAN.md new file mode 100644 index 00000000..a69a4338 --- /dev/null +++ b/.planning/phases/02-transaction-search-diagrams/02-03-PLAN.md @@ -0,0 +1,354 @@ +--- +phase: 02-transaction-search-diagrams +plan: 03 +type: execute +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 +autonomous: true +requirements: + - SRCH-01 + - SRCH-02 + - SRCH-03 + - SRCH-04 + - SRCH-05 + - SRCH-06 + +must_haves: + truths: + - "User can search by status and get only matching executions" + - "User can search by time range and get only executions within that window" + - "User can search by duration range (min/max ms) and get matching executions" + - "User can search by correlationId to find all related executions" + - "User can full-text search and find matches in bodies, headers, error messages, stack traces" + - "User can combine multiple filters in a single search (e.g., status + time + text)" + - "User can retrieve a transaction detail with nested processor execution tree" + - "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" + 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" + provides: "GET + POST /api/v1/search/executions endpoints" + exports: ["SearchController"] + - path: "cameleer3-server-app/src/main/java/com/cameleer3/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" + provides: "Integration tests for all search filter combinations" + min_lines: 100 + key_links: + - from: "SearchController" + to: "SearchService" + via: "constructor injection, delegates search()" + pattern: "searchService\\.search" + - from: "SearchService" + to: "ClickHouseSearchEngine" + via: "SearchEngine interface" + pattern: "engine\\.search" + - from: "ClickHouseSearchEngine" + to: "route_executions table" + via: "dynamic SQL with parameterized WHERE" + pattern: "SELECT.*FROM route_executions.*WHERE" + - from: "DetailController" + to: "DetailService" + via: "constructor injection" + pattern: "detailService\\.getDetail" + - from: "DetailService" + to: "ClickHouseExecutionRepository" + via: "findRawById for flat data, then reconstructTree" + pattern: "findRawById|reconstructTree" +--- + + +Implement the search endpoints (GET and POST), the ClickHouse search engine with dynamic SQL, the transaction detail endpoint with nested tree reconstruction, and comprehensive integration tests. + +Purpose: This is the core query capability of Phase 2 — users need to find transactions by any combination of filters and drill into execution details. The search engine abstraction allows future swap to OpenSearch. + +Output: SearchController (GET + POST), DetailController, ClickHouseSearchEngine, TreeReconstructionTest, SearchControllerIT, DetailControllerIT. + + + +@C:/Users/Hendrik/.claude/get-shit-done/workflows/execute-plan.md +@C:/Users/Hendrik/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/phases/02-transaction-search-diagrams/02-CONTEXT.md +@.planning/phases/02-transaction-search-diagrams/02-RESEARCH.md +@.planning/phases/02-transaction-search-diagrams/02-01-SUMMARY.md + +@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 + + + + +From cameleer3-server-core/.../search/SearchEngine.java: +```java +public interface SearchEngine { + SearchResult search(SearchRequest request); + long count(SearchRequest request); +} +``` + +From cameleer3-server-core/.../search/SearchRequest.java: +```java +public record SearchRequest( + String status, // nullable, filter by ExecutionStatus name + Instant timeFrom, // nullable, start_time >= this + Instant timeTo, // nullable, start_time <= this + Long durationMin, // nullable, duration_ms >= this + Long durationMax, // nullable, duration_ms <= this + String correlationId, // nullable, exact match + String text, // nullable, global full-text LIKE across all text fields + String textInBody, // nullable, LIKE on exchange_bodies only + String textInHeaders, // nullable, LIKE on exchange_headers only + String textInErrors, // nullable, LIKE on error_message + error_stacktrace + int offset, + int limit +) { /* compact constructor with validation */ } +``` + +From cameleer3-server-core/.../search/SearchResult.java: +```java +public record SearchResult(List data, long total, int offset, int limit) { + public static SearchResult empty(int offset, int limit); +} +``` + +From cameleer3-server-core/.../search/ExecutionSummary.java: +```java +public record ExecutionSummary( + String executionId, String routeId, String agentId, String status, + Instant startTime, Instant endTime, long durationMs, + String correlationId, String errorMessage, String diagramContentHash +) {} +``` + +From cameleer3-server-core/.../detail/DetailService.java: +```java +public class DetailService { + // Constructor takes ExecutionRepository (or a query interface) + public Optional getDetail(String executionId); + // Internal: reconstructTree(parallel arrays) -> List +} +``` + +From cameleer3-server-core/.../detail/ExecutionDetail.java: +```java +public record ExecutionDetail( + String executionId, String routeId, String agentId, String status, + Instant startTime, Instant endTime, long durationMs, + String correlationId, String exchangeId, String errorMessage, + String errorStackTrace, String diagramContentHash, + List processors +) {} +``` + +From cameleer3-server-core/.../detail/ProcessorNode.java: +```java +public record ProcessorNode( + String processorId, String processorType, String status, + Instant startTime, Instant endTime, long durationMs, + String diagramNodeId, String errorMessage, String errorStackTrace, + List children +) {} +``` + +Existing ClickHouse schema (after Plan 01 schema extension): +```sql +-- route_executions columns: +-- execution_id, route_id, agent_id, status, start_time, end_time, +-- duration_ms, correlation_id, exchange_id, error_message, error_stacktrace, +-- processor_ids, processor_types, processor_starts, processor_ends, +-- processor_durations, processor_statuses, +-- exchange_bodies, exchange_headers, +-- processor_depths, processor_parent_indexes, +-- processor_error_messages, processor_error_stacktraces, +-- processor_input_bodies, processor_output_bodies, +-- processor_input_headers, processor_output_headers, +-- processor_diagram_node_ids, diagram_content_hash, +-- server_received_at +-- ORDER BY (agent_id, status, start_time, execution_id) +``` + +Established controller pattern (from Phase 1): +```java +// Controllers accept raw String body for single/array flexibility +// Return 202 for ingestion, standard REST responses for queries +// ProtocolVersionInterceptor validates X-Cameleer-Protocol-Version: 1 header +``` + + + + + + + Task 1: ClickHouseSearchEngine, SearchController, and search integration tests + + 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 + + + - 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} + - Test searchByTimeRange: Insert executions at different times. Filter by timeFrom/timeTo returns only those in range + - Test searchByDuration: Insert executions with different durations. Filter by durationMin=100&durationMax=500 returns only matching + - Test searchByCorrelationId: Insert executions with different correlationIds. Filter returns only matching + - Test fullTextSearchGlobal: Insert execution with error_message="NullPointerException in OrderService". Search text=NullPointerException returns it. Search text=nonexistent returns empty + - Test fullTextSearchInBody: Insert execution with exchange body containing "customer-123". textInBody=customer-123 returns it + - Test fullTextSearchInHeaders: Insert execution with exchange headers containing "Content-Type". textInHeaders=Content-Type returns it + - Test fullTextSearchInErrors: Insert execution with error_stacktrace containing "com.example.MyException". textInErrors=MyException returns it + - Test combinedFilters: status=FAILED + text=NullPointer returns only failed executions with that error + - Test postAdvancedSearch: POST /api/v1/search/executions with JSON body containing all filters returns correct results + - Test pagination: Insert 10 executions. Request with offset=2&limit=3 returns 3 items, total=10, offset=2 + - Test emptyResults: Search with no matches returns {"data":[],"total":0,"offset":0,"limit":50} + + + 1. Create `ClickHouseSearchEngine` in `com.cameleer3.server.app.search`: + - Implements SearchEngine interface from core module. + - Constructor takes JdbcTemplate. + - `search(SearchRequest)` method: + - Build dynamic WHERE clause from non-null SearchRequest fields using ArrayList conditions and ArrayList params. + - status: `"status = ?"` with `req.status()` + - timeFrom: `"start_time >= ?"` with `Timestamp.from(req.timeFrom())` + - timeTo: `"start_time <= ?"` with `Timestamp.from(req.timeTo())` + - durationMin: `"duration_ms >= ?"` with `req.durationMin()` + - durationMax: `"duration_ms <= ?"` with `req.durationMax()` + - correlationId: `"correlation_id = ?"` with `req.correlationId()` + - text (global): `"(error_message LIKE ? OR error_stacktrace LIKE ? OR exchange_bodies LIKE ? OR exchange_headers LIKE ?)"` with `"%" + escapeLike(req.text()) + "%"` repeated 4 times + - textInBody: `"exchange_bodies LIKE ?"` with escaped pattern + - textInHeaders: `"exchange_headers LIKE ?"` with escaped pattern + - textInErrors: `"(error_message LIKE ? OR error_stacktrace LIKE ?)"` with escaped pattern repeated 2 times + - Combine conditions with AND. If empty, no WHERE clause. + - Count query: `SELECT count() FROM route_executions` + where + - Data query: `SELECT execution_id, route_id, agent_id, status, start_time, end_time, duration_ms, correlation_id, error_message, diagram_content_hash FROM route_executions` + where + ` ORDER BY start_time DESC LIMIT ? OFFSET ?` + - Map rows to ExecutionSummary records. Use `rs.getTimestamp("start_time").toInstant()` for Instant fields. + - Return SearchResult with data, total from count query, offset, limit. + - `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`: + - @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`: + - Inject SearchService. + - `GET /api/v1/search/executions` with @RequestParam for basic filters: + - status (optional String) + - timeFrom (optional Instant, use @DateTimeFormat or String parsing) + - timeTo (optional Instant) + - correlationId (optional String) + - offset (optional int, default 0) + - limit (optional int, default 50) + Build SearchRequest from params, call searchService.search(), return ResponseEntity with SearchResult. + - `POST /api/v1/search/executions` accepting JSON body: + - Accept SearchRequest directly (or a DTO that maps to SearchRequest). Jackson will deserialize the JSON body. + - All filters available including durationMin, durationMax, text, textInBody, textInHeaders, textInErrors. + - Call searchService.search(), return ResponseEntity with SearchResult. + - Response format per user decision: `{ "data": [...], "total": N, "offset": 0, "limit": 50 }` + + 4. Create `SearchControllerIT` (extends AbstractClickHouseIT): + - Use TestRestTemplate (auto-configured by @SpringBootTest with RANDOM_PORT). + - Seed test data: Insert multiple RouteExecution objects with varying statuses, times, durations, correlationIds, error messages, and exchange snapshot data. Use the POST /api/v1/data/executions endpoint to insert, then wait for flush (Awaitility). + - Write tests for each behavior listed above. Use GET for basic filter tests, POST for advanced/combined filter tests. + - All requests include X-Cameleer-Protocol-Version: 1 header per ProtocolVersionInterceptor requirement. + - Assert response structure matches the envelope format. + + + cd C:/Users/Hendrik/Documents/projects/cameleer3-server && mvn test -pl cameleer3-server-app -Dtest=SearchControllerIT + + 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 + + + + Task 2: DetailController, tree reconstruction, exchange snapshot endpoint, and integration tests + + 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 + + + - 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 + - Unit test: reconstructTree with [A, B, C], depths=[0,0,0], parents=[-1,-1,-1] produces 3 roots (no nesting) + - Unit test: reconstructTree with [parent, child1, child2, grandchild], depths=[0,1,1,2], parents=[-1,0,0,2] produces parent with 2 children, second child has one grandchild + - Unit test: reconstructTree with empty arrays produces empty list + - Integration test: GET /api/v1/executions/{id} returns ExecutionDetail with nested processors tree matching the ingested structure + - Integration test: detail response includes diagramContentHash field (can be empty string if not set) + - Integration test: GET /api/v1/executions/{nonexistent-id} returns 404 + - Integration test: GET /api/v1/executions/{id}/processors/{index}/snapshot returns exchange snapshot data for that processor + + + 1. Create `TreeReconstructionTest` in core module test directory: + - Pure unit test (no Spring context needed). + - Test DetailService.reconstructTree (make it a static method or package-accessible for testing). + - Cover cases: single root, linear chain, wide tree (multiple roots), branching tree, empty arrays. + - Verify correct parent-child wiring and that ProcessorNode.children() lists are correctly populated. + + 2. Extend `ClickHouseExecutionRepository` with query methods: + - Add `findRawById(String executionId)` method that queries all columns from route_executions WHERE execution_id = ?. Return Optional (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`: + - 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. + + 4. Create `DetailControllerIT` (extends AbstractClickHouseIT): + - Seed a RouteExecution with a 3-level processor tree (root with 2 children, one child has a grandchild). Give processors exchange snapshot data (bodies, headers). + - Also seed a RouteGraph diagram for the route to test diagram hash linking. + - POST to ingestion endpoints, wait for flush. + - Test GET /api/v1/executions/{id}: verify response has nested processors tree with correct depths. Root should have 2 children, one child should have 1 grandchild. Verify diagramContentHash is present. + - Test GET /api/v1/executions/{id}/processors/0/snapshot: returns snapshot data for root processor. + - Test GET /api/v1/executions/{nonexistent}/: returns 404. + - Test GET /api/v1/executions/{id}/processors/999/snapshot: returns 404 for out-of-bounds index. + + + cd C:/Users/Hendrik/Documents/projects/cameleer3-server && mvn test -pl cameleer3-server-core -Dtest=TreeReconstructionTest && mvn test -pl cameleer3-server-app -Dtest=DetailControllerIT + + 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 + + + + + +- `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 clean verify` passes (full suite green) + + + +- GET /api/v1/search/executions with status/time/duration/correlationId filters returns correct results +- POST /api/v1/search/executions with JSON body supports all filters including full-text and per-field targeting +- Full-text LIKE search finds matches in error_message, error_stacktrace, exchange_bodies, exchange_headers +- Combined filters work correctly (AND logic) +- Response envelope: { "data": [...], "total": N, "offset": 0, "limit": 50 } +- GET /api/v1/executions/{id} returns nested processor tree reconstructed from flat arrays +- GET /api/v1/executions/{id}/processors/{index}/snapshot returns per-processor exchange data +- Detail response includes diagramContentHash for linking to diagram render endpoint +- All tests pass including existing Phase 1 tests + + + +After completion, create `.planning/phases/02-transaction-search-diagrams/02-03-SUMMARY.md` +