This commit is contained in:
2026-05-31 17:38:43 +03:00
parent e17f4f64a9
commit 652de471d2
11 changed files with 1387 additions and 442 deletions

View File

@@ -1,105 +1,393 @@
# Tasks: LLM Analysis & Documentation Plugins
# Tasks: LLM Analysis & Documentation Plugins v2
**Feature**: `017-llm-analysis-plugin`
**Status**: Completed
**Spec**: [specs/017-llm-analysis-plugin/spec.md](spec.md)
**Status**: v1 Completed; v2 Planned
**Spec**: [spec.md](spec.md) | **Plan**: [plan.md](plan.md) | **Data Model**: [data-model.md](data-model.md)
## User Stories & Priorities
| ID | Story | Priority | Phase |
|----|-------|----------|-------|
| US1 | Create & Schedule Validation Task | P1 | 3 |
| US2 | Validation Task List & History | P1 | 3 |
| US3 | Dual-Path Dashboard Health Analysis | P1 | 4, 5 |
| US4 | URL Parsing Integration | P1 | 6 |
| US5 | Provider/Prompt Per-Task + Settings Cleanup | P2 | 7 |
| US6 | Dashboard Hub Cleanup | P2 | 8 |
| US7 | Dataset Documentation (unchanged) | P2 | — |
| US8 | Git Commit Suggestion (unchanged) | P3 | — |
## Dependencies
- **Phase 1 (Setup)**: Must run first.
- **Phase 2 (Foundational)**: Must complete before any User Stories.
- **Phase 3 (US1 - Dashboard Analysis)**: Depends on Phase 2.
- **Phase 4 (US2 - Documentation)**: Depends on Phase 2. Parallel with Phase 3.
- **Phase 5 (US3 - Configuration)**: Depends on Phase 2. Recommended before Phase 3/4 for easier testing, but technically parallelizable if config is mocked.
- **Phase 6 (US4 - Git Integration)**: Independent of other stories, depends on Phase 2.
```
Phase 1 (Setup)
└─→ Phase 2 (Foundational)
├─→ Phase 3 (US1 + US2) — Task CRUD
│ └─→ Phase 7 (US5) — Provider/Prompt
├─→ Phase 4 (US3 Path B) — Text-only execution
├─→ Phase 5 (US3 Path A) — Multi-chunk screenshots
├─→ Phase 6 (US4) — URL parsing
└─→ Phase 8 (US6) — Hub cleanup (independent)
└─→ Phase 9 (Polish)
```
## Phase 1: Setup
**Parallel**: Phase 4 + 5 + 6 can run concurrently after Phase 2. Phase 7 is tight with Phase 3. Phase 8 is independent.
**Goal**: Initialize project structure and dependencies.
---
- [x] T001 Install backend dependencies (openai, playwright, tenacity) in `backend/requirements.txt`
- [x] T002 Create plugin directory structure `backend/src/plugins/llm_analysis/`
- [x] T003 Initialize `backend/src/plugins/llm_analysis/__init__.py`
- [x] T004 Create `backend/src/plugins/llm_analysis/models.py` for Pydantic models (LLMProviderConfig, ValidationResult)
- [x] T005 Update `backend/src/core/plugin_loader.py` to recognize new plugin type if necessary
- [x] T006 Create `backend/src/api/routes/llm.py` placeholder
- [x] T007 Register new route in `backend/src/app.py`
## Phase 1: Setup — Database Migrations
## Phase 2: Foundational
**Goal**: Schema changes for v2 models. Blocks everything.
**Goal**: Implement core services and shared infrastructure.
- [ ] T001 [P] Add `sources` ORM relationship + `source_snapshot` JSON (audit only, nullable) to `ValidationPolicy` in `backend/src/models/llm.py`. No JSON denormalization column for authoritative state — relational `ValidationSource` table is the single source of truth.
- [ ] T002 [P] Create `ValidationSource` SQLAlchemy model in `backend/src/models/llm.py` (id, policy_id FK, type, value, parsed_context JSON, is_valid, title, last_error)
- [ ] T003 [P] Add new columns to `ValidationPolicy` model: `provider_id` (String), `prompt_template` (Text, nullable), `screenshot_enabled` (Boolean, default true), `logs_enabled` (Boolean, default true), `execute_chart_data` (Boolean, default false), `llm_batch_size` (Integer, default 1 — full isolation), `policy_dashboard_concurrency_limit` (Integer, default 3) in `backend/src/models/llm.py`
- [ ] T003a [P] Add `global_validation_worker_limit` config setting (default: 3) — max concurrent policy runs across all tasks. Stored in app config, not per-policy. in `backend/src/core/config_models.py`
- [ ] T004 [P] Add new columns to `ValidationRecord` model: `execution_path` (String), `source_id` (FK → ValidationSource, nullable), `dataset_health` (JSON, nullable), `chart_data_results` (JSON, nullable), `tab_screenshots` (JSON, nullable), `token_usage` (JSON, nullable), `dataset_key` (String, nullable) in `backend/src/models/llm.py`
- [ ] T004a [P] Create `ValidationRun` SQLAlchemy model in `backend/src/models/llm.py`: id, policy_id (FK), task_id (nullable), started_at, finished_at, trigger (manual/scheduled), status (completed/partial/failed), dashboard_count, pass_count, warn_count, fail_count, unknown_count
- [ ] T005 Create Alembic migration for all T001T004a schema changes in `backend/alembic/versions/`
- [ ] T006 Write data migration script: convert existing `dashboard_ids: list[str]``sources: [{type: "dashboard_id", value: id, is_valid: true}]` in `backend/src/plugins/llm_analysis/migrations/`
- [ ] T007 [P] Add `DEFAULT_LLM_PROMPTS["dashboard_validation_prompt_multimodal"]` (Path A, image-focused) and `DEFAULT_LLM_PROMPTS["dashboard_validation_prompt_text"]` (Path B, topology-focused) to `backend/src/services/llm_prompt_templates.py`
- [x] T008 Implement `LLMProviderService` in `backend/src/services/llm_provider.py` (CRUD for providers, AES-256 encryption)
- [x] T009 Implement `ScreenshotService` in `backend/src/plugins/llm_analysis/service.py` (Playwright + API strategies, fallback logic, 1920px width, full page height, CDP screenshot, recursive tab switching)
- [x] T010 Implement `LLMClient` in `backend/src/plugins/llm_analysis/service.py` (OpenAI SDK wrapper, retry logic with tenacity, rate limit handling)
- [x] T011 Create `backend/src/plugins/llm_analysis/plugin.py` with `PluginBase` implementation stubs
- [x] T012 Define database schema updates for `LLMProviderConfig` in `backend/src/models/llm.py` (or appropriate location)
- [x] T013 Run migration to create new tables (if using SQLAlchemy/Alembic) or update SQLite schema
**Verification**: `pytest backend/tests/models/test_llm_v2.py -v` — all models create/query correctly. Migration runs without data loss.
## Phase 3: Dashboard Health Analysis (US1)
---
**Goal**: Enable automated dashboard validation with multimodal analysis.
## Phase 2: Foundational — Services & Refactored Plugin Core
- [x] T014 [US1] Implement `validate_dashboard` task logic in `backend/src/plugins/llm_analysis/plugin.py`
- [x] T015 [US1] Implement log retrieval logic (fetch recent logs, limit 100 lines/24h) in `backend/src/plugins/llm_analysis/plugin.py`
- [x] T016 [US1] Construct multimodal prompt (image + text) in `backend/src/plugins/llm_analysis/service.py` (implement PII/credential filtering)
- [x] T017 [US1] Implement result parsing and persistence (ValidationResult) in `backend/src/plugins/llm_analysis/plugin.py` (ensure JSON structure: status, issues, summary, log results to task output with `[ANALYSIS_SUMMARY]` and `[ANALYSIS_ISSUE]` markers)
- [x] T018 [US1] Add `validate` endpoint trigger in `backend/src/api/routes/tasks.py` (or reuse existing dispatch)
- [x] T019 [US1] Implement notification dispatch (Email/Pulse) on failure in `backend/src/plugins/llm_analysis/plugin.py` (Summary + Link format)
- [x] T020 [US1] Create `frontend/src/components/llm/ValidationReport.svelte` for viewing results
- [x] T021 [US1] Add "Validate" button with loading state and disabling logic to `frontend/src/components/DashboardGrid.svelte` (FR-019)
- [x] T022 [US1] Enable scheduling for validation tasks in `backend/src/core/scheduler.py` (if not automatic via TaskManager)
**Goal**: `ValidationTaskService`, `DatasetHealthChecker`, refactored `ScreenshotService` (multi-chunk), `LLMClient` dual-mode. Blocks US1US6.
## Phase 4: Automated Dataset Documentation (US2)
### 2.1 ValidationTaskService
**Goal**: Generate and persist dataset documentation.
- [ ] T008 [P] Implement `ValidationTaskService.create_task()` — creates `ValidationPolicy` + `ValidationSource[]` rows, validates provider_id against execution path, in `backend/src/services/validation_service.py`
- [ ] T009 [P] Implement `ValidationTaskService.get_task(id)` — returns policy + sources with current status, in `backend/src/services/validation_service.py`
- [ ] T010 [P] Implement `ValidationTaskService.list_tasks(env_id)` — returns all policies with aggregated last-run status, in `backend/src/services/validation_service.py`
- [ ] T011 [P] Implement `ValidationTaskService.update_task(id, data)` — updates policy fields + sources, in `backend/src/services/validation_service.py`
- [ ] T012 [P] Implement `ValidationTaskService.delete_task(id)` — deletes policy + cascades to sources + records, in `backend/src/services/validation_service.py`
- [ ] T013 [P] Implement `ValidationTaskService.trigger_run(policy_id)` — checks FR-054 (same task not already running via ValidationRun with status=running), creates a `ValidationRun` record (FR-055d), enqueues dashboard validation tasks per FR-055 (FIFO with `global_validation_worker_limit`), in `backend/src/services/validation_service.py`
- [ ] T014 [P] Implement `ValidationTaskService.get_run_history(policy_id, page)` — paginated `ValidationRun` list with aggregate counts, in `backend/src/services/validation_service.py`
- [ ] T015 [P] Implement `ValidationTaskService.get_run_detail(run_id)` — full run with all dashboard records, aggregate counts, in `backend/src/services/validation_service.py`
- [ ] T015a [P] Implement `ValidationTaskService._finalize_run(run_id)` — computes aggregate counts from all `ValidationRecord`s with this `run_id`, sets finished_at and status, in `backend/src/services/validation_service.py`
- [x] T023 [US2] Implement `generate_documentation` task logic in `backend/src/plugins/llm_analysis/plugin.py`
- [x] T024 [US2] Implement metadata fetching (schema, columns) in `backend/src/plugins/llm_analysis/plugin.py`
- [x] T025 [US2] Construct documentation prompt in `backend/src/plugins/llm_analysis/service.py` (handle schema changes)
- [x] T026 [US2] Implement metadata update logic (write back to DB) in `backend/src/services/mapping_service.py` (handle partial failures with rollback, ignore invalid columns)
- [x] T045 [US2] Implement optimistic locking for concurrent metadata updates
- [x] T027 [US2] Add "Generate Docs" button to `frontend/src/components/tools/MapperTool.svelte` using standard system icons (FR-021)
- [x] T028 [US2] Create feedback/preview UI component `frontend/src/components/llm/DocPreview.svelte` (optional but recommended)
### 2.2 DatasetHealthChecker (Path B)
## Phase 5: LLM Provider Configuration (US3)
- [ ] T016 [P] Implement `DatasetHealthChecker` class in `backend/src/plugins/llm_analysis/service.py`:
- `check_dataset_health(dataset_id: int, client: SupersetClient) → DatasetHealth` — calls `GET /api/v1/dataset/{id}`, extracts database name, backend, kind, verifies accessibility
- `check_dashboard_datasets(dashboard_id: int, chart_list: list[dict], client: SupersetClient, execute_data: bool) → tuple[list[DatasetHealth], list[ChartDataResult]]`
**Goal**: Manage LLM providers via UI.
### 2.3 ScreenshotService Refactor (multi-chunk for Path A)
- [x] T029 [US3] Implement CRUD endpoints for providers in `backend/src/api/routes/llm.py`
- [x] T030 [US3] Create `frontend/src/components/llm/ProviderConfig.svelte` form
- [x] T031 [US3] Create `frontend/src/routes/admin/settings/llm/+page.svelte` settings page
- [x] T032 [US3] Implement "Test Connection" button logic with Toast feedback (FR-020)
- [x] T033 [US3] Ensure API keys are masked in frontend responses
- [ ] T017 [P] Implement `ScreenshotService.capture_dashboard_chunks(dashboard_id, parsed_filters, output_dir) → list[{tab_name, path}]` in `backend/src/plugins/llm_analysis/service.py`:
- Login → navigate with filters/tabs → iterate tabs → per-tab viewport (1920×1200) → CDP screenshot → collect PNG paths
## Phase 6: Git Commit Message Suggestion (US4)
### 2.3b Image Pipeline (PNG → JPEG + WebP)
**Goal**: AI-assisted commit messages.
- [ ] T017a [P] Implement `ScreenshotService._convert_screenshots_for_llm(png_paths: list[str]) → list[str]` in `backend/src/plugins/llm_analysis/service.py`:
- PNG → Pillow convert → JPEG quality=60, max 1024px → save as temp JPEG → return paths
- JPEG files deleted after LLM call succeeds
- [ ] T017b [P] Implement `ScreenshotService._archive_screenshots_as_webp(png_paths: list[str], output_dir: str) → list[{tab_name, webp_path}]` in `backend/src/plugins/llm_analysis/service.py`:
- PNG → Pillow convert → WebP quality=80, lossy → save to `{output_dir}/{tab_name}_{timestamp}.webp`
- Delete PNG after WebP saved
- Fallback: if WebP encode fails → keep PNG, log WARN (FR-059 edge case)
- [ ] T017c [P] Implement `ScreenshotService._cleanup_temp_files(paths: list[str])` in `backend/src/plugins/llm_analysis/service.py`:
- Delete PNG and JPEG intermediates after successful archive + LLM call
- [x] T034 [US4] Implement `generate_commit_message` logic in `backend/src/plugins/git/llm_extension.py` (validate conventional commit format)
- [x] T035 [US4] Create endpoint `POST /api/git/generate-message` in `backend/src/api/routes/git.py`
- [x] T036 [US4] Construct commit generation prompt (Diff + History) in `backend/src/plugins/git/llm_extension.py`
- [x] T037 [US4] Add "Generate" button to `frontend/src/components/git/CommitModal.svelte`
- [x] T038 [US4] Wire up frontend to call generation endpoint and populate textarea (handle empty/invalid response with Toast)
### 2.4 LLMClient Dual-Mode
## Phase 7: Polish & Cross-Cutting
- [ ] T018 [P] Implement `LLMClient.analyze_dashboard_multimodal(screenshot_paths: list[str], logs: list[str], prompt: str) → dict` in `backend/src/plugins/llm_analysis/service.py`:
- Sends all images in single `content[]` array → multimodal LLM → JSON result
- [ ] T019 [P] Implement `LLMClient.analyze_dashboard_text(topology_text: str, dataset_health: list[dict], logs: list[str], prompt: str) → dict` in `backend/src/plugins/llm_analysis/service.py`:
- Text-only LLM call with dashboard topology → JSON result
- [ ] T020 [P] Implement `LLMClient._estimate_payload_size()` and `LLMClient._reduce_image_quality()` in `backend/src/plugins/llm_analysis/service.py`:
- FR-056: estimate → progressive reduction (JPEG quality 60→30, width 1024→800) → Path B fallback
- **Belief-runtime**: `belief_scope("payload_reduction")` + `reason()` before each reduction + `reflect()` after outcome
**Goal**: Finalize and verify.
### 2.5 Plugin Refactor (dispatch to Path A / Path B)
- [x] T039 Verify all permissions (`plugin:llm:validate`, etc.) are registered and enforceable
- [x] T040 Verify i18n strings for all new UI components
- [x] T041 Run full end-to-end test of Dashboard Validation flow (including fallback scenarios)
- [x] T042 Run full end-to-end test of Documentation flow
- [x] T043 Update `README.md` with new plugin capabilities
- [x] T044 Verify API key masking in all UI responses and logs
- [ ] T021 Implement `DashboardValidationPlugin._build_dashboard_topology(dashboard_data: dict, charts_data: list[dict]) → str` in `backend/src/plugins/llm_analysis/plugin.py`:
- Recursive `position_json` parsing → tab → row → chart hierarchy → text description
- [ ] T022 Refactor `DashboardValidationPlugin.execute()` to dispatch based on `screenshot_enabled`:
- True → `_execute_path_a()`: call ScreenshotService → LLMClient.analyze_dashboard_multimodal()
- False → `_execute_path_b()`: call API chain → DatasetHealthChecker → LLMClient.analyze_dashboard_text()
- **Belief-runtime**: `belief_scope("execute_path_a")` / `belief_scope("execute_path_b")` + `reason()` at entry + `reflect()` at exit
## Implementation Strategy
### 2.7 Security & Retention (new)
1. **MVP**: Focus on Phase 1, 2, 3, and 5. This delivers the core value (Dashboard Validation) and the necessary configuration.
2. **Increment 1**: Add Phase 4 (Documentation).
3. **Increment 2**: Add Phase 6 (Git Integration).
- [ ] T024a [P] Implement `RedactionService` in `backend/src/plugins/llm_analysis/service.py`:
- `redact_logs(logs: list[str]) → list[str]` — strip PII, credentials, query text, tokens from URLs, native filter values
- `redact_raw_response(raw: str) → str` — strip sensitive data before persistence
- Applied at capture boundary (before LLM send + before DB save) per FR-029/FR-029a
- [ ] T024b [P] Add retention config to app settings: `LLM_SCREENSHOT_RETENTION_DAYS=30`, `LLM_RAW_RESPONSE_RETENTION_DAYS=30` in `backend/src/core/config_models.py`
- [ ] T024c [P] Rename all «molecular-cot» references in logs to «decision audit logging»: REASON/REFLECT markers use structured JSON with deterministic metadata only (no free-form reasoning text, no PII, no screenshot content) per FR-029b. Update docstrings in `backend/src/core/logger.py` and skill files.
## Parallel Execution Examples
- [ ] T023 [P] Add `dashboard_validation_prompt_multimodal` (Path A) template to `DEFAULT_LLM_PROMPTS` in `backend/src/services/llm_prompt_templates.py`:
- «Analyze the attached {N} dashboard tab screenshots and execution logs for visual and data health issues...»
- [ ] T024 [P] Add `dashboard_validation_prompt_text` (Path B) template to `DEFAULT_LLM_PROMPTS` in `backend/src/services/llm_prompt_templates.py`:
- «Analyze the following dashboard topology, dataset health, and execution logs for data consistency and KXD connectivity issues...»
- **Backend/Frontend Split**: T020 (Frontend Report) can be built while T014-T017 (Backend Logic) are in progress, using mock data.
- **Story Parallelism**: US2 (Documentation) and US4 (Git) are largely independent and can be worked on simultaneously by different developers (or sequentially without blocking).
**Verification**: `pytest backend/tests/services/test_validation_task_service.py backend/tests/plugins/llm_analysis/test_service.py -v`
---
## Phase 3: US1 + US2 — Task CRUD API + Task List Page
**Goal**: REST API for validation tasks + frontend task list and create/edit form.
### 3.1 API Routes
- [ ] T025 [US1] Implement `POST /api/validation-tasks` — creates `ValidationPolicy` + `ValidationSource[]` in `backend/src/api/routes/validation_tasks.py`
- [ ] T026 [US1] Implement `GET /api/validation-tasks` — list all tasks with aggregated last-run status in `backend/src/api/routes/validation_tasks.py`
- [ ] T027 [US2] Implement `GET /api/validation-tasks/{policy_id}` — task detail + per-source status in `backend/src/api/routes/validation_tasks.py`
- [ ] T028 [US1] Implement `PUT /api/validation-tasks/{policy_id}` — update task in `backend/src/api/routes/validation_tasks.py`
- [ ] T029 [US1] Implement `DELETE /api/validation-tasks/{policy_id}` — delete task (cascade sources + records), in `backend/src/api/routes/validation_tasks.py`
- [ ] T030 [US1] Implement `POST /api/validation-tasks/{policy_id}/run` — manual trigger (checks FR-054 + FR-055) in `backend/src/api/routes/validation_tasks.py`
- [ ] T031 [US2] Implement `GET /api/validation-tasks/{policy_id}/runs` — paginated run history (ValidationRun list, not individual records) in `backend/src/api/routes/validation_tasks.py`
- [ ] T032 [US2] Implement `GET /api/validation-tasks/{policy_id}/runs/{run_id}` — full run detail: run metadata + all per-dashboard ValidationRecords with dataset_health, screenshots, logs, timings, in `backend/src/api/routes/validation_tasks.py`
- [ ] T032b Implement legacy redirect: `GET /reports/llm/{taskId}` → 302 to `/validation-tasks/{policy_id}/runs/{run_id}` when `taskId` maps to `ValidationRun.task_id`, in `backend/src/api/routes/validation_tasks.py`
### 3.2 Task List Page
- [ ] T035 [US2] Create `frontend/src/routes/validation-tasks/+page.svelte` — task list page with columns: name, schedule, last run status (PASS/WARN/FAIL/UNKNOWN), dashboard count, status (active/inactive), actions (edit/delete/run now)
- [ ] T036 [US2] Create `frontend/src/routes/validation-tasks/+page.js` — load function calling `GET /api/validation-tasks`
### 3.3 Task Create/Edit Form
- [ ] T037 [US1] Create `frontend/src/routes/validation-tasks/new/+page.svelte` — task creation page
- [ ] T038 [US1] Create `frontend/src/components/llm/ValidationTaskForm.svelte` — multi-step form component:
- Step 1: Name, description, environment selector
- Step 2: Sources — dashboard ID multi-select + URL paste tabs
- Step 3: Analysis options — provider selector (filtered per FR-050), prompt textarea (pre-filled with path-specific default: multimodal when screenshot_enabled=true, text when false), screenshot toggle, logs toggle, execute_chart_data toggle (Path B only). When toggling screenshot_enabled on a task with custom prompt → show warning «Prompt may no longer match execution path» (FR-048a)
- Step 4: Schedule — cron input + preview next runs
- [ ] T039 [US1] Create `frontend/src/routes/validation-tasks/[policyId]/edit/+page.svelte` — task edit page (reuses `ValidationTaskForm`)
### 3.5 Task Detail Page + Report Page
- [ ] T040 [US2] Create `frontend/src/routes/validation-tasks/[policyId]/+page.svelte` — task detail page:
- Task info (name, environment, schedule, provider)
- Per-source breakdown table: dashboard title, last status, issues count, last run timestamp, link to latest run
- Run history table: timestamp, trigger type, aggregate status (N PASS / M WARN / K FAIL), duration, link to run detail
- Actions: Run Now, Edit, Delete
- [ ] T041 [US2] Create `frontend/src/routes/validation-tasks/[policyId]/+page.js` — load function calling `GET /api/validation-tasks/{id}` + `GET /api/validation-tasks/{id}/runs`
- [ ] T041a [US2] Create `frontend/src/routes/validation-tasks/[policyId]/runs/[runId]/+page.svelte` — multi-dashboard run report page (FR-055b):
- Run header: policy name, timestamp, trigger, duration, aggregate badge (e.g. «3 PASS • 1 WARN • 0 FAIL»)
- Collapsible dashboard list: each row = one dashboard, shows status icon + issue count + mini-summary
- Expanded dashboard row: full issues list + execution path badge (Path A/B) + dataset health table (Path B) + screenshot thumbnails with tab selector (Path A) + logs sent to LLM + task execution logs
- Handles mixed runs: some dashboards Path A, some Path B — renders appropriate sections per dashboard
- [ ] T041b Implement `GET /api/validation-tasks/{policy_id}/runs/{run_id}` — returns full ValidationRun with all dashboard records, each containing dataset_health, chart_data_results, tab_screenshots, token_usage, timings. Also resolves policy name, provider name, and dashboard titles for display.
### 3.6 Shared Report Component
- [ ] T042 [US1] Create `frontend/src/components/llm/ValidationTaskReport.svelte` — shared report component:
- Check result badge (PASS/WARN/FAIL) with `getCheckResultClasses()`
- Summary text
- Issues table (severity, message, location)
- Execution path indicator (Path A icon / Path B icon)
- Placeholder slots for Path A (screenshots) and Path B (dataset health)
**Verification**: Create task via UI → list shows task → detail shows sources → run now → report renders.
---
## Phase 4: US3 — Path B Execution (Text-Only + Batching)
**Goal**: Text-only validation with dataset health checking, optional chart data execution, and LLM batching.
- [ ] T043 [US3] Implement `DashboardValidationPlugin._execute_path_b(run_id, dashboard_sources, params, context)` in `backend/src/plugins/llm_analysis/plugin.py`:
1. For each dashboard: collect topology via `_build_dashboard_topology()` + dataset health via `DatasetHealthChecker` + logs
2. Default: `llm_batch_size=1` — one LLM call per dashboard (full isolation). Values >1 group dashboards into batches per FR-045a (experimental, documented accuracy risk in research decision 15). Path A dashboards excluded from batching
3. For each batch: build structured prompt with per-dashboard labeled sections + strict JSON response schema `{dashboards: [{dashboard_id, status, summary, issues}]}`
4. Call `LLMClient.analyze_dashboard_text_batch(dashboard_payloads, prompt)` → parse response
5. Missing/parse-error dashboards → mark UNKNOWN individually, preserve other dashboards in batch
6. Persist individual `ValidationRecord` per dashboard, linked to `run_id`
- **Belief-runtime**: `belief_scope("execute_path_b_batch")` + `reason(f"batch {i}/{total}: {n} dashboards")` + `reflect()` with token_usage per batch
- [ ] T043a [US3] Implement `LLMClient.analyze_dashboard_text_batch(payloads: list[dict], prompt: str) → dict` in `backend/src/plugins/llm_analysis/service.py`:
- Builds prompt: per-dashboard labeled sections, enforced `{dashboards: [{dashboard_id, ...}]}` JSON schema
- Validates response completeness: every input dashboard_id must appear; missing → UNKNOWN fallback per-dashboard (not whole batch)
- [ ] T044 [US3] Create `frontend/src/components/llm/DatasetHealthTable.svelte`
- [ ] T045 [US3] Create `frontend/src/components/llm/ChartDataResults.svelte`
**Rejected-path regression test**:
- [ ] T047 [US3] Add test: Path B batch of 5 dashboards, 1 with KXD error → only that dashboard UNKNOWN, other 4 preserved with valid results, in `backend/tests/plugins/llm_analysis/test_path_b_batch.py`
- [ ] T047a [US3] Add test: batch_size=1 (full isolation) → each dashboard gets individual LLM call with no cross-contamination, in `backend/tests/plugins/llm_analysis/test_path_b_isolated.py`
**Verification**: Create task with 5 Path B dashboards, `llm_batch_size=1` (default) → 5 individual LLM calls, full isolation. Opt-in `llm_batch_size=3` → 2 LLM calls, structured prompt with per-dashboard isolation. Dashboard with broken dataset → only its record UNKNOWN, others preserved.
---
## Phase 5: US3 — Path A Execution (Multi-Chunk Screenshots)
**Goal**: Per-tab screenshots + multimodal LLM + payload estimation.
- [ ] T048 [US3] Implement `DashboardValidationPlugin._execute_path_a(params, context)` in `backend/src/plugins/llm_analysis/plugin.py`:
1. Parse URL filters if source is URL type (re-parse per FR-057)
2. `ScreenshotService.capture_dashboard_chunks()` → per-tab screenshots
3. `GET /api/v1/log/` → execution logs
4. `LLMClient._estimate_payload_size()` → check token budget per FR-056
5. Quality reduction loop if needed (FR-056)
6. `LLMClient.analyze_dashboard_multimodal()` → JSON result
7. Fallback to `_execute_path_b()` if payload still exceeds limit
8. Persist `ValidationRecord` with `execution_path="screenshot"`, `tab_screenshots`, `token_usage`
- **Belief-runtime**: `belief_scope("execute_path_a")` + `reason()` before payload reduction + `reflect()` after each reduction + `reason()` on Path B fallback
- [ ] T049 [US3] Update `frontend/src/routes/validation-tasks/[policyId]/runs/[runId]/+page.svelte` — add Path A sections: tab selector (from `tab_screenshots`), per-tab image viewer with zoom, issues table with tab/chart location. Legacy `/reports/llm/[taskId]` is a redirect-only passthrough — NOT updated.
**Rejected-path regression test**:
- [ ] T050 [US3] Add test: multi-chunk screenshots exceed token limit → quality reduction triggered → Path B fallback when still exceeded, logged via molecular-cot, in `backend/tests/plugins/llm_analysis/test_payload_reduction.py`
**Verification**: Create task with `screenshot_enabled=true`, 5-tab dashboard → report shows per-tab screenshots + issues.
---
## Phase 6: US4 — URL Parsing Integration
**Goal**: Paste Superset URL → parse → use in task creation + execution.
- [ ] T051 [US4] Implement `POST /api/validation-tasks/parse-url` in `backend/src/api/routes/validation_tasks.py`:
- Receives `{url, environment_id}`
- Calls `SupersetContextExtractor.parse_superset_link(url)`
- Returns `{dashboard_id, title, native_filters, activeTabs, anchor, partial_recovery, warnings}`
- [ ] T052 [US4] Create `frontend/src/components/llm/UrlParser.svelte`:
- URL paste input with debounced parse
- Loading state during API call
- Success: green banner with dashboard title, native_filters count, activeTabs list
- Error: red banner with `partial_recovery` warnings or parse failure message
- Copy button: «Dashboard structure changed? Paste new URL»
- [ ] T053 [US4] Integrate `UrlParser` into `ValidationTaskForm.svelte` — add «Paste URL» tab in source selector (Step 2)
- [ ] T054 [US4] Implement URL re-parse on execution per FR-057 in `DashboardValidationPlugin`:
- For URL-type sources: call `SupersetContextExtractor.parse_superset_link()` on every run
- If fails → mark source `is_valid=false`, set `last_error`, log WARN
- If dashboard_id changed → log WARN, use new ID
**Rejected-path regression test**:
- [ ] T055 [US4] Add test: URL source with deleted dashboard → re-parse fails → source marked invalid, error logged, task skips source and continues with other valid ones, in `backend/tests/plugins/llm_analysis/test_url_reparse.py`
**Verification**: Paste Superset URL with filters → form shows dashboard title + 3 native filters + 2 active tabs → create task → execution re-parses URL successfully.
---
## Phase 7: US5 — Provider/Prompt Per-Task + Settings Cleanup
**Goal**: Remove `dashboard_validation` binding from LLM settings. Provider + prompt configured per-task.
- [ ] T056 [US5] Remove `"dashboard_validation": ""` from `DEFAULT_LLM_PROVIDER_BINDINGS` in `backend/src/services/llm_prompt_templates.py`
- [ ] T057 [US5] Remove dashboard_validation binding UI row from `frontend/src/routes/admin/settings/llm/+page.svelte`
- [ ] T058 [US5] Update LLM settings API schema to exclude `dashboard_validation` from provider_bindings payload in `backend/src/schemas/llm.py`
- [ ] T059 [US5] Implement provider selector filter logic in `ValidationTaskForm.svelte` (Step 3):
- `screenshot_enabled=true` → show only multimodal providers (`is_multimodal=true`)
- `screenshot_enabled=false` → show all active providers
- [ ] T060 [US5] Add `ValidationTaskService.create_task()` validation: reject if `screenshot_enabled=true` but provider `is_multimodal=false`
**Rejected-path regression test**:
- [ ] T061 [US5] Add test: create task with `screenshot_enabled=true` and non-multimodal provider → HTTP 400, in `backend/tests/api/test_validation_tasks_api.py`
- [ ] T061a [US5] Implement `DELETE /api/llm/providers/{id}` enforcement: query active ValidationPolicy references, return 409 with `{error, blocking_tasks: [{id, name}]}` (FR-058), in `backend/src/api/routes/llm.py`
- [ ] T062 [US5] Add test: delete provider with active tasks → HTTP 409 with blocking task list (FR-058), in `backend/tests/api/test_llm_provider_delete.py`
- [ ] T063 [US5] Add test: POST /api/tasks with `plugin_id=llm_dashboard_validation` → HTTP 400 with migration message (FR hard cut), in `backend/tests/api/test_tasks_deprecated_llm.py`
**Verification**: LLM settings page shows only 3 bindings. Create task with multimodal provider → works. Create with non-multimodal + screenshot → rejected.
---
## Phase 8: US6 — Dashboard Hub Cleanup + Validation Indicator
**Goal**: Remove Validate button + LLM status column. Add lightweight validation indicator per dashboard row + detail page history.
- [ ] T064 [US6] Remove `handleValidate()` function from `frontend/src/routes/dashboards/+page.svelte`
- [ ] T065 [US6] Remove LLM validation status column from dashboard hub grid (column header + `getLlmSummaryLabel()` + `getValidationBadgeClass()` rendering) in `frontend/src/routes/dashboards/+page.svelte`
- [ ] T065a [US6] Implement `GET /api/dashboards/{env_id}/validation-status` in `backend/src/api/routes/dashboards.py` — batch endpoint accepting `dashboard_ids` list, returns `{dashboard_id: {status, last_run_at, task_name, run_id}}` per dashboard
- [ ] T065b [US6] Add validation indicator dot (PASS/WARN/FAIL/UNKNOWN) next to dashboard title in hub rows. Click opens dropdown with last 3 runs (date, aggregate status across all dashboards in that run, task name) + «View all» link → `/validation-tasks/{policyId}/runs/{runId}`, in `frontend/src/routes/dashboards/+page.svelte`
- [ ] T066 [US6] Remove «Validate» action from action dropdown menu in `frontend/src/routes/dashboards/+page.svelte`
- [ ] T067 [US6] Deprecate (comment out) `normalizeValidationStatus()` + `getLlmSummaryLabel()` — replaced by indicator + batch endpoint, in `frontend/src/routes/dashboards/+page.svelte`
- [ ] T068 [US6] Remove LLM validation status loading from `frontend/src/routes/dashboards/[id]/+page.svelte` (old `runLlmValidationTask()` + `openLlmReport()`)
- [ ] T068a [US6] Add «Validation History» section to `frontend/src/routes/dashboards/[id]/+page.svelte` — last 10 runs for this dashboard across all tasks, links to canonical run detail pages
- [ ] T069 [US6] Add «Create Validation Task» button to dashboard hub toolbar that navigates to `/validation-tasks/new?ids=1,2,3` (pre-fills selected dashboard IDs) in `frontend/src/routes/dashboards/+page.svelte`
- [ ] T070 [US6] Update `frontend/src/lib/components/layout/TaskDrawer.svelte` — ensure LLM validation tasks still show correct status after refactor
**Verification**: Dashboard hub has no Validate button, no LLM column. Colored dot indicator per row with dropdown. Detail page has Validation History section. «Create Validation Task» button pre-fills IDs.
---
## Phase 9: Polish & Cross-Cutting Verification
**Goal**: Migration testing, backward compat, ADR updates, final verification.
### 9.1 Migration & Backward Compat
- [ ] T071 Run data migration on test DB: verify all existing `dashboard_ids``sources[]` conversion in `backend/tests/migrations/test_v2_data_migration.py`
- [ ] T072 Update assistant tool `_tool_llm_validation.py` — replace ad-hoc `run_llm_validation` dispatch with task creation via `POST /api/validation-tasks`, in `backend/src/api/routes/assistant/_tool_llm_validation.py`
### 9.2 ADR Updates
- [ ] T073 Update `docs/adr/ADR-0008-assistant-tool-registry.md` — note that `_tool_llm_validation.py` now creates tasks instead of ad-hoc validation
- [ ] T074 Update `docs/adr/ADR-0004-plugin-architecture.md` — mention v2 dual-path + task-based flow as plugin evolution example
### 9.3 Final Verification
- [ ] T075 Run full end-to-end: create task → URL source (with filters) → Path A run → view report with per-tab screenshots
- [ ] T076 Run full end-to-end: create task → dashboard ID sources → Path B run (execute_chart_data=true) → view report with dataset health + chart data
- [ ] T077 Run full end-to-end: create task → Path B run against dashboard with broken dataset → verify LLM reports UNHEALTHY
- [ ] T078 Run full end-to-end: delete provider used by active task → verify HTTP 409 with blocking task names
- [ ] T079 Run full test suite: `cd backend && source .venv/bin/activate && python -m pytest -v`
- [ ] T080 Run frontend tests: `cd frontend && npm run test`
- [ ] T081 Run backend lint: `cd backend && python -m ruff check .`
- [ ] T082 Run frontend lint: `cd frontend && npm run lint`
- [ ] T083 Run semantic audit: `axiom_semantic_validation audit_belief_protocol` on all new C4/C5 contracts
- [ ] T084 Run semantic audit: `axiom_semantic_validation audit_contracts` on `backend/src/plugins/llm_analysis/` and `backend/src/services/validation_service.py`
---
## Verification Criteria per User Story
| Story | Independent Verification |
|-------|-------------------------|
| **US1** | Create task with 2 dashboards (ID + URL), Path A, cron `0 8 * * *` → task appears in list → scheduled execution succeeds → report shows per-tab screenshots |
| **US2** | Task list shows 3 tasks with different statuses → click task → detail shows per-dashboard breakdown → click dashboard → full report renders |
| **US3** | Path B against broken dataset → report shows dataset UNHEALTHY + affected charts. Path A against dashboard with visual error → report shows issue with tab/chart location |
| **US4** | Paste Superset URL with native_filters → parse returns dashboard title + 3 filters + 2 tabs → saved in ValidationSource → re-parsed on execution |
| **US5** | LLM settings shows 3 bindings (no dashboard_validation). Create task with multimodal provider → works. Create with non-multimodal + screenshot → rejected |
| **US6** | Dashboard hub: no Validate button, no LLM column. «Create Validation Task» toolbar button pre-fills selected IDs |
## Rejected-Path Regression Coverage
| Rejected Path | Test Task | Expected Behavior |
|---------------|-----------|-------------------|
| Old `POST /api/tasks` with `llm_dashboard_validation` | T063 | HTTP 400: «deprecated, use /api/validation-tasks» |
| Provider deletion when used by active tasks | T062 | HTTP 409: returns blocking task names |
| Duplicate manual trigger of same task | T013 (FR-054 check) | Toast «Already running», no queue |
| Non-multimodal provider + screenshot task | T061 | HTTP 400: «provider must be multimodal» |
| URL re-parse fails (dashboard deleted) | T055 | Source marked invalid, task skips, continues with other sources |
| Multi-chunk token limit exceeded | T050 | Quality reduction → Path B fallback, logged via molecular-cot |
| Path B dataset health check fails | T047 | LLM report flags dataset UNHEALTHY + affected charts |
## Belief-Runtime Instrumentation Tasks
| Contract | Tier | Task | Instrumentation |
|----------|------|------|-----------------|
| `LLMClient._estimate_payload_size` + `_reduce_image_quality` | C4 | T020 | `belief_scope("payload_reduction")` + `reason()` per reduction + `reflect()` on outcome |
| `DashboardValidationPlugin._execute_path_a` | C4 | T048 | `belief_scope("execute_path_a")` + `reason()` at navigation/tab/resize + `reflect()` with timings |
| `DashboardValidationPlugin._execute_path_b` | C4 | T043 | `belief_scope("execute_path_b")` + `reason()` at each API call + `reflect()` with token_usage |
| `ValidationTaskService.trigger_run` | C4 | T013 | `belief_scope("trigger_run")` + `reason()` on queue decision + `reflect()` after enqueue |
| `ScreenshotService.capture_dashboard_chunks` | C4 | T017 | `belief_scope("capture_chunks")` + `reason()` per tab switch + `reflect()` with screenshot count |
## Parallel Execution Opportunities
| Group | Tasks | Dependencies |
|-------|-------|--------------|
| **Phase 1** | T001T007 | None (all parallel) |
| **Phase 2** | T008T015 (service), T016 (health checker), T017 (screenshot), T018T020 (LLM client) | After Phase 1 |
| **Phase 3** | T025T034 (API), T035T042 (frontend) | After Phase 2 |
| **Phase 4+5+6** | T043T047 (Path B), T048T050 (Path A), T051T055 (URL) | After Phase 2, all three parallel |
| **Phase 7+8** | T056T063 (provider/prompt), T064T070 (hub) | After Phase 3, both parallel |
| **Phase 9** | T071T084 | After all phases |
## Total: 98 tasks across 9 phases
| Phase | Tasks | User Stories |
|-------|-------|-------------|
| 1 — Setup | 8 | — |
| 2 — Foundational | 23 | — |
| 3 — Task CRUD + Reports | 22 | US1, US2 |
| 4 — Path B | 5 | US3 |
| 5 — Path A | 3 | US3 |
| 6 — URL Parsing | 5 | US4 |
| 7 — Provider/Prompt | 8 | US5 |
| 8 — Hub Cleanup | 10 | US6 |
| 9 — Polish | 14 | All |