Skip to content

feat(agent): preserve claim-source attribution through report synthesis#89

Merged
atomicdragonranch merged 1 commit into
masterfrom
feat/88-report-attribution
Jul 9, 2026
Merged

feat(agent): preserve claim-source attribution through report synthesis#89
atomicdragonranch merged 1 commit into
masterfrom
feat/88-report-attribution

Conversation

@atomicdragonranch

Copy link
Copy Markdown
Owner

Closes #88.

Problem

Attribution was enforced within the diagnosis (#81) and passed intact into the Report agent, but lost at synthesis: IncidentReport carried no sources and no claims. root_cause/summary are prose, low_confidence_claims is text-only, so the artifact a human reads couldn't be traced back to the tool signals behind it. Traceability survived only in the audit log's separate diagnosis dump. This violated the assertion that every synthesis step must output claim-source pairs.

Fix

  • IncidentReport gains sources: list[SourceRecord] and supporting_claims: list[ClaimRecord] (default empty), plus a self-contained validator mirroring _check_attribution_integrity: every non-UNSOURCED supporting claim traces to a real carried source; source_ids and claim_ids unique.
  • _parse_incident re-attributes every report from the diagnosis deterministically (_attach_attribution: carry sources + claims, re-validate), so no hallucinated source_ids and the invariant holds on what the pipeline emits.
  • _fallback_report carries attribution too, so the fallback path stays traceable.

Out of scope (follow-up)

Root-cause / per-action provenance citing specific claim_ids. This delivers the deterministic no-loss guarantee first.

Why now

#67 (fork-style exploration) reconciles N forks: a synthesis step. This gives that merge an attribution-preserving target instead of the one synthesis example that dropped it.

Tests

IncidentReport attribution validator (valid, unknown-source rejected, UNSOURCED exempt, duplicate rejected, empty default) + monitor carry-forward (_parse_incident, _fallback_report).

Full suite 236 passed; ruff + mypy clean.

https://claude.ai/code/session_01R5VygSzbGTggW7mHd3PVwE

Closes #88.

Attribution was enforced within the diagnosis (#81) and passed intact into the
Report agent, but lost at synthesis: IncidentReport carried no sources and no
claims, so the artifact a human reads (prose root_cause/summary, text-only
low_confidence_claims) could not be traced back to the tool signals behind it.
Traceability survived only in the audit log's separate diagnosis dump. This
violated the assertion that every synthesis step must output claim-source pairs.

- IncidentReport gains sources: list[SourceRecord] and supporting_claims:
  list[ClaimRecord] (default empty), plus a self-contained validator mirroring
  the diagnosis integrity check: every non-UNSOURCED supporting claim traces to
  a real carried source; source_ids and claim_ids unique.
- _parse_incident re-attributes every report from the diagnosis deterministically
  (_attach_attribution: carry sources + claims, re-validate), so no hallucinated
  source_ids and the invariant holds on what the pipeline emits.
- _fallback_report carries attribution too, so the fallback path stays traceable.

Root-cause/action-level provenance (citing specific claim_ids) is left as a
follow-up; this delivers the deterministic no-loss guarantee first.

Tests: IncidentReport attribution validator (valid, unknown-source rejected,
UNSOURCED exempt, duplicate rejected, empty default) and monitor carry-forward
(_parse_incident + _fallback_report). Full suite 236 passed; ruff + mypy clean.
@atomicdragonranch atomicdragonranch merged commit 9d89bbf into master Jul 9, 2026
3 checks passed
@atomicdragonranch atomicdragonranch deleted the feat/88-report-attribution branch July 9, 2026 18:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(agent): preserve claim-source attribution through report synthesis

1 participant