You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Reporting already ships four tabs, four tables, and a working BullMQ scheduler. This issue finishes that domain rather than starting a new one. What exists must not be rebuilt:
report_templates, report_template_versions, scheduled_reports, report_runs (20260619190359-create-reporting-domain.js), 3 seeded system templates
Templates / Scheduled / Archive tabs + the 6-step ConfigureReportWizard
DELETE /api/reporting/scheduled-reports/:id (scheduledReport.route.ts:20 → softDeleteQuery, scheduledReport.utils.ts:44-48) — already implemented, has no frontend caller
AI summarisation (aiSummarizer.ts:388-460) producing executive summary, keyFindings, recommendations, riskHighlights, per-section prose — off-pattern and unpersisted
What is missing: persisted/versioned/auditable analysis, any template write path, and a report id to attach analysis to.
Note the *.agent.ts + registry layer is orphaned — bootstrapAgentNetwork() (Servers/index.ts:85) registers 9 agents across two registries, but nothing reads AgentDefinition.systemPrompt/.tools and getAgentToolFilter has zero callers. A report.agent.ts would do nothing at runtime. Analysis follows the shipped evidence-analyzer pattern instead: backend calls the tenant's own llm_keys row, outbound only. No inbound agent API, no sk-mcp-*.
Tasks
Phase 1 — Pipeline (everything else depends on a run id existing)
Create Servers/database/migrations/YYYYMMDDHHMMSS-fix-report-runs-fields.js — add report_runs.file_id FK → files (missing at 20260619190359-...js:80), add scheduled_reports.llm_key_id column (read at reportRunOrchestrator.ts:22, no column exists), add trigger_type enum (manual | scheduled)
Servers/controllers/reporting.ctrl.ts — generateReportsV2 stops returning a blob; creates a report_runs row with trigger_type='manual', enqueues, returns run id
Servers/services/automations/automationProducer.ts — scheduleReportGeneration(), explicit tz, no queue.obliterate()
Servers/jobs/producer.ts — call it at the end of addAllJobs()
Servers/services/automations/automationWorker.ts — dispatch arm for the new job
Servers/services/reporting/reportRunOrchestrator.ts — per-org Redis lock (SET key ts PX <ttl> NX, TTL < tick interval, released in finally; copy aiDetection/scheduledScanProcessor.ts:36-52)
Retire the legacy scheduled_report automation trigger (automationWorker.ts:250-492) which bypasses scheduled_reports/report_runs entirely — preserve its working email send as the reference for Phase 4
Create 6 pure analyzers under Servers/advisor/reportAnalyzers/ — executiveSummary, keyFindings, recommendedActions, riskAnalysis, complianceGap, vendorRisk. Pure: data + llmKey in, result out, zero DB writes, never touch req/res
Create Servers/advisor/reportAnalyzers/index.ts — runAnalyzers, Promise.all, only template-enabled blocks
Widen createModel() to an exhaustive switch on the provider union (evidenceAnalyzer/analyzer.service.ts:37; :130 only discriminates Anthropic, :137-141 collapses OpenAI/OpenRouter/Custom) and use openai.chat(modelId) when a custom baseURL is set
complianceGap consumes getControlScoresQuery / getWeakestControlsQuery / getFrameworkScoreByTypeQuery (readiness.utils.ts:185-299) — must pass a non-null projectId; all three default to AND project_id IS NULL (:194, 231, 269) and no stored row can ever have a null project_id (readiness.ctrl.ts:83-88), so a project-less call returns [] and renders as "no gaps" forever
complianceGap treats /api/evidence-ai/gaps as an independent input, not a join — it covers only eu_ai_act/iso_42001 (evidenceAi.utils.ts:161), is not project-scoped, uses a different key space, and ?framework_type=iso_27001 returns EU rows mislabeled (:170-172)
Create Servers/utils/reportRunAnalysis.utils.ts — org-scoped ON CONFLICT DO UPDATE version bump (follow readiness.utils.ts:31-120, not the racy check-then-write in evidenceAi.utils.ts:12-107)
Widen AiBlocksConfig (i.reportTemplate.ts:14-18) to 6 independent blocks; reportTemplateResolver.ts stops OR-ing them into aiEnhanced. Column is unconstrained JSONB — no migration needed
Render analysis blocks in Servers/templates/reports/report-pdf.ejsandServers/services/reporting/docxGenerator.ts — miss the second and analysis silently vanishes from docx
Delete Servers/services/reporting/aiSummarizer.ts once ported — one AI system in reporting, not two
Create Clients/src/presentation/components/Reporting/ReportAnalysisPanel/index.tsx — presentational, {analysis, isLoading, isAnalyzing, hasLLMKey, onTrigger}, caller owns hooks. Copy types from the analyzer, not by hand (EvidenceAnalysisPanel's rationales/document_signals have zero producers → :480 is unreachable dead UI)
Phase 3 — Templates
Create GET /api/reporting/sections — catalog derived from VALID_SECTION_KEYS (services/reporting/index.ts:21-35), excluding the all wildcard; frontend stops hardcoding REPORT_SECTION_GROUPS (constants.ts:22-53)
Servers/utils/reportTemplate.utils.ts — write queries + append-only version bump; fix org-scoping at :24-39 (template versions are unscoped, harmless only while all templates are system templates)
Validate templateVersionId belongs to templateId and the caller's org in createScheduledReportQuery — accepted raw from req.body today (reportTemplate.ctrl.ts:37)
Create Clients/src/presentation/pages/Reporting/TemplateBuilder/ — mirrors the existing ConfigureReportWizard Stepper
TemplatesTab.tsx — create/edit/delete
Phase 4 — Truthfulness
Servers/services/reporting/reportDeliveryService.ts — wire sendEmailLink/attachFile to the MJML service, link to /api/reporting/runs/:id/download. Today :42-44 is a TODO, :46/:49 write status:"success" unconditionally, and the catch at :51-53 is unreachable (try block is pure object assignment) so status:"failed" is already dead code
Validate recipients — unvalidated free text today
Fix scheduled-report invisibility: project_id = null at reportDeliveryService.ts:29 vs bare JOIN projects at reporting.utils.ts:93-94. Note deliverReport only uploads when delivery.saveToStorage is true (:16)
Add PATCH /api/reporting/scheduled-reports/:id — no update endpoint exists. Do not add DELETE — it already exists (scheduledReport.route.ts:20); wire it into reporting.repository.ts, which has no caller for it
Add refetchInterval to useReportRuns — running/pending runs stay stale forever
Paginate listRunsQuery — hard LIMIT 200 at reportRun.utils.ts:38
Gate the wizard's AI blocks on useLLMKeyStatus().hasKeys — a keyless user can schedule an AI report today. Consume hasKeys directly (useLLMKeyStatus.ts:38)
Unhardcode format: "pdf" — ConfigureReportWizard.tsx:103, the file's only format occurrence
Docs
Update docs/technical/domains/reporting.md + bump Last Updated; correct :185 (describes report-docx.ejs as live — it is dead)
Correct docs/technical/infrastructure/pdf-generation.md:195 — same stale report-docx.ejs claim
Add the new job to the schedule table in docs/technical/infrastructure/automations.md (~L103)
npm run generate:swagger && npm run generate:endpoints — CI api-docs-drift fails otherwise
API Endpoints
Route
Method
Purpose
/api/reporting/v2/generate-report
POST
Changed — creates a run, enqueues, returns run id (no longer a blob)
/api/reporting/runs/:id
GET
Run status for polling
/api/reporting/runs/:id/analyses
GET
Persisted analysis sections for a run
/api/reporting/sections
GET
Section catalog (single source of truth)
/api/reporting/templates
POST
Create custom template (Admin, Editor)
/api/reporting/templates/:id
PATCH
Update custom template (Admin, Editor)
/api/reporting/templates/:id
DELETE
Delete custom template (Admin, Editor)
/api/reporting/scheduled-reports/:id
PATCH
Update schedule (Admin, Editor) — new
Acceptance
cd Servers && npm run build && npm run test and cd Clients && npm run build && npm run test pass; a report generated with all six AI blocks enabled produces one report_run_analyses row per enabled section_key with a non-null analysis_model, renders those sections in both PDF and DOCX, and a run whose email delivery fails records status: "failed" — never "success"; an analyzer throwing leaves the report generated with that section abstained; a keyless org receives 400 on generate and cannot enable AI blocks in the wizard.
Risks
The async cutover changes an existing endpoint's contract and rewrites the Generate modal. Phase 1 lands green before anything else starts.
Worker process required.Servers/index.ts only calls addAllJobs(); it never constructs a Worker. Without npm run worker, generation jobs queue and never run — after this change that means reports never generate at all, where today manual ones still work.
Six analyzers is real LLM spend per report. Template gating is the control; system template defaults must not enable all six.
Branch
hp-apr-16-add-tasks-agent
Claims in the linked spec were adversarially verified against the codebase. Four were wrong and are corrected there — notably that DELETE /scheduled-reports/:id already ships, and that the section taxonomy lists do not actually disagree.
Add agent analysis sections, custom templates, and a unified async report pipeline
Design spec:
docs/superpowers/specs/2026-07-17-reporting-agent-analysis-design.md(commitdcad85be3).Current state
Reporting already ships four tabs, four tables, and a working BullMQ scheduler. This issue finishes that domain rather than starting a new one. What exists must not be rebuilt:
report_templates,report_template_versions,scheduled_reports,report_runs(20260619190359-create-reporting-domain.js), 3 seeded system templatesConfigureReportWizardreport_scheduler_tick(*/15 * * * *) + timezone-correctcomputeNextRun()(scheduleCalculator.ts)DELETE /api/reporting/scheduled-reports/:id(scheduledReport.route.ts:20→softDeleteQuery,scheduledReport.utils.ts:44-48) — already implemented, has no frontend calleraiSummarizer.ts:388-460) producing executive summary,keyFindings,recommendations,riskHighlights, per-section prose — off-pattern and unpersistedWhat is missing: persisted/versioned/auditable analysis, any template write path, and a report id to attach analysis to.
Note the
*.agent.ts+ registry layer is orphaned —bootstrapAgentNetwork()(Servers/index.ts:85) registers 9 agents across two registries, but nothing readsAgentDefinition.systemPrompt/.toolsandgetAgentToolFilterhas zero callers. Areport.agent.tswould do nothing at runtime. Analysis follows the shipped evidence-analyzer pattern instead: backend calls the tenant's ownllm_keysrow, outbound only. No inbound agent API, nosk-mcp-*.Tasks
Phase 1 — Pipeline (everything else depends on a run id existing)
Servers/database/migrations/YYYYMMDDHHMMSS-create-report-run-analyses.js—report_run_analyses, unique(report_run_id, section_key, organization_id), JSONB payload,analysis_model,analysis_version,analyzed_at,analyzed_by,audit_metadata, fulldown()Servers/database/migrations/YYYYMMDDHHMMSS-fix-report-runs-fields.js— addreport_runs.file_idFK →files(missing at20260619190359-...js:80), addscheduled_reports.llm_key_idcolumn (read atreportRunOrchestrator.ts:22, no column exists), addtrigger_typeenum (manual|scheduled)Servers/controllers/reporting.ctrl.ts—generateReportsV2stops returning a blob; creates areport_runsrow withtrigger_type='manual', enqueues, returns run idServers/services/automations/automationProducer.ts—scheduleReportGeneration(), explicittz, noqueue.obliterate()Servers/jobs/producer.ts— call it at the end ofaddAllJobs()Servers/services/automations/automationWorker.ts— dispatch arm for the new jobServers/services/reporting/reportRunOrchestrator.ts— per-org Redis lock (SET key ts PX <ttl> NX, TTL < tick interval, released infinally; copyaiDetection/scheduledScanProcessor.ts:36-52)scheduled_reportautomation trigger (automationWorker.ts:250-492) which bypassesscheduled_reports/report_runsentirely — preserve its working email send as the reference for Phase 4Clients/src/domain/interfaces/i.reporting.ts—ReportTemplate,ReportTemplateVersion,ScheduledReport,ReportRun,ReportRunAnalysis,SectionsConfig,AiBlocksConfig,ScheduleConfig,DeliveryConfig(every payload fn inreporting.repository.tsreturnsanytoday exceptdownloadReportRun)Clients/src/application/hooks/useReportRunStatus.ts— pollingClients/src/presentation/components/Reporting/GenerateReport/— async + real progress (DownloadReportFrom/index.tsx:32is literally// Simulated progress)Phase 2 — Analyzers
Servers/advisor/reportAnalyzers/schema.ts— zod.strict(),.describe()every field, nullableabstain_reasonServers/advisor/reportAnalyzers/prompts.ts—ANALYZER_VERSION = "report-analyzer-v1"+ prompt buildersServers/advisor/reportAnalyzers/—executiveSummary,keyFindings,recommendedActions,riskAnalysis,complianceGap,vendorRisk. Pure: data +llmKeyin, result out, zero DB writes, never touchreq/resServers/advisor/reportAnalyzers/index.ts—runAnalyzers,Promise.all, only template-enabled blockscreateModel()to an exhaustive switch on the provider union (evidenceAnalyzer/analyzer.service.ts:37;:130only discriminates Anthropic,:137-141collapses OpenAI/OpenRouter/Custom) and useopenai.chat(modelId)when a custombaseURLis setcomplianceGapconsumesgetControlScoresQuery/getWeakestControlsQuery/getFrameworkScoreByTypeQuery(readiness.utils.ts:185-299) — must pass a non-nullprojectId; all three default toAND project_id IS NULL(:194, 231, 269) and no stored row can ever have a nullproject_id(readiness.ctrl.ts:83-88), so a project-less call returns[]and renders as "no gaps" forevercomplianceGaptreats/api/evidence-ai/gapsas an independent input, not a join — it covers onlyeu_ai_act/iso_42001(evidenceAi.utils.ts:161), is not project-scoped, uses a different key space, and?framework_type=iso_27001returns EU rows mislabeled (:170-172)Servers/utils/reportRunAnalysis.utils.ts— org-scopedON CONFLICT DO UPDATEversion bump (followreadiness.utils.ts:31-120, not the racy check-then-write inevidenceAi.utils.ts:12-107)AiBlocksConfig(i.reportTemplate.ts:14-18) to 6 independent blocks;reportTemplateResolver.tsstops OR-ing them intoaiEnhanced. Column is unconstrained JSONB — no migration neededServers/templates/reports/report-pdf.ejsandServers/services/reporting/docxGenerator.ts— miss the second and analysis silently vanishes from docxServers/services/reporting/aiSummarizer.tsonce ported — one AI system in reporting, not twoClients/src/presentation/components/Reporting/ReportAnalysisPanel/index.tsx— presentational,{analysis, isLoading, isAnalyzing, hasLLMKey, onTrigger}, caller owns hooks. Copy types from the analyzer, not by hand (EvidenceAnalysisPanel'srationales/document_signalshave zero producers →:480is unreachable dead UI)Phase 3 — Templates
GET /api/reporting/sections— catalog derived fromVALID_SECTION_KEYS(services/reporting/index.ts:21-35), excluding theallwildcard; frontend stops hardcodingREPORT_SECTION_GROUPS(constants.ts:22-53)Servers/controllers/reportTemplate.ctrl.ts(38 lines today, list+get only) — add POST/PATCH/DELETE,authorize(["Admin","Editor"]),is_system_templatewrite guardServers/utils/reportTemplate.utils.ts— write queries + append-only version bump; fix org-scoping at:24-39(template versions are unscoped, harmless only while all templates are system templates)templateVersionIdbelongs totemplateIdand the caller's org increateScheduledReportQuery— accepted raw fromreq.bodytoday (reportTemplate.ctrl.ts:37)Clients/src/presentation/pages/Reporting/TemplateBuilder/— mirrors the existingConfigureReportWizardStepperTemplatesTab.tsx— create/edit/deletePhase 4 — Truthfulness
Servers/services/reporting/reportDeliveryService.ts— wiresendEmailLink/attachFileto the MJML service, link to/api/reporting/runs/:id/download. Today:42-44is a TODO,:46/:49writestatus:"success"unconditionally, and thecatchat:51-53is unreachable (try block is pure object assignment) sostatus:"failed"is already dead codeproject_id = nullatreportDeliveryService.ts:29vs bareJOIN projectsatreporting.utils.ts:93-94. NotedeliverReportonly uploads whendelivery.saveToStorageis true (:16)PATCH /api/reporting/scheduled-reports/:id— no update endpoint exists. Do not add DELETE — it already exists (scheduledReport.route.ts:20); wire it intoreporting.repository.ts, which has no caller for itrefetchIntervaltouseReportRuns— running/pending runs stay stale foreverlistRunsQuery— hardLIMIT 200atreportRun.utils.ts:38useLLMKeyStatus().hasKeys— a keyless user can schedule an AI report today. ConsumehasKeysdirectly (useLLMKeyStatus.ts:38)format: "pdf"—ConfigureReportWizard.tsx:103, the file's onlyformatoccurrenceDocs
docs/technical/domains/reporting.md+ bump Last Updated; correct:185(describesreport-docx.ejsas live — it is dead)docs/technical/infrastructure/pdf-generation.md:195— same stalereport-docx.ejsclaimdocs/technical/infrastructure/automations.md(~L103)npm run generate:swagger && npm run generate:endpoints— CIapi-docs-driftfails otherwiseAPI Endpoints
/api/reporting/v2/generate-report/api/reporting/runs/:id/api/reporting/runs/:id/analyses/api/reporting/sections/api/reporting/templates/api/reporting/templates/:id/api/reporting/templates/:id/api/reporting/scheduled-reports/:idAcceptance
cd Servers && npm run build && npm run testandcd Clients && npm run build && npm run testpass; a report generated with all six AI blocks enabled produces onereport_run_analysesrow per enabledsection_keywith a non-nullanalysis_model, renders those sections in both PDF and DOCX, and a run whose email delivery fails recordsstatus: "failed"— never"success"; an analyzer throwing leaves the report generated with that section abstained; a keyless org receives400on generate and cannot enable AI blocks in the wizard.Risks
Servers/index.tsonly callsaddAllJobs(); it never constructs aWorker. Withoutnpm run worker, generation jobs queue and never run — after this change that means reports never generate at all, where today manual ones still work.Branch
hp-apr-16-add-tasks-agentClaims in the linked spec were adversarially verified against the codebase. Four were wrong and are corrected there — notably that
DELETE /scheduled-reports/:idalready ships, and that the section taxonomy lists do not actually disagree.