You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
**Severity**: High (user-visible correctness regression)
603
+
604
+
**Problem**: Feature 112 (attack-path-pages) shipped a Typst "text-fallback" branch in `templates/tachi/security-report/attack-path.typ` that emitted raw `flowchart TD` Mermaid source inline when `@mermaid-js/mermaid-cli` (`mmdc`) was unavailable. The branch was gated by an `else if mermaid-text != ""` clause in the template and supported by a silent `shutil.which("mmdc")` early-return in `scripts/extract-report-data.py::render_mermaid_to_png()` that flipped every entry's `has_image` to `False`, printed a one-line warning, and continued. The pipeline reported exit 0. CI reported success. The only way a user discovered the broken output was by flipping through a board-ready PDF and seeing 40+ lines of raw Mermaid source where a rendered attack tree should have been. This directly blocked the flagship "show to exec board" deliverable from spec 112 and violated AC#2 (legibility at page size).
605
+
606
+
**Root Cause**: "Graceful degradation" became "silent failure" because the fallback branch was never reachable in any realistic user path — nobody runs `/tachi.security-report`*hoping* mmdc is missing and *hoping* the PDF ships with raw Mermaid source as a substitute. The fallback existed because the Feature 112 author treated it as a courtesy safety net. In practice it masked a prerequisite violation behind a success exit code. The same failure shape held for the rarer "mmdc installed but subprocess crashed" case: each failing entry was silently marked `has_image=False` and the same text fallback kicked in. Both modes violated SC-003 (legibility) and both were invisible to CI.
607
+
608
+
**Solution**: For CLI prerequisites that are actually required at runtime (not optional external APIs in the ADR-014 sense), enforce at two entry points — shell-level at the command file (mirroring the Typst check that already existed) AND Python-level at the function boundary (`shutil.which(...) → raise RuntimeError(...)`). Gate the check on input detection so projects without the triggering input are unaffected (mmdc check fires only when `attack-trees/` contains Critical/High findings). Delete the fallback branch outright — no placeholder, no comment, no "removed in NNN" stub. Document the rule in a governing ADR (ADR-022 is the first tachi ADR covering CLI-prerequisite posture). Prove backward compatibility with a byte-deterministic baseline pair under `SOURCE_DATE_EPOCH` (ADR-021) before and after the refactor: happy path must be byte-identical.
609
+
610
+
**Result**: 48/48 pytest green (9 new cases covering both preflight and mid-render aggregator). 5/5 byte-deterministic baselines remained byte-identical before/after the refactor — the happy path is provably unchanged. New fresh-install CI workflow on `ubuntu-latest` (no mmdc preinstalled) asserts the loud-failure path fires with all three canonical tokens in stderr, including a team-lead T4 enforcement assertion that fails the CI job if mmdc is unexpectedly present on PATH. The canonical install command `npm install -g @mermaid-js/mermaid-cli` appears in exactly 7 coordinated enforcement locations (extract-report-data.py raise, tachi.security-report.md shell echo, install.sh warning, README Prerequisites, test_mmdc_preflight.py assertion, tachi-mmdc-preflight.yml grep, ADR-022 decision body), verified by the T023 grep consistency check.
611
+
612
+
**When to Apply**: Any time the codebase adds a runtime CLI prerequisite (third-party binary, language runtime, renderer, compiler, tool that must be on PATH). The two-gate defense-in-depth pattern is now the tachi convention for CLI prerequisites, analogous to how Feature 054 Typst checks already work. If a third CLI prerequisite is ever added, per ADR-022 Future Work, extract an `install.sh` prerequisite helper — three data points is the minimum for meaningful abstraction. Do NOT preserve a fallback branch as a "courtesy" — if the fallback is worse than a loud error, delete it. A silent fallback that produces a broken-looking output is worse than no fallback at all.
Copy file name to clipboardExpand all lines: docs/architecture/README.md
+2-1Lines changed: 2 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,6 @@
1
1
# Architecture Documentation - tachi
2
2
3
-
**Last Updated**: 2026-04-10
3
+
**Last Updated**: 2026-04-11
4
4
**Owner**: Architect
5
5
**Status**: Template
6
6
@@ -44,6 +44,7 @@ Significant technical decisions with context and trade-offs
44
44
-`ADR-019-shared-definitions-and-model-field-governance.md` - Shared cross-agent definitions and model field governance (Feature 078)
45
45
-`ADR-020-maestro-layer-classification.md` - CSA MAESTRO seven-layer taxonomy for agentic AI component classification (Feature 084; Revision History adds canonical layer rename rule for enum-value-only minor schema bumps in Feature 136)
46
46
-`ADR-021-source-date-epoch-for-deterministic-pdf-comparison.md` - SOURCE_DATE_EPOCH reproducible-builds convention for byte-deterministic PDF baseline comparison (Feature 128)
47
+
-`ADR-022-mmdc-hard-prerequisite.md` - `mmdc` (Mermaid CLI) as hard prerequisite gated on attack-tree detection; establishes fail-loud-on-missing-CLI posture with defense-in-depth preflight gates — first ADR governing CLI-prerequisite posture (Feature 130)
Copy file name to clipboardExpand all lines: docs/devops/01_Local/README.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -19,7 +19,7 @@
19
19
-**jq**: JSON processor, required by `.aod/scripts/bash/run-state.sh` for the Full Lifecycle Orchestrator (`brew install jq` on macOS, `apt-get install jq` on Linux)
20
20
-**GitHub CLI (`gh`)**: Used by `make init` to auto-create a GitHub Projects board for backlog tracking. Requires the `project` OAuth scope (`gh auth refresh -s project`). If not installed or not authenticated, init continues without creating the board. Install via `brew install gh` on macOS or see [cli.github.com](https://cli.github.com)
21
21
-**Typst CLI**: Required by `/tachi.security-report` for PDF generation. Install via `brew install typst` on macOS, `cargo install typst-cli` on Linux, or `winget install typst` on Windows. If not installed, the `/tachi.security-report` command displays platform-specific install instructions and halts. See `templates/tachi/security-report/` for Typst template sources
22
-
-**Mermaid CLI (`mmdc`)**: Optional. Used by `scripts/extract-report-data.py` to render Mermaid attack tree diagrams to PNG for the Attack Path Pages section of the PDF security report (Feature 112). Requires Node.js. Install via `npm install -g @mermaid-js/mermaid-cli`. If not installed, the extraction script logs a warning and the attack path pages omit rendered diagram images while retaining narrative and remediation content. Only relevant when the scanned project contains an `attack-trees/` directory
22
+
- **Mermaid CLI (`mmdc`)**: **Hard prerequisite when scanning projects that contain an `attack-trees/` directory with Critical/High findings** (Feature 130, [ADR-022](../../architecture/02_ADRs/ADR-022-mmdc-hard-prerequisite.md)). Used by `scripts/extract-report-data.py::render_mermaid_to_png()` to render Mermaid attack tree diagrams to PNG for the Attack Path Pages section of the PDF security report (Feature 112). Requires Node.js. Install via `npm install -g @mermaid-js/mermaid-cli`. If not installed and attack trees are present, the `/tachi.security-report` pipeline fails loud at the preflight gate with a `RuntimeError` listing the install command — this replaces the pre-Feature-130 silent text fallback which produced broken PDFs with raw Mermaid source dumped verbatim. For projects WITHOUT an `attack-trees/` directory, mmdc remains unused and the pipeline continues to run unaffected. `scripts/install.sh` emits a best-effort courtesy warning at setup time if mmdc is absent. See `README.md` `## Prerequisites` section for per-OS install commands and the CI acceptance test at `.github/workflows/tachi-mmdc-preflight.yml`
Copy file name to clipboardExpand all lines: docs/devops/CI_CD_GUIDE.md
+56Lines changed: 56 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -239,6 +239,62 @@ permissions:
239
239
240
240
---
241
241
242
+
### tachi mmdc Preflight Gate (Feature 130)
243
+
244
+
**Best For**: Proving the `/tachi.security-report` preflight gate fires on a fresh runner where `@mermaid-js/mermaid-cli` (`mmdc`) is intentionally absent
245
+
246
+
Feature 130 introduced a CI acceptance test at `.github/workflows/tachi-mmdc-preflight.yml` that verifies the fail-loud preflight behavior documented in [ADR-022](../architecture/02_ADRs/ADR-022-mmdc-hard-prerequisite.md). This workflow is **not** a general CI/CD pipeline — it is a single-job verification that the pipeline aborts non-zero when `mmdc` is absent and the scanned project contains attack trees.
**Runner**: `ubuntu-latest` — intentionally chosen because `ubuntu-latest` ships WITHOUT `@mermaid-js/mermaid-cli` preinstalled (plan.md spike S3 confirmed). Do NOT switch to a custom image that preinstalls mmdc — that would silently validate the happy path instead of the loud-failure path this workflow is designed to exercise.
259
+
260
+
**Job Steps**:
261
+
262
+
1. **Checkout repository** — standard `actions/checkout@v4`
263
+
2. **Set up Python 3.11** — via `actions/setup-python@v5`
264
+
3. **Set up Typst** — via `typst-community/setup-typst@v5` (must NOT transitively install Node.js tooling; see T4 enforcement below)
265
+
4. **Diagnostic: show mmdc absence** — logs `which mmdc` output for human-readable CI debugging (architect refinement R3)
266
+
5. **Enforce mmdc absence (T4 / plan Risk #6)** — **team-lead T4 enforcement assertion** that fails the job if `command -v mmdc` unexpectedly succeeds. This guards against future upstream changes (e.g., a new major version of `typst-community/setup-typst` that transitively installs Node.js tooling, or a GitHub Actions runner image update that adds mmdc to the base image) that would silently break the spike-S3 assumption and make the subsequent preflight test meaningless. If this assertion ever fires, treat it as a high-severity CI infrastructure bug — do NOT install mmdc to "fix" it.
267
+
6. **Run `scripts/extract-report-data.py` against `examples/mermaid-agentic-app/`** — direct Python invocation with `--target-dir`, `--output`, `--template-dir`. Slash commands cannot run in CI, so we invoke the script directly. The `mermaid-agentic-app` example contains an `attack-trees/` directory with Critical/High findings, so the preflight gate is expected to fire. Expected exit code: non-zero. Captures stdout+stderr to `/tmp/out.txt` for the next step.
268
+
7. **Assert canonical error tokens present** — greps `/tmp/out.txt` for three tokens from the canonical preflight `RuntimeError` message:
269
+
- `@mermaid-js/mermaid-cli`
270
+
- `npm install -g @mermaid-js/mermaid-cli`
271
+
- `Attack path rendering`
272
+
273
+
Each token is grepped individually so a missing token produces a specific, actionable error message. This is the **seven-location canonical command consistency** guarantee — the install command appears in exactly 7 enforcement locations across the codebase (extract-report-data.py raise, tachi.security-report.md shell echo, install.sh warning, README Prerequisites, test_mmdc_preflight.py assertion, tachi-mmdc-preflight.yml grep, ADR-022 decision body). Any drift fails this workflow.
274
+
275
+
**Required Permissions**:
276
+
```yaml
277
+
permissions:
278
+
contents: read
279
+
```
280
+
281
+
**No Repository Secrets Required**: The workflow uses only `GITHUB_TOKEN` implicitly via `actions/checkout@v4`.
282
+
283
+
**What This Workflow Does NOT Test**:
284
+
- Happy-path rendering when `mmdc` IS installed (covered by local pytest suite `tests/scripts/test_mmdc_preflight.py` and backward-compatibility baselines in `tests/scripts/test_backward_compatibility.py`)
285
+
- Non-attack-tree projects (covered by the backward-compatibility baseline suite — 5 example PDFs remain byte-identical without mmdc required)
286
+
- Mid-render failures (covered by the 5 aggregator tests in `tests/scripts/test_mmdc_preflight.py`)
287
+
288
+
**Infrastructure Impact**: No new environment variables, Docker services, or deployment changes. The workflow runs entirely within GitHub Actions using the standard `ubuntu-latest` image.
289
+
290
+
**Related Files**:
291
+
- `scripts/extract-report-data.py`(`render_mermaid_to_png()` is the enforcement point)
- `specs/130-prd-130-fix/plan.md`(spike S3 and Risk #6 mitigation)
295
+
296
+
---
297
+
242
298
### Python Test Harness (pytest)
243
299
244
300
**Best For**: Validating deterministic data extraction scripts, command dispatch, PDF page positioning, and golden-file comparisons for tachi pipeline outputs
Copy file name to clipboardExpand all lines: docs/devops/README.md
+33Lines changed: 33 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -163,6 +163,39 @@ See [CI/CD Guide](CI_CD_GUIDE.md) for detailed configuration and changelog secti
163
163
164
164
---
165
165
166
+
## Mermaid CLI Hard Prerequisite (Feature 130)
167
+
168
+
Feature 130 upgraded `@mermaid-js/mermaid-cli` (`mmdc`) from an optional nice-to-have to a **hard prerequisite** for `/tachi.security-report` when the scanned project contains an `attack-trees/` directory with Critical/High findings. Prior to Feature 130, absence of `mmdc` produced a broken PDF with raw Mermaid source dumped verbatim into the Attack Path Pages section. The pipeline now fails loud and fast with a preflight gate instead.
169
+
170
+
### Enforcement Points (defense-in-depth)
171
+
172
+
| Enforcement Point | Location | When It Fires |
173
+
|-------------------|----------|---------------|
174
+
| Shell preflight gate |`.claude/commands/tachi.security-report.md` Step 1 | Command-level check before Python is invoked |
175
+
| Python preflight gate |`scripts/extract-report-data.py::render_mermaid_to_png()` via `shutil.which("mmdc")`| Script-level check before any rendering is attempted |
176
+
177
+
Both gates are **conditionally active** — they fire only when `attack-trees/` contains Critical/High findings. Projects without attack trees are unaffected, preserving backward compatibility for the majority of threat-modeling workflows.
178
+
179
+
### New CI Workflow: `tachi-mmdc-preflight.yml`
180
+
181
+
A new GitHub Actions workflow at `.github/workflows/tachi-mmdc-preflight.yml` runs on `ubuntu-latest` (which ships without `mmdc` preinstalled — confirmed by plan.md spike S3) and asserts the pipeline aborts non-zero with three canonical tokens in stderr. This is the fresh-install acceptance test for Feature 130. The workflow includes a **team-lead T4 enforcement assertion** that fails the CI job if `mmdc` is unexpectedly present on `PATH` — this mitigates the risk that a future GitHub Actions runner image change or a transitive install via a Typst action ships with `mmdc` preinstalled and silently validates the happy path when we intended to validate the loud-failure path.
182
+
183
+
See [CI/CD Guide — tachi mmdc Preflight Gate](CI_CD_GUIDE.md#tachi-mmdc-preflight-gate-feature-130) for trigger paths, step-by-step breakdown, and the T4 rationale.
184
+
185
+
### Developer Setup Signal
186
+
187
+
`scripts/install.sh` emits a courtesy warning at setup time if `mmdc` is absent from `PATH` (`command -v mmdc`). This is a best-effort early signal, not the enforcement point — the per-command preflight gates remain the authoritative check. See `README.md``## Prerequisites` section for per-OS install commands (`npm install -g @mermaid-js/mermaid-cli` on macOS/Linux/WSL).
188
+
189
+
### Related Architecture Decision
190
+
191
+
[ADR-022 — Mermaid CLI Hard Prerequisite](../architecture/02_ADRs/ADR-022-mmdc-hard-prerequisite.md) establishes the new rule: **pipelines are fail-loud when a required CLI is absent, gated on input detection**. ADR-022 is the first ADR in tachi governing CLI-prerequisite posture and cross-references ADR-014 (optional external APIs) and ADR-021 (determinism).
192
+
193
+
### Infrastructure Impact
194
+
195
+
No new environment variables, no Docker services, no staging/production deployment changes. Prerequisites live on developer machines and CI runners only. Developer-facing docs (`README.md`, `docs/devops/01_Local/README.md`) and CI workflow docs (`docs/devops/CI_CD_GUIDE.md`) are the touchpoints.
196
+
197
+
---
198
+
166
199
## Deployment Policy (MANDATORY)
167
200
168
201
**ALL deployments MUST go through the devops agent.**
0 commit comments