Skip to content

fix(130): enforce mmdc as hard prerequisite with loud preflight/mid-render aborts#148

Merged
davidmatousek merged 8 commits intomainfrom
130-prd-130-fix
Apr 11, 2026
Merged

fix(130): enforce mmdc as hard prerequisite with loud preflight/mid-render aborts#148
davidmatousek merged 8 commits intomainfrom
130-prd-130-fix

Conversation

@davidmatousek
Copy link
Copy Markdown
Owner

Summary

Feature 130 (PRD #130) turns a silent pipeline failure into a loud, actionable one: when @mermaid-js/mermaid-cli (mmdc) is absent on a shell running /tachi.security-report against a project with Critical/High attack trees, the pipeline now aborts at preflight with a canonical three-line install command instead of silently shipping a PDF containing 40+ lines of raw flowchart TD source on every attack-path page. Defense-in-depth is enforced at two entry points (shell-level in the command file, Python-level in scripts/extract-report-data.py), the unreachable text-fallback branch in templates/tachi/security-report/attack-path.typ is deleted outright, mid-render failures (mmdc present but a specific tree fails) now abort with a per-finding error list, and mmdc is documented as a hard prerequisite across README, install.sh, the Tech Stack doc, and parent spec 112. The change is governed by the first ADR in tachi covering CLI-prerequisite posture: ADR-022.

Before / After

Before. When mmdc was absent from PATH and the target project had attack trees under attack-trees/, scripts/extract-report-data.py::render_mermaid_to_png() hit a silent shutil.which("mmdc") check that printed a one-line warning to stderr, flipped every attack-tree entry's has_image field to False, and continued. The Typst template then followed an else if mermaid-text != "" fallback branch in attack-path.typ that emitted the raw Mermaid source as a raw code block inside the PDF. The pipeline reported exit 0. The CI workflow reported success. The only way a user discovered the broken output was by flipping through the PDF and seeing flowchart TD text where a rendered attack tree should have been. The same silent-failure shape held for the rarer "mmdc installed but render crashed" case: each failing entry was marked has_image=False and the same text fallback kicked in. Both modes violated spec 112 SC-003 (legibility at page size) and directly blocked the flagship "show to exec board" deliverable.

After. The pipeline aborts at preflight with the canonical three-line error message whenever mmdc is missing and attack trees are present. Both error paths emit an identical message containing @mermaid-js/mermaid-cli, npm install -g @mermaid-js/mermaid-cli, and Attack path rendering:

Attack path rendering requires @mermaid-js/mermaid-cli (mmdc).
Install with: npm install -g @mermaid-js/mermaid-cli
Then re-run /tachi.security-report.

Defense-in-depth is implemented as two gates: a shell-level command -v mmdc in .claude/commands/tachi.security-report.md Step 1 (mirroring the existing Typst check) and a Python-level shutil.which("mmdc") inside render_mermaid_to_png() that raises RuntimeError instead of warning silently. Both checks fire only when attack-trees/*.md contains at least one file with Critical/High severity — projects without attack trees continue to work without mmdc. Mid-render failures (a specific tree failing after mmdc has passed preflight) are collected by a new per-finding aggregator that raises RuntimeError("Attack path rendering failed for N findings: ...") with each failed finding's ID, file path, failure class (exit:<code>, timeout, signal), and a 200-byte stderr excerpt (architect refinement R6). The text-fallback branch at attack-path.typ:78-86 is deleted outright — no placeholder, no comment, no "removed in 130" stub. The only render path that remains is the PNG-image branch. Release-please will auto-cut a new tachi release on merge.

FR-130.x Deliverables

FR Deliverable Commit Primary File(s)
FR-130.1 Preflight gate (shell + Python defense-in-depth) — aborts with canonical error message when mmdc absent and attack trees present db0073c .claude/commands/tachi.security-report.md, scripts/extract-report-data.py
FR-130.2 Eliminate the silent if not shutil.which("mmdc"): fallback in render_mermaid_to_png(); convert to raise RuntimeError db0073c scripts/extract-report-data.py lines 721-730
FR-130.3 Delete unreachable else if mermaid-text != "" text-fallback branch outright db0073c templates/tachi/security-report/attack-path.typ lines 78-86
FR-130.4 Mid-render failure aggregator: abort with per-finding error list (ID + file path + failure class + stderr excerpt) on any mmdc subprocess failure 732fd49 scripts/extract-report-data.py _render_single + as_completed loop
FR-130.5 Docs sync — README Prerequisites section, install.sh mmdc check, Tech Stack doc line 279, spec 112 SC-004 inversion, spec 112 research.md pymmdc correction b46e931 README.md, scripts/install.sh, docs/architecture/00_Tech_Stack/README.md, specs/112-attack-path-pages/spec.md, specs/112-attack-path-pages/research.md
FR-130.6 Baseline regeneration for mermaid-agentic-app under SOURCE_DATE_EPOCH=1700000000; agentic-app/sample-report non-baseline regeneration to confirm 47-tree rendering 648b4d1 examples/mermaid-agentic-app/security-report.pdf.baseline, examples/agentic-app/sample-report/security-report.pdf
FR-130.7 CI fresh-install acceptance test: new GitHub Actions workflow on ubuntu-latest (no mmdc preinstalled) that runs the pipeline against examples/mermaid-agentic-app/ and asserts non-zero exit with all 3 canonical tokens in stderr 648b4d1 .github/workflows/tachi-mmdc-preflight.yml

Commit lineage: 528204f scaffolded the feature workspace (PRD, spec, plan, research, ADR-022, agent-assignments), then the four implementation commits landed in topological order (US1 preflight → US2 mid-render → US3 docs → Phase 6 CI and verification).

Test Coverage

  • tests/scripts/test_mmdc_preflight.py (new file, 339 lines, 9 pytest cases):
    • 4 preflight tests (T004): test_preflight_raises_when_mmdc_missing, test_preflight_skipped_when_attack_trees_empty, test_preflight_skipped_when_only_low_medium_findings, test_preflight_error_distinct_from_midrender
    • 5 mid-render aggregator tests (T009): test_midrender_aggregator_raises_on_any_failure, test_midrender_aggregator_message_format, test_midrender_all_success_no_exception, test_midrender_all_failure_raises_with_full_list, test_midrender_error_distinct_from_preflight
    • Status: 9/9 pass (T008 + T012 verification gates)
  • tests/scripts/test_backward_compatibility.py: 5/5 baselines byte-identical under SOURCE_DATE_EPOCH=1700000000 for web-app, microservices, ascii-web-api, mermaid-agentic-app, free-text-microservice. agentic-app/sample-report/ remains intentionally excluded from BASELINE_EXAMPLES per Feature 128 decision (architect refinement R8 explicit negative task T019 verified).
  • Full pytest suite: pytest tests/ returns 48/48 pass (T030 Wave 7 verification gate). No skipped tests without documented justification.
  • CI fresh-install acceptance test: .github/workflows/tachi-mmdc-preflight.yml runs on ubuntu-latest (which ships without mmdc preinstalled), installs Typst and Python 3.11, omits the mmdc install step, then runs the pipeline against examples/mermaid-agentic-app/ and asserts exit code non-zero with all 3 canonical tokens in stderr. Includes a which mmdc || echo "expected absence" diagnostic step (architect refinement R3) and a team-lead T4 enforcement assertion that fails the job if mmdc is unexpectedly present on PATH (plan Risk feat(005): implement STRIDE threat agents #6 mitigation).

Manual Validation Results (T027 / T028 / T029)

Full evidence at .aod/results/tester-130-t027-t028-t029.md. Summary:

  • T027 (happy path, mmdc present): Replayed the T020 regeneration command against examples/mermaid-agentic-app/ on a shell with mmdc at /opt/homebrew/bin/mmdc. Pipeline exit 0; 12 Critical/High attack trees extracted; output PDF byte-identical to the committed baseline (zero-line xxd diff); 25 image xobjects embedded. Raw Mermaid source is now structurally impossible in the output — T024 confirmed the else if mermaid-text branch is deleted, so the only render path is the image branch.
  • T028 (loud abort, mmdc absent): Invoked env -i PATH="/usr/bin:/bin" /usr/bin/python3 scripts/extract-report-data.py --target-dir examples/mermaid-agentic-app/ ... (clean PATH, mmdc absent). Pipeline exit 1. RuntimeError message contains all 3 canonical tokens. No PDF produced at any output path. Error message format matches FR-130.1 spec exactly.
  • T029 (47-tree sample-report): Sub-wave 6b T021 regeneration regenerated examples/agentic-app/sample-report/security-report.pdf (6.4MB, ~10s) with 17 Critical/High trees dispatched, zero mmdc/Typst warnings; regenerated PDF embeds 39 image xobjects (higher than mermaid-agentic-app's 25, proportional to tree count 47 vs 24).

Visual PDF inspection is deferred to the PR reviewer as a belt-and-suspenders courtesy check — the automation above provides dispositive evidence that rendering works end-to-end.

Before/after backward-compatibility guardrail (architect refinement R9, High priority):

  • Pre-flight: .aod/results/130-baseline-pretest.md — 5 passed in 11.71s, all 5 baselines byte-identical, captured before any Feature 130 code changes
  • Post-flight: .aod/results/130-baseline-posttest.md — 5 passed in 8.79s, all 5 baselines byte-identical, captured after b46e931 (preflight gate + mid-render aggregator + docs landed)
  • Delta: IDENTICAL — zero byte divergence, zero divergence on any of the 5 baseline examples. The only numeric delta is wall-clock time (environmental noise). R9 fully satisfied.

Governance Artifacts

  • ADR-022 — NEW. The first ADR in tachi governing CLI-prerequisite posture. Documents the decision to treat mmdc as a hard prerequisite when attack-trees/ contains at least one Critical/High finding, the defense-in-depth two-gate rationale (parity with Feature 054 Typst precedent), the explicit rejection of pymmdc/Kroki/auto-install alternatives, a Future Work clause on extracting a reusable helper once a third CLI prerequisite is added, and cross-references to ADR-014 (optional external APIs — different class of dependency) and ADR-021 (determinism convention used by the baseline regeneration in T020/T022). The ADR notes that the canonical install command appears in exactly 7 locations across the codebase, verified by the T023 grep pass.
  • Spec 112 corrections (specs/112-attack-path-pages/spec.md, research.md):
    • SC-004 inverted: prior text ("text fallback is acceptable when rendering tool is unavailable") replaced with an assertion that rendering tool availability is verified at preflight and the pipeline aborts loudly if unavailable. Audit-trail comment <!-- Inverted by Feature 130 (2026-04-11): text fallback is no longer a supported shipping mode --> added above the new SC-004.
    • Line 135 ("text fallback is acceptable") removed/rewritten as a removed shipping mode.
    • research.md line 80 corrected: pymmdc is not a pure-Python renderer — it is a GPL-3.0 licensed Node.js wrapper around the same @mermaid-js/mermaid-cli CLI, license-incompatible with tachi's distribution model.
    • research.md lines 91-93: Durable Decision Rationale block added documenting the mmdc-hard-prerequisite choice with references to Feature 130 and the PRD Rejected Alternatives section (A through E).
  • Tech Stack doc (docs/architecture/00_Tech_Stack/README.md line 279): prior "optional with text fallback" sentence replaced by "hard prerequisite when attack-trees/ contains Critical/High findings, aborting at preflight if missing." Cross-linked to ADR-022 (plan Risk STRIDE Threat Agents #5 mitigation — ensures ADR-022 is discoverable from the Tech Stack doc).

Risk Register — What Did NOT Regress

  • Backward compatibility on the happy path: pytest tests/scripts/test_backward_compatibility.py returns 5/5 pass with the same byte-identity baselines on all 5 deterministic examples (web-app, microservices, ascii-web-api, mermaid-agentic-app, free-text-microservice). R9 before/after guardrail pair documents zero divergence. The mermaid-agentic-app baseline was regenerated in T020 and confirmed byte-identical to the prior committed baseline under SOURCE_DATE_EPOCH=1700000000 — the regeneration was a no-op verification, not a new artifact.
  • No runtime Python dependencies added: The Feature 128 runtime-stdlib-only constraint is preserved. The only modules touched in scripts/extract-report-data.py are ones already imported (shutil, subprocess, concurrent.futures, tempfile). No pip install, no new requirements.txt line, no new PyPI package.
  • agentic-app/sample-report/ stays excluded from BASELINE_EXAMPLES: Per architect refinement R8 and task T019 (explicit negative task), the Feature 128 decision to exclude this example from the byte-deterministic baseline set stands. Feature 130 does not revisit that decision. The regeneration in T021 is a correctness smoke test (47 trees render), not a baseline addition.
  • CI enforcement: The CI workflow includes team-lead T4 refinement — an enforcement assertion that fails the CI job if mmdc is unexpectedly present on PATH (plan Risk feat(005): implement STRIDE threat agents #6 mitigation). Previously this was observability-only (which mmdc || echo "expected absence"). The enforcement assertion ensures that if a future GitHub Actions runner image change ships with mmdc preinstalled, the workflow breaks visibly instead of silently validating a happy path when we intended to validate the loud-failure path.
  • Projects without attack trees are unaffected: Both preflight gates are gated on "at least one Critical/High attack tree file present in attack-trees/." Projects using tachi for threat modeling without the Feature 112 attack-path output continue to work unchanged without mmdc installed.

Review Checklist / Test Plan

  • Pull the branch: git fetch origin && git checkout 130-prd-130-fix
  • Run the preflight pytest suite: pytest tests/scripts/test_mmdc_preflight.py -v — expect 9 passed
  • Run the backward-compatibility suite: SOURCE_DATE_EPOCH=1700000000 pytest tests/scripts/test_backward_compatibility.py -v — expect 5 passed
  • Run the full pytest suite: pytest tests/ — expect 48/48 pass
  • Visual PDF inspection (happy path, 24 trees): Open examples/mermaid-agentic-app/security-report.pdf.baseline and scroll to the Attack Path section. Confirm every attack path page shows a rendered Mermaid diagram image (not raw flowchart TD source). Node labels and edge connections should be legible at page size per spec 112 SC-003.
  • Visual PDF inspection (47 trees): Open examples/agentic-app/sample-report/security-report.pdf and confirm all 17 Critical/High attack path pages render as images. (This example is intentionally excluded from the byte-deterministic baseline set per Feature 128; the visual check is the correctness gate.)
  • Loud-failure reproduction: On a shell with mmdc present, run:
    env -i PATH="/usr/bin:/bin" /usr/bin/python3 scripts/extract-report-data.py \
      --target-dir examples/mermaid-agentic-app/ \
      --output /tmp/out.typ \
      --template-dir templates/tachi/security-report/
    Expect exit 1 with a RuntimeError containing @mermaid-js/mermaid-cli, npm install -g @mermaid-js/mermaid-cli, and Attack path rendering. Confirm no PDF is produced at /tmp/out.typ.pdf or elsewhere.
  • CI check: Wait for the .github/workflows/tachi-mmdc-preflight.yml workflow to pass on this PR. Confirm the diagnostic step logs "expected absence: mmdc not on PATH, preflight gate should fire" and the assertion step confirms all 3 canonical tokens appear in captured stderr.
  • Governance review: Read docs/architecture/02_ADRs/ADR-022-mmdc-hard-prerequisite.md for the decision rationale, the Future Work clause on helper extraction, and the cross-references to ADR-014 and ADR-021. Confirm the ADR accurately reflects the shipped implementation.
  • Dead-code verification (SC-130.6): grep -n 'else if mermaid-text' templates/tachi/security-report/attack-path.typ — expect zero results. grep -rn 'npm install -g @mermaid-js/mermaid-cli' — expect exactly 7 locations (extract-report-data.py, command file, install.sh, README, test file, CI workflow, ADR-022).

🤖 Generated with Claude Code

davidmatousek and others added 8 commits April 11, 2026 10:37
Feature 130 fixes silent failure in attack path Mermaid rendering when
mmdc is not installed. This commit lays the governance groundwork for
the fix: PRD, spec, plan, research, tasks, agent assignments, and a new
ADR establishing mmdc as a hard prerequisite.

- PRD 130: Fix Attack Path Mermaid Rendering (PM/Architect/Team-Lead approved)
- ADR-022: First ADR governing CLI-prerequisite posture in tachi.
  Decision: mmdc is a hard prerequisite when attack-trees/ contains
  Critical/High findings. Cross-refs ADR-014 (optional external APIs)
  and ADR-021 (determinism). Future Work clause defers install.sh
  helper extraction until a 3rd CLI prereq arrives.
- Spec/plan/tasks/research/agent-assignments under specs/130-prd-130-fix/
- docs/architecture/01_system_design/README.md: Feature 130 components
  section covering preflight gate (shell + Python), mid-render
  aggregator, CI workflow, and docs sync cluster.
- docs/product/02_PRD/INDEX.md: add row 130
- docs/product/_backlog/BACKLOG.md: regenerate after Issue #130 moved
  to Build stage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Turn silent failure into loud failure when mmdc is missing. Previously
the pipeline silently fell back to raw Mermaid text in the PDF when
mmdc was unavailable; now the pipeline aborts at preflight with a
canonical three-line install message and non-zero exit.

User Story 1 (T004-T008) — prerequisites are enforced, not assumed:

- .claude/commands/tachi.security-report.md Step 2: shell-level gate.
  Detects attack-trees/*.md presence, then `command -v mmdc`. If mmdc
  is missing, echoes canonical message to stderr and halts non-zero.
  Mirrors the existing Typst check. Skip the check on projects without
  attack trees — they do not need mmdc.
- scripts/extract-report-data.py render_mermaid_to_png(): replace the
  silent shutil.which("mmdc") warn+fallback with raise RuntimeError
  using the canonical message. Defense-in-depth for direct Python
  invocations (tests, tooling).
  Also: add `sys.path.insert(0, ...)` before the tachi_parsers import,
  parallel to extract-infographic-data.py, required enabler for the
  importlib-based test fixture.
- templates/tachi/security-report/attack-path.typ: delete the
  `else if mermaid-text != ""` branch entirely. With the preflight
  gate guaranteeing mmdc presence, the text-fallback branch is dead
  code. The `if has-img and img-path != ""` branch is now the only
  render path.
- tests/scripts/test_mmdc_preflight.py (new): 4 preflight tests +
  5 mid-render aggregator tests. The 4 preflight tests pass; the
  5 mid-render tests fail as expected (Wave 4 T010/T011 will land
  the module-level _render_single + failure aggregator to make
  them green).

Verification:
- 4/4 preflight tests green under test_mmdc_preflight.py -k preflight
- 5/5 backward-compatibility baselines still byte-identical under
  SOURCE_DATE_EPOCH=1700000000 (happy path unchanged)
- Canonical three-line error message contains: @mermaid-js/mermaid-cli,
  `npm install -g @mermaid-js/mermaid-cli`, and "Attack path rendering"

Tasks: T001-T008 marked [X] in tasks.md. Waves 1-3 complete (8/32);
Wave 4 (US2 mid-render aggregator) next.

Refs: ADR-022, spec.md FR-130.1/FR-130.3, plan.md Phase 1 Design
R5-R7 refinements

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
User Story 2 (T009-T012) — mid-render failures are loud, not silent.

When mmdc is installed but a specific attack tree fails to render
(syntax error, crash, timeout), the pipeline previously set
has_image=False and silently produced a PDF with some pages missing.
Now any render failure aborts the pipeline with an informative per-
finding error list on stderr.

Implementation:

- scripts/extract-report-data.py: promote _render_single from nested
  closure to module-level function so tests can patch.object on it.
  Extend the return shape to (entry, success, value) where on failure
  `value` is a structured error record dict:
  - id: finding ID
  - file_path: canonical "attack-trees/{fid_lower}.mmd" path
  - failure_class: "exit:<code>", "timeout", or "signal"
  - stderr_excerpt: first 200 bytes of stderr (utf-8, errors=replace)

- render_mermaid_to_png() as_completed loop: collect failures into a
  list. When the loop ends, if the failure list is non-empty, format
  per R6 spec and raise RuntimeError:

      Attack path rendering failed for N findings:
        - F-002 (attack-trees/f-002.mmd)
          failure: exit:2
          stderr: Parse error on line 5

  R6 (the highest-priority architect refinement) is the format that
  decides whether this feature delivers on its fail-loud promise.

- _render_single uses two module-level globals (_render_attack_trees_dir
  and _render_rel_target) published by render_mermaid_to_png before
  pool execution. This keeps _render_single's signature at 2 positional
  args (entry, tmp_path) — required by the test harness's 2-arg mock.
  Thread-safe for the sequential CLI model (documented inline).

- No new imports. No new dependencies. Runtime stdlib-only constraint
  preserved per ADR-001 / Constitution Principle III.

Verification:

- 9/9 tests pass under test_mmdc_preflight.py (4 preflight + 5 mid-render)
- 5/5 backward-compatibility baselines still byte-identical under
  SOURCE_DATE_EPOCH=1700000000 — happy path is unchanged
- R6 format assertions verified:
  * summary line "Attack path rendering failed for N findings:"
  * per-finding ID + file path + failure class + stderr excerpt
- R7 distinction verified: mid-render message does NOT contain
  "npm install -g @mermaid-js/mermaid-cli" (preflight's distinct shape)

Tasks: T009-T012 marked [X]. Wave 4 complete (12/32, 37.5%).
Wave 5 (US3 docs sync, T013-T017) next.

Refs: spec.md FR-130.3/FR-130.4, plan.md R5/R6/R7 refinements,
ADR-022

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…h, Tech Stack, spec 112

User Story 3 (T013-T017) — document the mmdc hard-prerequisite posture
consistently across all user-facing and spec-level documentation.

Changes:

- README.md: new Prerequisites section between "What is tachi?" and
  "Quick Start". Names typst and @mermaid-js/mermaid-cli as required
  external CLIs with macOS/Linux/WSL install commands. Cross-links to
  ADR-022 for rationale.
- scripts/install.sh: courtesy warning block if mmdc is missing. Points
  users to README Prerequisites and the canonical install command.
  Does NOT check Typst (scope containment per plan S2 decision — the
  per-command preflight is the enforcement, install.sh warning is
  advisory only).
- docs/architecture/00_Tech_Stack/README.md: update mmdc entry (line
  272) from "optional with graceful fallback" to "hard prerequisite
  as of Feature 130 — see ADR-022". Rewrite the surrounding note
  (line 279) to describe preflight enforcement with cross-link to
  ADR-022. Makes ADR-022 discoverable from Tech Stack per plan Risk #5.
- specs/112-attack-path-pages/spec.md: invert SC-004. Was "When the
  rendering tool is unavailable, 100% of attack path pages still
  appear with text fallback." Now "Rendering tool availability is
  verified at preflight; pipeline aborts loudly if unavailable." Adds
  audit-trail comment above the inverted criterion:
  <!-- Inverted by Feature 130 (2026-04-11): text fallback is no
  longer a supported shipping mode -->
  Also rewrites the Assumptions bullet that previously said mmdc is
  not a hard dependency.
- specs/112-attack-path-pages/research.md: correct the pymmdc factual
  error — pymmdc on PyPI is a GPL-3.0 Python wrapper around the
  Node.js @mermaid-js/mermaid-cli CLI, not a pure-Python renderer.
  Adds a "Durable Decision Rationale" block documenting the
  mmdc-hard-prereq choice with references to Feature 130 PRD Rejected
  Alternatives (A-E) and ADR-022.

Verification:
- All 14 tests still green (9 preflight + 5 baselines)
- Canonical install command `npm install -g @mermaid-js/mermaid-cli`
  appears verbatim in all 5 edited files
- 8 ADR-022 references across 4 files (Tech Stack + spec/research 112
  + README)
- Audit-trail comment present in spec 112 with exact date 2026-04-11

Tasks: T013-T017 marked [X]. Wave 5 complete (17/32, 53%).
Wave 6 (Cross-Cutting & CI, T018-T029) next.

Refs: spec.md FR-130.5/FR-130.6, ADR-022, plan Risk #5

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wave 6 of Feature 130 — cross-cutting artifacts and verification gates.

CI workflow (T018, FR-130.7):
- .github/workflows/tachi-mmdc-preflight.yml on ubuntu-latest
- Installs typst-community/setup-typst@v5 + Python 3.11; does NOT install mmdc
- Diagnostic echo (R3) + enforcement assertion (team-lead T4, plan Risk #6) —
  fails workflow if mmdc unexpectedly appears on PATH
- Invokes python3 scripts/extract-report-data.py directly (no slash-command
  agent orchestration in CI); asserts non-zero exit + all 3 canonical tokens
  (@mermaid-js/mermaid-cli, npm install -g @mermaid-js/mermaid-cli,
  Attack path rendering) grep-asserted individually

Docs (T025, T026):
- CLAUDE.md Recent Changes: prepend Feature 130 entry above Feature 136
- specs/130-prd-130-fix/quickstart.md: developer reproduction, loud-failure
  validation, happy-path baseline run, regeneration instructions

Verification (T019-T024, T027-T031 automation):
- T019: BASELINE_EXAMPLES confirmed to exclude agentic-app/sample-report/
  (R8 negative — Feature 128 decision stands)
- T020/T021: mermaid-agentic-app baseline and agentic-app/sample-report PDFs
  regenerated under SOURCE_DATE_EPOCH=1700000000; byte-identical to prior
- T022: backward-compat 5/5 baselines byte-identical vs T002 pre-flight
  snapshot (R9 before/after guardrail pair complete)
- T023: canonical install command appears in exactly the 7 required
  enforcement locations (R4)
- T024: dead-code greps return zero — else-if-mermaid-text branch deleted
  in attack-path.typ, silent-fallback has_image=False loop replaced with
  raise RuntimeError in extract-report-data.py shutil.which context
- T027/T028/T029 automated: pipeline exit 0 with mmdc present (byte-ident
  to baseline, 25 image xobjects); pipeline exit 1 with clean PATH emitting
  all 3 canonical tokens; 47-tree sample-report embeds 39 image xobjects
- T030: full pytest 48/48 pass
- T031: constitutional walk-through — no runtime deps added, stdlib-only
  preserved, feature branch workflow intact, conventional commits

Tasks.md: T018-T031 marked [X].

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wave 7 polish — code-reviewer non-blocking fixes and PR assembly.

- templates/tachi/security-report/attack-path.typ: remove stale
  "image or text fallback" references in header comment (line 4-5) and
  attack-path-page parameter docstring (line 38-39). The `mermaid-text`
  field and text-fallback branch were deleted in T007; comments now
  reflect that only the rendered-image path exists. Pure comment change —
  backward-compat baselines verified byte-identical post-fix.
- specs/130-prd-130-fix/PR-description.md: T032 PR body for /aod.deliver.
  Assembles FR-130.1 through FR-130.7 deliverables with commit SHAs,
  Before/After CHANGELOG narrative, test coverage summary, automated
  manual-validation results (T027/T028/T029), governance links (ADR-022,
  spec 112 corrections, Tech Stack doc), and reviewer checklist.
- specs/130-prd-130-fix/tasks.md: T032 marked [X]. All 32 tasks complete.
- docs/product/_backlog/BACKLOG.md: timestamp bump from /aod.build Step 1
  GitHub stage label update (issue #130 to stage:build).

Full suite 48/48 pass. Backward-compat 5/5 baselines byte-identical under
SOURCE_DATE_EPOCH=1700000000. Feature ready for Step 6 security scan.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SAST: 3 files scanned (extract-report-data.py, install.sh,
test_mmdc_preflight.py). OWASP P0 pattern review clean — no injection,
no hardcoded secrets, no path traversal introduced, no weak crypto,
no auth/session handling.

SCA: skipped — no dependency manifests changed on this feature branch.

Status: PASSED, 0 findings across all severity tiers.

Scan ID: 65936668-f62b-4548-a9bd-e503c6e114ff
Chain hash: 0bf47a425fcfddef942052831cb8ea9e25a3629d3b9529a45383a4136d844892

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
T030: full pytest suite 48/48 green
T031: conventional commits, stdlib-only, branch hygiene verified

Co-Authored-By: Claude <noreply@anthropic.com>
@davidmatousek davidmatousek merged commit d35a667 into main Apr 11, 2026
1 check passed
@davidmatousek davidmatousek deleted the 130-prd-130-fix branch April 11, 2026 16:11
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.

1 participant