docs(02): create phase plan - 3 plans, 2 waves
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -45,11 +45,12 @@ Plans:
|
|||||||
2. User can full-text search across message bodies, headers, error messages, and stack traces and find matching transactions
|
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
|
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
|
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:
|
Plans:
|
||||||
- [ ] 02-01: Transaction query engine (structured filters + full-text via ClickHouse skip indexes)
|
- [ ] 02-01-PLAN.md -- Schema extension, core domain types, ingestion updates for search/detail columns
|
||||||
- [ ] 02-02: Transaction detail + diagram versioning, linking, and rendering
|
- [ ] 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
|
### 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
|
**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 |
|
| Phase | Plans Complete | Status | Completed |
|
||||||
|-------|----------------|--------|-----------|
|
|-------|----------------|--------|-----------|
|
||||||
| 1. Ingestion Pipeline + API Foundation | 2/3 | In Progress| |
|
| 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 | - |
|
| 3. Agent Registry + SSE Push | 0/2 | Not started | - |
|
||||||
| 4. Security | 0/1 | Not started | - |
|
| 4. Security | 0/1 | Not started | - |
|
||||||
|
|||||||
260
.planning/phases/02-transaction-search-diagrams/02-01-PLAN.md
Normal file
260
.planning/phases/02-transaction-search-diagrams/02-01-PLAN.md
Normal file
@@ -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"
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
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.
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<execution_context>
|
||||||
|
@C:/Users/Hendrik/.claude/get-shit-done/workflows/execute-plan.md
|
||||||
|
@C:/Users/Hendrik/.claude/get-shit-done/templates/summary.md
|
||||||
|
</execution_context>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
@.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
|
||||||
|
|
||||||
|
<interfaces>
|
||||||
|
<!-- Existing interfaces the executor needs -->
|
||||||
|
|
||||||
|
From cameleer3-server-core/.../storage/ExecutionRepository.java:
|
||||||
|
```java
|
||||||
|
public interface ExecutionRepository {
|
||||||
|
void insertBatch(List<RouteExecution> executions);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
From cameleer3-server-core/.../storage/DiagramRepository.java:
|
||||||
|
```java
|
||||||
|
public interface DiagramRepository {
|
||||||
|
void store(RouteGraph graph);
|
||||||
|
Optional<RouteGraph> findByContentHash(String contentHash);
|
||||||
|
Optional<String> 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<ProcessorExecution>),
|
||||||
|
// inputSnapshot (ExchangeSnapshot), outputSnapshot (ExchangeSnapshot)
|
||||||
|
|
||||||
|
// ProcessorExecution: processorId, processorType, status, startTime, endTime, durationMs,
|
||||||
|
// children (List<ProcessorExecution>), diagramNodeId,
|
||||||
|
// inputSnapshot (ExchangeSnapshot), outputSnapshot (ExchangeSnapshot)
|
||||||
|
|
||||||
|
// ExchangeSnapshot: body (String), headers (Map<String,String>), properties (Map<String,String>)
|
||||||
|
|
||||||
|
// RouteGraph: routeId, nodes (List<RouteNode>), edges (List<RouteEdge>), processorNodeMapping (Map<String,String>)
|
||||||
|
// RouteNode: id, label, type (NodeType enum), properties (Map<String,String>)
|
||||||
|
// 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)
|
||||||
|
```
|
||||||
|
</interfaces>
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<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
|
||||||
|
</files>
|
||||||
|
<action>
|
||||||
|
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<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`:
|
||||||
|
- `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).
|
||||||
|
|
||||||
|
4. Extend `ExecutionRepository` interface with new query methods:
|
||||||
|
- `Optional<ExecutionDetail> 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<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>
|
||||||
|
</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>
|
||||||
|
|
||||||
|
<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
|
||||||
|
</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
|
||||||
|
- 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
|
||||||
|
</behavior>
|
||||||
|
<action>
|
||||||
|
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<String,String> 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
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>cd C:/Users/Hendrik/Documents/projects/cameleer3-server && mvn test -pl cameleer3-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>
|
||||||
|
|
||||||
|
</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)
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- 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
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
After completion, create `.planning/phases/02-transaction-search-diagrams/02-01-SUMMARY.md`
|
||||||
|
</output>
|
||||||
261
.planning/phases/02-transaction-search-diagrams/02-02-PLAN.md
Normal file
261
.planning/phases/02-transaction-search-diagrams/02-02-PLAN.md
Normal file
@@ -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"
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
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.
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<execution_context>
|
||||||
|
@C:/Users/Hendrik/.claude/get-shit-done/workflows/execute-plan.md
|
||||||
|
@C:/Users/Hendrik/.claude/get-shit-done/templates/summary.md
|
||||||
|
</execution_context>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
@.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
|
||||||
|
|
||||||
|
<interfaces>
|
||||||
|
<!-- Existing interfaces needed -->
|
||||||
|
|
||||||
|
From cameleer3-server-core/.../storage/DiagramRepository.java:
|
||||||
|
```java
|
||||||
|
public interface DiagramRepository {
|
||||||
|
void store(RouteGraph graph);
|
||||||
|
Optional<RouteGraph> findByContentHash(String contentHash);
|
||||||
|
Optional<String> findContentHashForRoute(String routeId, String agentId);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
From cameleer3-common (decompiled — diagram models):
|
||||||
|
```java
|
||||||
|
// RouteGraph: routeId (String), nodes (List<RouteNode>), edges (List<RouteEdge>),
|
||||||
|
// processorNodeMapping (Map<String,String>)
|
||||||
|
// RouteNode: id (String), label (String), type (NodeType), properties (Map<String,String>)
|
||||||
|
// 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)
|
||||||
|
</interfaces>
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<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
|
||||||
|
</files>
|
||||||
|
<action>
|
||||||
|
1. Add Maven dependencies to `cameleer3-server-app/pom.xml`:
|
||||||
|
```xml
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.elk</groupId>
|
||||||
|
<artifactId>org.eclipse.elk.core</artifactId>
|
||||||
|
<version>0.11.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.elk</groupId>
|
||||||
|
<artifactId>org.eclipse.elk.alg.layered</artifactId>
|
||||||
|
<version>0.11.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jfree</groupId>
|
||||||
|
<artifactId>org.jfree.svg</artifactId>
|
||||||
|
<version>5.0.7</version>
|
||||||
|
</dependency>
|
||||||
|
```
|
||||||
|
|
||||||
|
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<PositionedNode> — for compound/swimlane groups). JSON-serializable for the JSON layout response.
|
||||||
|
|
||||||
|
- `PositionedEdge` record: sourceId (String), targetId (String), label (String), points (List<double[]> — waypoints for edge routing). The points list contains [x,y] pairs from source to target.
|
||||||
|
|
||||||
|
- `DiagramLayout` record: width (double), height (double), nodes (List<PositionedNode>), edges (List<PositionedEdge>). 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).
|
||||||
|
</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>
|
||||||
|
</verify>
|
||||||
|
<done>ELK and JFreeSVG dependencies resolve, DiagramRenderer interface and layout DTOs compile in core module</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<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
|
||||||
|
</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
|
||||||
|
- 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 '<svg' or '<?xml'
|
||||||
|
- Integration test: GET /api/v1/diagrams/{hash} with Accept: application/json returns 200 with JSON containing 'nodes' and 'edges' arrays
|
||||||
|
- Integration test: GET /api/v1/diagrams/{nonexistent-hash} returns 404
|
||||||
|
- 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`:
|
||||||
|
|
||||||
|
**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 `<svg`, `<rect` or `<path`, node labels.
|
||||||
|
- Test layoutJson returns DiagramLayout with 3 nodes, all with positive x/y coordinates.
|
||||||
|
- Build a RouteGraph with CHOICE -> 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 "<svg".
|
||||||
|
- GET /api/v1/diagrams/{hash}/render with Accept: application/json -> 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).
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>cd C:/Users/Hendrik/Documents/projects/cameleer3-server && mvn test -pl cameleer3-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>
|
||||||
|
|
||||||
|
</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 clean verify` passes (all existing tests still green)
|
||||||
|
- SVG output contains color-coded nodes matching the NodeType color scheme
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- 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
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
After completion, create `.planning/phases/02-transaction-search-diagrams/02-02-SUMMARY.md`
|
||||||
|
</output>
|
||||||
354
.planning/phases/02-transaction-search-diagrams/02-03-PLAN.md
Normal file
354
.planning/phases/02-transaction-search-diagrams/02-03-PLAN.md
Normal file
@@ -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"
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
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.
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<execution_context>
|
||||||
|
@C:/Users/Hendrik/.claude/get-shit-done/workflows/execute-plan.md
|
||||||
|
@C:/Users/Hendrik/.claude/get-shit-done/templates/summary.md
|
||||||
|
</execution_context>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
@.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
|
||||||
|
|
||||||
|
<interfaces>
|
||||||
|
<!-- Core types created by Plan 01 — executor reads these from plan 01 SUMMARY -->
|
||||||
|
|
||||||
|
From cameleer3-server-core/.../search/SearchEngine.java:
|
||||||
|
```java
|
||||||
|
public interface SearchEngine {
|
||||||
|
SearchResult<ExecutionSummary> 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<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:
|
||||||
|
```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<ExecutionDetail> getDetail(String executionId);
|
||||||
|
// Internal: reconstructTree(parallel arrays) -> List<ProcessorNode>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
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<ProcessorNode> 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<ProcessorNode> 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
|
||||||
|
```
|
||||||
|
</interfaces>
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<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
|
||||||
|
</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}
|
||||||
|
- 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}
|
||||||
|
</behavior>
|
||||||
|
<action>
|
||||||
|
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<String> conditions and ArrayList<Object> 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.
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>cd C:/Users/Hendrik/Documents/projects/cameleer3-server && mvn test -pl cameleer3-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>
|
||||||
|
|
||||||
|
<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
|
||||||
|
</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
|
||||||
|
- 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
|
||||||
|
</behavior>
|
||||||
|
<action>
|
||||||
|
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<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`:
|
||||||
|
- 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.
|
||||||
|
</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>
|
||||||
|
</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>
|
||||||
|
|
||||||
|
</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 clean verify` passes (full suite green)
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- 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
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
After completion, create `.planning/phases/02-transaction-search-diagrams/02-03-SUMMARY.md`
|
||||||
|
</output>
|
||||||
Reference in New Issue
Block a user