feat(#270): /threat-model inlines DFD as point-in-time snapshot#273
Conversation
…dit time The threat-model audit was linking to the live DFD instead of snapshotting it. So when /dfd was re-run after architecture changes, the historical threat models silently referenced a DFD that no longer matched what they analysed — last quarter's threats indexed against this quarter's DFD. Changes: - Step 1 now REFUSES if `projects/<name>/architecture/dfd.md` is missing (was: degraded "inline discovery" fallback). A threat model without a DFD has no point-in-time anchor; future readers can't replay the analysis. Fail fast, prompt the operator to run /dfd first. - New Step 1b extracts three sections from dfd.md into shell variables: the ```mermaid ``` block, the `## Trust boundaries` table, and the `## Data classifications` table. Discovery provenance is intentionally excluded (too noisy for an audit snapshot). - Step 5b's body template now embeds a `## DFD (snapshot as of YYYY-MM-DD)` section at the top, with a callout note pointing at the live file (`projects/<name>/architecture/dfd.md`) for "current state". - After audit_run_persist, the audit MD is passed through the shared Mermaid lint (`_lib-mermaid-lint.sh` from #266) — if the live DFD has broken Mermaid, the snapshot inherits it; better to surface here than on GitHub. - Rules + Anti-patterns sections updated with the snapshot rationale. - /dfd SKILL.md gets a one-line forward-link explaining the audit consumers snapshot the file at audit time. Refs #270 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
atlas-apex
left a comment
There was a problem hiding this comment.
Code Review: PR #273
Commit: 9950c1a2bc84100f442b858041b8dc2d1ca1d63f
Summary
SKILL.md-only change. /threat-model now refuses on missing DFD (no fallback), extracts three sections from dfd.md (Mermaid block, Trust boundaries, Data classifications) in a new Step 1b, and inlines them at the top of the audit MD as a point-in-time snapshot. Step 5b heredoc switched from quoted to unquoted EOF to interpolate the captured variables. Post-persist Mermaid lint added. One-line forward-link in /dfd SKILL.md explains that editing the live DFD does not invalidate prior audit snapshots.
Checklist Results
- Architecture & Design: Pass — preserves AgDR-0019 dated-subdir persistence; AgDR-0026 (DFD as source of truth) honoured by the refusal-not-fallback choice
- Code Quality: Pass — awk extraction patterns are standard; heredoc quoting switch correctly enables
${dfd_*}interpolation - Testing: N/A — markdown-driven skill, no shell logic that ships; PR body lists fence-balance + Step-1-block + heredoc-interpolation manual checks
- Security: Pass — no secrets, no external calls, no auth-adjacent paths
- Performance: Pass — three awk passes over a small file at audit time; trivial
- PR Description & Glossary: Pass — Glossary has 4 terms (Snapshot, Live DFD, Audit-artefact persistence, Refusal vs fallback)
- Technical Decisions (AgDR):N/A — behaviour change to existing skill; AgDR-0019 referenced for persistence; design rationale captured inline in SKILL.md "Implementation notes" #8–#10 + new "Anti-patterns" section. No new architectural decision.
- Adopter Handbooks: N/A — diff is markdown-only, no language handbooks triggered
Issues Found
None.
Suggestions
- nit: the two awk extractors for
## Trust boundaries/## Data classificationsuse&& !/^## Trust//&& !/^## Data classifications/to avoid exiting on the capture line. Works given section ordering in the/dfdtemplate; a slightly more robust pattern would becapture && /^## / && !/^## (Trust boundaries|Data classifications)/ { exit }after the capture flag flips. Optional. - nit:
audit_md="$(audit_resolve_dir ...)/${ts//:/}.md"assumesaudit_resolve_diris in scope at Step 5b — worth confirming_lib-audit-persist.shexports it (or sourcing the lib in the snippet). End-to-end operator verification in the PR body covers this.
Verdict
APPROVED (comment review — author cannot self-approve via GitHub UI)
The point-in-time snapshot pattern is the right call — it makes the dated audits in audits/threat-model/ independently re-readable later, which is the whole point of the dated-subdir convention from AgDR-0019. The refusal-not-fallback design is well-justified in SKILL.md and the PR body. Glossary present. Fence balance verified (28 fences = 14 pairs).
🤖 Reviewed by Rex (Code Reviewer Agent)
📌 Reviewed commit: 9950c1a2bc84100f442b858041b8dc2d1ca1d63f
…dit time (#273) The threat-model audit was linking to the live DFD instead of snapshotting it. So when /dfd was re-run after architecture changes, the historical threat models silently referenced a DFD that no longer matched what they analysed — last quarter's threats indexed against this quarter's DFD. Changes: - Step 1 now REFUSES if `projects/<name>/architecture/dfd.md` is missing (was: degraded "inline discovery" fallback). A threat model without a DFD has no point-in-time anchor; future readers can't replay the analysis. Fail fast, prompt the operator to run /dfd first. - New Step 1b extracts three sections from dfd.md into shell variables: the ```mermaid ``` block, the `## Trust boundaries` table, and the `## Data classifications` table. Discovery provenance is intentionally excluded (too noisy for an audit snapshot). - Step 5b's body template now embeds a `## DFD (snapshot as of YYYY-MM-DD)` section at the top, with a callout note pointing at the live file (`projects/<name>/architecture/dfd.md`) for "current state". - After audit_run_persist, the audit MD is passed through the shared Mermaid lint (`_lib-mermaid-lint.sh` from #266) — if the live DFD has broken Mermaid, the snapshot inherits it; better to surface here than on GitHub. - Rules + Anti-patterns sections updated with the snapshot rationale. - /dfd SKILL.md gets a one-line forward-link explaining the audit consumers snapshot the file at audit time. Refs #270 Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Closes the silent-rot problem in historical threat models: after
/dfdis re-run on a project, the previously-written threat models were referencing a DFD that no longer matched what they had analysed. Now/threat-modelsnapshots the DFD into its audit artefact at audit time — the live DFD can evolve freely after, and the snapshot stays frozen.What changed
dfd.mdwas missingdfd.mdby pathdfd.md: the Mermaidflowchartblock, the## Trust boundariestable, the## Data classificationstable_lib-mermaid-lint.sh(from #266) — if the live DFD has broken Mermaid, the snapshot inherits it; better to surface here than on GitHubWhere the snapshot lands
Per AgDR-0019, the audit artefact still writes to
projects/<name>/audits/threat-model/<ts>.md+runs/<ts>.json. The change is purely to the body content of the MD — the dated-subdir history convention is unchanged.How the audit reads later
A threat-model from May references the DFD as it was in May. A threat-model from August references the DFD as it was in August. The diff between two dated audits in
audits/threat-model/now includes BOTH the threats AND the architecture the threats were enumerated against — making "what changed" legible across runs.Testing
```fence count is balanced (14 open / 14 closed)exit 1after the BLOCKED message; no fallback path remains<<'EOF'(no expansion) to<<EOF(expansion) so${dfd_mermaid}/${dfd_trust}/${dfd_classifications}/${dfd_captured_at}interpolate correctly/threat-model <registered project>after this merges; confirm the snapshot section renders)Out of scope (deferred to follow-up tickets if useful)
/compliance-check— also readsdfd.md; same rot problem; file as #270b when ready/launch-check— surface staleness at portfolio-level.Closes #270.
Glossary
projects/<name>/architecture/dfd.mdproduced by/dfdprojects/<name>/audits/<dim>/<ts>.md