User Story
As a security engineer reviewing a historical threat model, I want the DFD that the threats were enumerated against to be inlined as a point-in-time snapshot in the threat-model output (with a "DFD as of YYYY-MM-DD" marker), so that the threat model remains internally consistent and auditable even after the live DFD evolves.
Context
Today (per AgDR-0026) /threat-model consumes projects/<name>/architecture/dfd.md as the source of truth — it reads the DFD, enumerates STRIDE threats per trust-boundary crossing, and writes its output to projects/<name>/audits/threat-model/<ts>.md (per AgDR-0019 + #218).
The threat-model output links to the live DFD but doesn't copy it. So when the architecture evolves and /dfd is re-run, the historical threat models reference a DFD that no longer matches what they analysed. That's silent rot — last quarter's threats are now indexed against this quarter's DFD.
Audit-class outputs are by design point-in-time snapshots (per AgDR-0019). The DFD that an audit consumed is part of that snapshot.
Acceptance Criteria
Design Notes
- Inline by copy, not by transclusion. A snapshot pattern (
{{include: dfd.md}} style) would couple the rendering of historical threat models to whatever the live DFD says today — same root cause as today's problem. The whole point is the file is self-contained.
/compliance-check consumes the same DFD (also per AgDR-0026). Same snapshot pattern should apply there. Out of scope for this ticket — file a sibling ticket if accepted.
- Same pattern applies to other DFD-consumers (
/security-review, potentially /launch-check if it summarises threats). Out of scope for v1; design here so it's reusable.
- Drift detection (out of scope, v1). A future enhancement: when
/threat-model runs again, compare the snapshot from the previous run to the current live DFD; flag deltas so the operator knows what's new. v1 just inlines the snapshot — diff/drift is a v2 concern.
Risks
- Snapshot can rot if the operator never re-runs
/threat-model. Mitigation: print a "last run was N days ago" warning in /launch-check / portfolio status output. Out of scope for this ticket.
- File-size growth — each threat-model now carries the DFD's Mermaid block (~30-100 lines) + tables. Acceptable given audit-history is dated subdirs (per AgDR-0019); doesn't bloat the live tree.
Out of scope
- Inlining DFD into
/compliance-check output (separate ticket if accepted)
- DFD drift detection between threat-model runs (v2)
- Inlining DFD into
/security-review or /launch-check (separate analysis)
Glossary
| Term |
Definition |
| Snapshot |
A point-in-time copy of an artefact, captured at audit time so the audit remains internally consistent later |
| Live DFD |
The continuously-refreshed projects/<name>/architecture/dfd.md produced by /dfd |
| Audit-artefact persistence |
The dated-subdir convention shipped via AgDR-0019 / #218 — projects/<name>/audits/<dim>/<ts>.md |
User Story
As a security engineer reviewing a historical threat model, I want the DFD that the threats were enumerated against to be inlined as a point-in-time snapshot in the threat-model output (with a "DFD as of YYYY-MM-DD" marker), so that the threat model remains internally consistent and auditable even after the live DFD evolves.
Context
Today (per AgDR-0026)
/threat-modelconsumesprojects/<name>/architecture/dfd.mdas the source of truth — it reads the DFD, enumerates STRIDE threats per trust-boundary crossing, and writes its output toprojects/<name>/audits/threat-model/<ts>.md(per AgDR-0019 + #218).The threat-model output links to the live DFD but doesn't copy it. So when the architecture evolves and
/dfdis re-run, the historical threat models reference a DFD that no longer matches what they analysed. That's silent rot — last quarter's threats are now indexed against this quarter's DFD.Audit-class outputs are by design point-in-time snapshots (per AgDR-0019). The DFD that an audit consumed is part of that snapshot.
Acceptance Criteria
/threat-modelreadsprojects/<name>/architecture/dfd.mdat audit time and inlines:```mermaidflowchart block (verbatim)## Trust boundariestable (verbatim)## Data classificationstable (verbatim)## DFD (snapshot as of YYYY-MM-DD)with a callout: "This is a point-in-time capture of the DFD that this threat model was enumerated against. Live DFD:projects/<name>/architecture/dfd.md."## Discovery provenanceblock fromdfd.mdis not inlined (too noisy for a threat-model output; readers click through to the live DFD for that)dfd.mddoesn't exist when/threat-modelruns, the skill stops and prompts the operator to run/dfd <project>firstprojects/<name>/audits/threat-model/<ts>.md+runs/<ts>.jsonper AgDR-0019Design Notes
{{include: dfd.md}}style) would couple the rendering of historical threat models to whatever the live DFD says today — same root cause as today's problem. The whole point is the file is self-contained./compliance-checkconsumes the same DFD (also per AgDR-0026). Same snapshot pattern should apply there. Out of scope for this ticket — file a sibling ticket if accepted./security-review, potentially/launch-checkif it summarises threats). Out of scope for v1; design here so it's reusable./threat-modelruns again, compare the snapshot from the previous run to the current live DFD; flag deltas so the operator knows what's new. v1 just inlines the snapshot — diff/drift is a v2 concern.Risks
/threat-model. Mitigation: print a "last run was N days ago" warning in/launch-check/ portfolio status output. Out of scope for this ticket.Out of scope
/compliance-checkoutput (separate ticket if accepted)/security-reviewor/launch-check(separate analysis)Glossary
projects/<name>/architecture/dfd.mdproduced by/dfdprojects/<name>/audits/<dim>/<ts>.md