Skip to content

KG drops entire risk layer for current-format sessions: risk-summary.json not persisted + Phase 7 parser schema drift #231

Description

@Number531

Summary

The knowledge graph silently drops the entire risk layer (node_type='risk') for every current-format session. The risk-aggregator now emits structured review-outputs/risk-summary.json, but neither the persistence layer nor the KG Phase 7 parser was updated to match — so risk-summary never reaches the reports table, and even when it does it fails to parse. Confirmed 0 risk nodes on the two most recent completed sessions, vs 22–23 on May sessions that used the older Markdown narrative.

risk-summary also feeds executive-summary synthesis (risk-aggregator.js:26consumedBy: ['memo-executive-summary-writer']) and is listed as a KG CRITICAL_REPORT gate (hookDBBridge.js:1359), so the impact is broader than the graph alone.

Two independent root causes (both must be fixed)

Bug #1 — Persistence gap: risk-summary.json never becomes a report

Producer writes JSON (risk-aggregator.js:25outputFiles: ['risk-summary.json', 'risk-aggregator-state.json']), but both persistence paths store Markdown only:

  • Live hooksrc/utils/hookDBBridge.js:1444-1447:
    if (tool_name === 'Write' && filePath.includes('/reports/')) {
      if (filePath.endsWith('.md')) {            // <-- .json never persisted as a report
        await persistReport(pool, sessionCache, input, result);
      }
  • Backfillscripts/backfill-local-to-db.mjs walkMarkdown() collects .md only.

Meanwhile hookDBBridge.js:1359 waits for risk-summary as a CRITICAL_REPORT (6 retries), times out, records missing_reports: ['risk-summary'], and builds the KG with no risk input.

Bug #2 — Parser schema drift: even when persisted, the JSON doesn't parse

src/utils/knowledgeGraph/kgPhases6to8.js:380 (Phase 7 risk extraction):

const categories = parsed.risk_categories || parsed.categories || [];

The current producer keys its array as exposure_by_category (neither risk_categories nor categories), and exposures are strings ("weighted_exposure": "$433.75M", "exposure_low"/"exposure_high") rather than the numeric p50/p10/p90 fields the synth block reads (lines 391-394). Result: categories = [], fall through to the Markdown regex (Path B, no **bold** in JSON) → 0 risk nodes.

Why it regressed (Cardinal worked, June doesn't)

May sessions emitted risk-summary-narrative.md (Markdown) → hit the .md persist path and Phase 7 Path B regex. The producer was later switched to structured risk-summary.json without updating the persistence filter or the JSON parser.

Blast radius (queried against prod super_legal)

Session risk nodes has risk report failing bug
2026-06-16-1781644875 0 no #1 (+#2)
2026-06-08-1780888014 0 no #1 (+#2)
2026-05-27-1779903178 0 yes #2 alone
2026-05-22-1779484021 23 yes — (Markdown narrative)
2026-05-20-1779247022 22 yes — (Markdown narrative)

The 2026-05-27 row is the isolated proof of Bug #2: the report was present but still yielded 0 risk nodes. June sessions fail earlier at Bug #1.

Suggested solutions

Fix #1 — persist risk-summary.json as a report (report_type='review', report_key='risk-summary'):

  • Live hook hookDBBridge.js:~1445: persist risk-summary.json (and, conservatively, scope to that filename rather than all /reports/*.json to avoid pulling in state sidecars). extractReportKey already strips .json (hookDBBridge.js:173), so key derivation is in place.
  • Backfill scripts/backfill-local-to-db.mjs: have walkMarkdown (or a sibling pass) also ingest review-outputs/risk-summary.json. Note the existing exclude set already skips *-state.json; only the structured deliverable should be ingested as a report.

Fix #2 — make Phase 7 accept the current schema (kgPhases6to8.js:380):

  • Add parsed.exposure_by_category to the category source list.
  • Parse string exposure fields: read weighted_exposure / exposure_low / exposure_high and normalize $433.75M / $2.33B → numeric before building the synth block; map string severity and probability (e.g. "8% fail") defensively.
  • Unit-test with a fixture of the real risk-summary.json shape (exposure_by_category[].findings[]) — see reports/2026-06-16-1781644875/review-outputs/risk-summary.json for a canonical example (16 findings, 9 HIGH).

Hardening (recommended):

  • Add a contract test asserting the risk-aggregator output schema matches what Phase 7 / executive-summary synthesis consume (this is producer↔consumer drift; pin it).
  • Consider widening the alias resolver if the producer key changes again (kgHelpers.js:180 already aliases risk['risk-summary', 'risk-narrative', 'risk-assessment', 'risk-summary-narrative']).

Remediation for affected past sessions

Once both fixes land, re-ingest risk-summary.json for affected sessions and re-run the KG build (upsert) to backfill the missing risk nodes (and any downstream re-synthesis). At minimum: 2026-06-16, 2026-06-08, 2026-05-27.

Notes

  • Surfaced while recovering an unpersisted session (2026-06-16-1781644875, Fox/Roku). Diagnosis is read-only against prod; no fix applied yet pending triage.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions