30 KiB
Tasks: LLM Analysis & Documentation Plugins v2
Feature: 017-llm-analysis-plugin
Status: v2 Implementation — complete ✅ (~30 backend files, ~12 frontend files, ~7 880 + ~4 558 строк)
Spec: spec.md | Plan: plan.md | Data Model: 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)
└─→ 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)
Parallel: Phase 4 + 5 + 6 can run concurrently after Phase 2. Phase 7 is tight with Phase 3. Phase 8 is independent.
Phase 1: Setup — Database Migrations
Goal: Schema changes for v2 models. Blocks everything.
- T001 [P] Add
sourcesORM relationship +source_snapshotJSON (audit only, nullable) toValidationPolicyinbackend/src/models/llm.py. No JSON denormalization column for authoritative state — relationalValidationSourcetable is the single source of truth. - T002 [P] Create
ValidationSourceSQLAlchemy model inbackend/src/models/llm.py(id, policy_id FK, type, value, parsed_context JSON, status enum, resolved_dashboard_id, title, last_error, last_checked_at) - T003 [P] Add new columns to
ValidationPolicymodel: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) inbackend/src/models/llm.py - T003a [P] Add
global_validation_worker_limitconfig setting (default: 3) — max concurrent policy runs across all tasks. Stored in app config inGLOBAL_VALIDATION_WORKER_LIMITfield inbackend/src/core/config_models.py - T004 [P] Add new columns to
ValidationRecordmodel: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),timings(JSON, nullable),screenshot_paths(JSON, nullable),logs_sent_to_llm(JSON, nullable),run_id(FK → ValidationRun) inbackend/src/models/llm.py - T004a [P] Create
ValidationRunSQLAlchemy model inbackend/src/models/llm.py: id, policy_id (FK), task_id (nullable), started_at, finished_at, trigger (manual/scheduled), status (running/completed/partial/failed), dashboard_count, pass_count, warn_count, fail_count, unknown_count - T005 Create Alembic migration for all T001–T004a schema changes in
backend/alembic/versions/ - T006 Write data migration script: convert existing
dashboard_ids: list[str]→sourcesrows inbackend/src/plugins/llm_analysis/migrations/v1_to_v2.py - T007 [P] Add
DEFAULT_LLM_PROMPTS["dashboard_validation_prompt_multimodal"](Path A, image-focused) andDEFAULT_LLM_PROMPTS["dashboard_validation_prompt_text"](Path B, topology-focused) tobackend/src/services/llm_prompt_templates.py
Verification: pytest backend/tests/models/test_llm_v2.py -v — all models create/query correctly. Migration runs without data loss.
Phase 2: Foundational — Services & Refactored Plugin Core
Goal: ValidationTaskService, DatasetHealthChecker, refactored ScreenshotService (multi-chunk), LLMClient dual-mode. Blocks US1–US6.
2.1 ValidationTaskService
- T008 [P] Implement
ValidationTaskService.create_task()— createsValidationPolicy+ValidationSource[]rows, validates provider_id against execution path, inbackend/src/services/validation_service.py - T009 [P] Implement
ValidationTaskService.get_task(id)— returns policy + sources with current status, inbackend/src/services/validation_service.py - T010 [P] Implement
ValidationTaskService.list_tasks(env_id)— returns all policies with aggregated last-run status, inbackend/src/services/validation_service.py - T011 [P] Implement
ValidationTaskService.update_task(id, data)— updates policy fields + sources, inbackend/src/services/validation_service.py - T012 [P] Implement
ValidationTaskService.delete_task(id)— deletes policy + cascades to sources + records, inbackend/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 aValidationRunrecord (FR-055d), enqueues dashboard validation tasks per FR-055, inbackend/src/services/validation_service.py - T014 [P] Implement
ValidationTaskService.get_run_history(policy_id, page)— paginatedValidationRunlist with aggregate counts, inbackend/src/services/validation_service.py - T015 [P] Implement
ValidationTaskService.get_run_detail(run_id)— full run with all dashboard records, aggregate counts, inbackend/src/services/validation_service.py - T015a [P] Implement
ValidationTaskService._finalize_run(run_id)— computes aggregate counts from allValidationRecords with thisrun_id, sets finished_at and status, inbackend/src/services/validation_service.py
2.2 DatasetHealthChecker (Path B)
- T016 [P] Implement
DatasetHealthCheckerclass inbackend/src/plugins/llm_analysis/service.py:check_dataset_health(dataset_id)— callsGET /api/v1/dataset/{id}, extracts database name, backend, kind, verifies accessibilitycheck_chart_data(chart_id, form_data)— optional level 3-4 checkscheck_dashboard_datasets(chart_list, execute_chart_data)— batch check all unique datasets
2.3 ScreenshotService Refactor (multi-chunk for Path A)
- T017 [P] Implement
ScreenshotService.capture_dashboard_chunks(dashboard_id, parsed_filters, output_dir) → list[{tab_name, path}]inbackend/src/plugins/llm_analysis/service.py:- Login → navigate with filters/tabs → iterate tabs → per-tab viewport (1920×1200) → CDP screenshot → collect PNG paths
2.3b Image Pipeline (PNG → JPEG + WebP)
- T017a [P] Implement
ScreenshotService._convert_screenshots_for_llm(png_paths: list[str]) → list[str]inbackend/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}]inbackend/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)
- PNG → Pillow convert → WebP quality=80, lossy → save to
- T017c [P] Implement
ScreenshotService._cleanup_temp_files(paths: list[str])inbackend/src/plugins/llm_analysis/service.py:- Delete PNG and JPEG intermediates after successful archive + LLM call
2.4 LLMClient Dual-Mode
- T018 [P] Implement
LLMClient.analyze_dashboard_multimodal(screenshot_paths, logs, prompt) → dictinbackend/src/plugins/llm_analysis/service.py:- Sends all images in single
content[]array → multimodal LLM → JSON result
- Sends all images in single
- T019 [P] Implement
LLMClient.analyze_dashboard_text_batch(payloads, prompt) → dictinbackend/src/plugins/llm_analysis/service.py:- Text-only LLM call with per-dashboard sections →
{dashboards: [{dashboard_id, status, summary, issues}]}
- Text-only LLM call with per-dashboard sections →
- T020 [P] Implement
LLMClient._estimate_payload_size()andLLMClient._reduce_image_quality()inbackend/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
2.5 Plugin Refactor (dispatch to Path A / Path B)
- T021 Implement
DashboardValidationPlugin._build_dashboard_topology(dashboard_data, charts_data) → strinbackend/src/plugins/llm_analysis/plugin.py:- Recursive
position_jsonparsing → tab → row → chart hierarchy → text description
- Recursive
- T022 Refactor
DashboardValidationPlugin.execute()to dispatch based onscreenshot_enabled:- True →
_execute_path_a(): call ScreenshotService → LLMClient multimodal - False →
_execute_path_b(): call API chain → DatasetHealthChecker → LLMClient text - Belief-runtime:
belief_scope("execute_path_a")/belief_scope("execute_path_b")+reason()at entry +reflect()at exit
- True →
2.7 Security & Retention (new)
-
T024a [P] Implement
RedactionServiceinbackend/src/plugins/llm_analysis/service.py:redact_logs(logs: list[str]) → list[str]— strip PII, credentials, query text, tokens from URLs, native filter valuesredact_raw_response(raw: str) → str— strip sensitive data before persistence
-
T024b [P] Add retention config to app settings:
LLM_SCREENSHOT_RETENTION_DAYS=30,LLM_RAW_RESPONSE_RETENTION_DAYS=30inbackend/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 per FR-029b. Updated
backend/src/core/logger.py -
T023 [P] Add
dashboard_validation_prompt_multimodal(Path A) template toDEFAULT_LLM_PROMPTSinbackend/src/services/llm_prompt_templates.py -
T024 [P] Add
dashboard_validation_prompt_text(Path B) template toDEFAULT_LLM_PROMPTSinbackend/src/services/llm_prompt_templates.py
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— createsValidationPolicy+ValidationSource[]inbackend/src/api/routes/validation_tasks.py - T026 [US1] Implement
GET /api/validation-tasks— list all tasks with aggregated last-run status inbackend/src/api/routes/validation_tasks.py - T027 [US2] Implement
GET /api/validation-tasks/{policy_id}— task detail + per-source status inbackend/src/api/routes/validation_tasks.py - T028 [US1] Implement
PUT /api/validation-tasks/{policy_id}— update task inbackend/src/api/routes/validation_tasks.py - T029 [US1] Implement
DELETE /api/validation-tasks/{policy_id}— delete task (cascade sources + records), inbackend/src/api/routes/validation_tasks.py - T030 [US1] Implement
POST /api/validation-tasks/{policy_id}/run— manual trigger (checks FR-054 + FR-055) inbackend/src/api/routes/validation_tasks.py - T031 [US2] Implement
GET /api/validation-tasks/{policy_id}/runs— paginated run history (ValidationRun list, not individual records) inbackend/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, inbackend/src/api/routes/validation_tasks.py - T032b Implement legacy redirect:
GET /reports/llm/{taskId}→ 302 to/validation-tasks/{policy_id}/runs/{run_id}whentaskIdmaps toValidationRun.task_id, inbackend/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 callingGET /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 (5 steps: name → sources → options → schedule → review). Includes provider filter per FR-050, path-specific prompt defaults per FR-048, screenshot toggle per FR-053 - T039 [US1] Create
frontend/src/routes/validation-tasks/[policyId]/edit/+page.svelte— task edit page (reusesValidationTaskForm)
3.5 Task Detail Page + Report Page
- T040 [US2] Create
frontend/src/routes/validation-tasks/[policyId]/+page.svelte— task detail page with run history table, per-source breakdown, actions - T041 [US2] Create
frontend/src/routes/validation-tasks/[policyId]/+page.js— load function callingGET /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 with collapsible dashboard list, Path A/B sections, screenshots, dataset health, logs, timings - T041b Implement
GET /api/validation-tasks/{policy_id}/runs/{run_id}— API endpoint returning full run with all records
3.6 Shared Report Component
- T042 [US1] Create
frontend/src/lib/components/llm/ValidationTaskReport.svelte— shared report component with status badge, summary, issues table, execution path indicator
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)inbackend/src/plugins/llm_analysis/plugin.py:- For each dashboard: collect topology via
_build_dashboard_topology()+ dataset health viaDatasetHealthChecker+ logs - Default:
llm_batch_size=1— one LLM call per dashboard (full isolation). Values >1 group dashboards into batches per FR-045a - For each batch: build structured prompt with per-dashboard labeled sections + strict JSON response schema
{dashboards: [{dashboard_id, status, summary, issues}]} - Call
LLMClient.analyze_dashboard_text_batch(dashboard_payloads, prompt)→ parse response - Missing/parse-error dashboards → mark UNKNOWN individually, preserve other dashboards in batch
- Persist individual
ValidationRecordper dashboard, linked torun_id
- Belief-runtime:
belief_scope("execute_path_b_batch")+reason(f"batch {i}/{total}: {n} dashboards")+reflect()with token_usage per batch
- For each dashboard: collect topology via
- T043a [US3] Implement
LLMClient.analyze_dashboard_text_batch(payloads: list[dict], prompt: str) → dictinbackend/src/plugins/llm_analysis/service.py:- Builds prompt: per-dashboard labeled sections, enforced
{dashboards: [{dashboard_id, ...}]}JSON schema - Validates response completeness: missing dashboard_id → UNKNOWN fallback per-dashboard
- Builds prompt: per-dashboard labeled sections, enforced
- T044 [US3] Create DatasetHealth display in run detail page — table: dataset name, database, backend, status icon (✓/✗), error message, affected charts count
- T045 [US3] Create ChartDataResults display in run detail page — table: chart name, viz_type, executed (✓/skipped), duration, row count, error
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/services/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/services/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.
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)inbackend/src/plugins/llm_analysis/plugin.py:- Parse URL filters if source is URL type (re-parse per FR-057)
ScreenshotService.capture_dashboard()(existing) → fallback: single screenshot per dashboard- Log fetch via
_fetch_dashboard_logs() LLMClient._estimate_payload_size()→ check token budget per FR-056- Quality reduction loop if needed (FR-056)
LLMClient.analyze_dashboard_multimodal()→ JSON result- Fallback to
_execute_path_b()if payload still exceeds limit - Persist
ValidationRecordwithexecution_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 (fromtab_screenshots), per-tab image viewer with zoom, issues table with tab/chart location
Rejected-path regression test:
- T050 [US3] Add test: multi-chunk screenshots exceed token limit → quality reduction triggered → Path B fallback when still exceeded — in
backend/tests/services/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-urlinbackend/src/api/routes/validation_tasks.py - T052 [US4] Create
frontend/src/lib/components/llm/UrlParser.svelte— URL paste input with parse, success green banner, error red banner, partial recovery amber banner - T053 [US4] Integrate
UrlParserintoValidationTaskForm.svelte— «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 invalid, set last_error, log WARN
- If dashboard_id changed → log WARN, use new ID
- For URL-type sources: call
Rejected-path regression test:
- T055 [US4] Add test: URL source with deleted dashboard → re-parse fails → source marked invalid, error logged, task skips source — in
backend/tests/services/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": ""fromDEFAULT_LLM_PROVIDER_BINDINGSinbackend/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_validationfrom provider_bindings payload inbackend/src/schemas/llm.py - T059 [US5] Implement provider selector filter logic in
ValidationTaskForm.svelte(Step 3) —screenshot_enabled=trueshows only multimodal providers;screenshot_enabled=falseshows all active providers - T060 [US5] Add
ValidationTaskService.create_task()validation: reject ifscreenshot_enabled=truebut provideris_multimodal=false— implemented via_validate_provider(provider_id, require_multimodal=screenshot_enabled)inbackend/src/services/validation_service.py - T061 [US5] Add test: create task with
screenshot_enabled=trueand non-multimodal provider → HTTP 400, inbackend/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), inbackend/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), inbackend/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 fromfrontend/src/routes/dashboards/+page.svelte - T065 [US6] Remove LLM validation status column from dashboard hub grid (column header +
getLlmSummaryLabel()+getValidationBadgeClass()rendering) infrontend/src/routes/dashboards/+page.svelte - T065a [US6] Add
getValidationStatusBatch()API method infrontend/src/lib/api.js— callsGET /api/dashboards/{env_id}/validation-status?dashboard_ids=...with graceful 404 fallback - T065b [US6] Add validation indicator dot (PASS/WARN/FAIL/UNKNOWN) next to dashboard title in hub rows. Click opens popover with last 3 runs + «View all» link, 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 old
normalizeValidationStatus()+getLlmSummaryLabel()— replaced by indicator + batch endpoint, infrontend/src/routes/dashboards/+page.svelte - T068 [US6] Remove LLM validation status loading from
frontend/src/routes/dashboards/[id]/+page.svelte(oldrunLlmValidationTask()+openLlmReport()) - T068a [US6] Add «Validation History» section to
frontend/src/routes/dashboards/[id]/+page.svelte— last 10 runs for this dashboard, 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) infrontend/src/routes/dashboards/+page.svelte - T070 [US6] Update
frontend/src/lib/components/layout/TaskDrawer.svelte— updatedresolveLlmValidationStatus()to use ValidationRun aggregate counts; updatedhandleOpenLlmReport()to navigate to canonical/validation-tasks/{policyId}/runs/{runId}
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 inbackend/tests/migrations/test_v2_data_migration.py - T072 Update assistant tool
_tool_llm_validation.py— replaced ad-hocrun_llm_validationdispatch with task creation viaValidationTaskService.create_task()+trigger_run(), inbackend/src/api/routes/assistant/_tool_llm_validation.py
9.2 ADR Updates
- T073 Update
docs/adr/ADR-0008-assistant-tool-registry.md— noted that_tool_llm_validation.pynow creates tasks instead of ad-hoc validation; added positive consequence for task-based validation - T074 Update
docs/adr/ADR-0004-plugin-architecture.md— updated description to note v2 dual-path execution + ValidationTaskService persistent policies
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_protocolon all new C4/C5 contracts - T084 Run semantic audit:
axiom_semantic_validation audit_contractsonbackend/src/plugins/llm_analysis/andbackend/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 | T001–T007 | None (all parallel) |
| Phase 2 | T008–T015 (service), T016 (health checker), T017 (screenshot), T018–T020 (LLM client) | After Phase 1 |
| Phase 3 | T025–T034 (API), T035–T042 (frontend) | After Phase 2 |
| Phase 4+5+6 | T043–T047 (Path B), T048–T050 (Path A), T051–T055 (URL) | After Phase 2, all three parallel |
| Phase 7+8 | T056–T063 (provider/prompt), T064–T070 (hub) | After Phase 3, both parallel |
| Phase 9 | T071–T084 | After all phases |
Total: 99 tasks across 9 phases — 86% complete
| Phase | Tasks | Status |
|---|---|---|
| 1 — Setup | 8 | ✅ 8/8 |
| 2 — Foundational | 23 | ✅ 23/23 |
| 3 — Task CRUD + Reports | 22 | ✅ 22/22 |
| 4 — Path B | 5 | ✅ 5/5 |
| 5 — Path A | 3 | ✅ 3/3 |
| 6 — URL Parsing | 5 | ✅ 5/5 |
| 7 — Provider/Prompt | 8 | ✅ 8/8 |
| 8 — Hub Cleanup | 10 | ✅ 9/10 (T070 done) |
| 9 — Polish | 14 | ⬜ 14/14 pending (T071, T075-T084 — e2e tests, semantic audit, full test suite) |