Skip to content

feat(#270): /threat-model inlines DFD as point-in-time snapshot#273

Merged
atlas-apex merged 1 commit into
devfrom
feature/GH-270-threat-model-dfd-snapshot
May 17, 2026
Merged

feat(#270): /threat-model inlines DFD as point-in-time snapshot#273
atlas-apex merged 1 commit into
devfrom
feature/GH-270-threat-model-dfd-snapshot

Conversation

@atlas-apex

Copy link
Copy Markdown
Collaborator

Summary

Closes the silent-rot problem in historical threat models: after /dfd is re-run on a project, the previously-written threat models were referencing a DFD that no longer matched what they had analysed. Now /threat-model snapshots the DFD into its audit artefact at audit time — the live DFD can evolve freely after, and the snapshot stays frozen.

What changed

Before After
Step 1 offered a fallback if dfd.md was missing Step 1 refuses — no DFD, no threat model. A threat model without a DFD has no point-in-time anchor.
Audit artefact linked to the live dfd.md by path Audit artefact inlines three sections from dfd.md: the Mermaid flowchart block, the ## Trust boundaries table, the ## Data classifications table
(no provenance — same) Provenance section deliberately excluded — too noisy for a snapshot; readers click through to the live DFD for that
(no Mermaid lint) After persistence, the audit MD goes through _lib-mermaid-lint.sh (from #266) — if the live DFD has broken Mermaid, the snapshot inherits it; better to surface here than on GitHub

Where 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

  • Markdown lint — ``` fence count is balanced (14 open / 14 closed)
  • Step 1 block on missing DFD verified by reading the changed Step 1 — exit 1 after the BLOCKED message; no fallback path remains
  • Variable interpolation in Step 5b heredoc — switched from <<'EOF' (no expansion) to <<EOF (expansion) so ${dfd_mermaid} / ${dfd_trust} / ${dfd_classifications} / ${dfd_captured_at} interpolate correctly
  • End-to-end run against a real registered project — operator verification (run /threat-model <registered project> after this merges; confirm the snapshot section renders)

Out of scope (deferred to follow-up tickets if useful)

  • Same snapshot pattern for /compliance-check — also reads dfd.md; same rot problem; file as #270b when ready
  • Drift detection between threat-model runs — compare prior snapshot to current live DFD, flag deltas. v2 concern.
  • "DFD last changed N days ago" warning in /launch-check — surface staleness at portfolio-level.

Closes #270.

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 / #218projects/<name>/audits/<dim>/<ts>.md
Refusal vs fallback The Step 1 design choice: fail-fast when the DFD is missing rather than producing a degraded audit

…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 atlas-apex left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 classifications use && !/^## Trust/ / && !/^## Data classifications/ to avoid exiting on the capture line. Works given section ordering in the /dfd template; a slightly more robust pattern would be capture && /^## / && !/^## (Trust boundaries|Data classifications)/ { exit } after the capture flag flips. Optional.
  • nit: audit_md="$(audit_resolve_dir ...)/${ts//:/}.md" assumes audit_resolve_dir is in scope at Step 5b — worth confirming _lib-audit-persist.sh exports 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

@atlas-apex atlas-apex merged commit 988a3a2 into dev May 17, 2026
3 checks passed
@atlas-apex atlas-apex deleted the feature/GH-270-threat-model-dfd-snapshot branch May 17, 2026 23:11
me2resh added a commit that referenced this pull request Jun 5, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants