From cbe411996fda7cc2a27b592671cc6dd24b2e52e3 Mon Sep 17 00:00:00 2001 From: Mikkel Georgsen Date: Fri, 10 Apr 2026 07:51:58 +0000 Subject: [PATCH] =?UTF-8?q?docs(07-01):=20complete=20research=20agent=20pl?= =?UTF-8?q?an=20=E2=80=94=20SearXNG=20client,=20ResearchAgent,=20trigger?= =?UTF-8?q?=20endpoint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../07-research-agent-search/07-01-SUMMARY.md | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 .planning/phases/07-research-agent-search/07-01-SUMMARY.md diff --git a/.planning/phases/07-research-agent-search/07-01-SUMMARY.md b/.planning/phases/07-research-agent-search/07-01-SUMMARY.md new file mode 100644 index 0000000..5de9297 --- /dev/null +++ b/.planning/phases/07-research-agent-search/07-01-SUMMARY.md @@ -0,0 +1,90 @@ +--- +phase: 07-research-agent-search +plan: "01" +subsystem: research-agent +tags: [research, searxng, ai-enrichment, background-worker] +dependency_graph: + requires: [internal/ai, internal/netbox, internal/inventory, internal/config] + provides: [internal/research, internal/api/handlers/research] + affects: [cmd/hwlab/main.go, internal/api/router.go] +tech_stack: + added: [internal/research package] + patterns: [interface-injection for testability, TDD red-green, background ticker worker] +key_files: + created: + - internal/research/searxng.go + - internal/research/searxng_test.go + - internal/research/agent.go + - internal/research/agent_test.go + - internal/api/handlers/research.go + modified: + - internal/ai/client.go + - internal/netbox/client.go + - internal/config/config.go + - internal/api/router.go + - cmd/hwlab/main.go +decisions: + - "Used interface injection (NetBoxer, TextCompleter, CatalogTransitioner) in Agent instead of concrete types to enable stub-based unit tests without live NetBox" + - "Added TierClient.TextComplete as separate method rather than reusing AnalyzePhotos to keep vision and text paths distinct" + - "SanitizeQuery exported (capitalised) to allow external test package verification of T-07-01 mitigation" + - "Agent.RunOnce returns (int, error) rather than just error so callers and tests can assert enrichment count" +metrics: + duration_seconds: 256 + completed_date: "2026-04-10" + tasks_completed: 2 + tasks_total: 2 + files_created: 5 + files_modified: 5 +--- + +# Phase 07 Plan 01: SearXNG Client + ResearchAgent Summary + +**One-liner:** SearXNG HTTP client + background ResearchAgent that polls NetBox for `needs_research` items, enriches via SearXNG search + Tier 2 LLM text completion, patches NetBox custom fields, and transitions status to `researched` every 10 minutes or on-demand via `POST /api/research/trigger`. + +## Tasks Completed + +| Task | Name | Commit | Key Files | +|------|------|--------|-----------| +| 1 | SearXNG client + netbox.ListDevicesWithStatus | 30cd279 | internal/research/searxng.go, internal/netbox/client.go, internal/config/config.go | +| 2 | ResearchAgent worker + main.go wiring + trigger endpoint | 0072aa4 | internal/research/agent.go, internal/api/handlers/research.go, internal/api/router.go, cmd/hwlab/main.go | + +## Decisions Made + +1. **Interface injection for Agent dependencies** — `NetBoxer`, `TextCompleter`, and `CatalogTransitioner` interfaces in `internal/research/agent.go` allow stub injection in tests without a live NetBox or LLM. The concrete `*netbox.Client` and `*ai.TierClient` satisfy these interfaces automatically. + +2. **TierClient.TextComplete as distinct method** — Rather than forcing research prompts through `AnalyzePhotos` (which builds a vision multipart message), added a clean `TextComplete(ctx, prompt) (string, error)` method on `TierClient` that posts a simple single-user-message ChatCompletion. This keeps the vision and text paths separate and makes intent clear. + +3. **SanitizeQuery exported** — The T-07-01 threat mitigation (strip `[^a-zA-Z0-9 .\-_]+` before SearXNG dispatch) is tested from the `_test` package, which requires the function to be exported. Consistent with the plan's explicit mention of adversarial input testing. + +4. **Product URL fallback** — If the LLM does not return a `product_url` in its JSON response, the agent falls back to the first SearXNG result URL. This ensures `product_url` is populated even when the LLM provides incomplete JSON. + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 1 - Bug] Test expectation for sanitizeQuery with `/` character** +- **Found during:** Task 2 GREEN phase +- **Issue:** Test case `"Dell"` expected `"Dell script alert 1 /script "` but `/` is correctly stripped by the `[^a-zA-Z0-9 .\-_]+` regex +- **Fix:** Updated test expectation to `"Dell script alert 1 script"` (correct behavior) +- **Files modified:** internal/research/agent_test.go +- **Commit:** 0072aa4 + +None otherwise — plan executed as written. + +## Known Stubs + +None. All interfaces are fully implemented. The `NoOpResearchClient` in `internal/ai/research.go` is still present but is no longer used — it has been superseded by `research.SearXNGClient` wired in `main.go`. + +## Threat Surface Scan + +No new trust boundaries introduced beyond those documented in the plan's threat model. T-07-01 (query sanitization) is mitigated and tested. T-07-03 (trigger DoS) is mitigated — `RunOnce` is bounded per-item with no queuing amplification. + +## Self-Check: PASSED + +- internal/research/searxng.go: FOUND +- internal/research/agent.go: FOUND +- internal/api/handlers/research.go: FOUND +- Commit 30cd279: FOUND +- Commit 0072aa4: FOUND +- `go build ./...`: PASSES +- `go test ./internal/research/...`: 9/9 PASS