guides/installation:prerequisites guides/advanced:prerequisites
Architecture Decisions
This chapter records the most important architectural decisions using the ADR format by Michael Nygard.
ADR-001: File-System as Single Source of Truth
Status: Accepted (2025-09-18)
Context: The PRD requires that the system integrates with existing Git workflows, that files remain human-readable, and that there are no database dependencies. We need a simple, robust way to store the documentation content that honors these constraints.
Decision:
The file system will be treated as the single source of truth. The server will not have its own persistent state. All content and structure information is derived directly from the .adoc and .md files within the project directory.
Consequences:
-
Simplifies the architecture immensely. No database schema migrations or data synchronization logic needed.
-
Inherently compatible with Git and other version control systems.
-
Developers can still use their favorite text editors.
-
Queries that are not based on the document’s natural hierarchy may be inefficient to answer.
-
The system’s performance is tied to file system performance.
Pugh Matrix: Storage Strategy
| Criterion | File System (Baseline) | SQLite | Key-Value Store |
|---|---|---|---|
Git Integration |
0 |
- |
- |
Human-Readable Files |
0 |
- |
- |
No Database Dependency |
0 |
- |
- |
Query Flexibility |
0 |
+ |
+ |
Implementation Simplicity |
0 |
- |
- |
Total |
0 |
-3 |
-2 |
Legend: + better than baseline, 0 same as baseline, - worse than baseline
ADR-002: In-Memory Index for Performance
Status: Accepted (2025-09-18)
Context: The quality goal PERF-1 requires API calls to respond in under 2 seconds. Reading and parsing text files from disk on every request would be too slow for large projects, as identified in the runtime analysis.
Decision: On startup, the server will perform a one-time scan of the entire project directory. It will parse all documentation files and build an "In-Memory Structure Index". This index will hold metadata about each document, including section names, hierarchical paths, and the start/end line numbers for each section in its source file. Read requests will consult this index to find the exact byte range to read from a file.
Consequences:
-
Read operations (
get_section) are extremely fast, as they become simple dictionary lookups followed by a targeted file read. -
Enables efficient implementation of structure-aware APIs like
get_structure. -
Increased memory consumption, proportional to the size of the documentation project.
-
Slower server startup time due to the initial indexing phase.
-
A mechanism to detect external file changes (file watching) is needed to keep the index from becoming stale.
Pugh Matrix: Indexing Strategy
| Criterion | In-Memory Index (Baseline) | No Index | Persistent Disk Index |
|---|---|---|---|
Read Performance |
0 |
- - |
0 |
Startup Time |
0 |
+ |
+ |
Memory Efficiency |
0 |
+ |
+ |
Implementation Simplicity |
0 |
+ |
- |
Stateless Design |
0 |
+ |
- |
Cache Invalidation |
0 |
+ |
- |
Total |
0 |
+2 |
-2 |
Note: Despite scoring lower, In-Memory Index was chosen because read performance is the critical quality goal (PERF-1). The "No Index" approach would violate performance requirements.
ADR-003: Technology Stack (Python/FastAPI)
Status: Accepted (2025-09-18)
Context: A programming language and web framework are needed to build the MCP API Server. The choice must align with the need for rapid development, strong text-processing capabilities, and high performance for an I/O-bound application.
Decision: The backend will be implemented in Python. The FastAPI framework will be used to build the web server and API endpoints.
Consequences:
-
Python has an exceptional ecosystem for text processing and data manipulation.
-
FastAPI provides high performance for I/O-bound tasks, data validation, and automatic OpenAPI/Swagger documentation, which helps achieve USAB-1 and USAB-2.
-
The large talent pool for Python simplifies maintenance.
-
Python’s GIL can be a limitation for CPU-bound tasks, but this application is primarily I/O-bound (reading files, network requests).
Pugh Matrix: Technology Stack
| Criterion | Python/FastAPI (Baseline) | Node.js/Express | Go/Gin | Java/Spring |
|---|---|---|---|---|
Text Processing |
0 |
0 |
- |
0 |
Development Speed |
0 |
0 |
- |
- |
I/O Performance |
0 |
0 |
+ |
0 |
Auto API Documentation |
0 |
- |
- |
0 |
Ecosystem Maturity |
0 |
0 |
0 |
+ |
Memory Footprint |
0 |
0 |
+ |
- |
Talent Pool |
0 |
0 |
- |
+ |
Total |
0 |
-1 |
-2 |
0 |
Python/FastAPI chosen for its balanced strengths in text processing, rapid development, and automatic API documentation generation.
ADR-004: Atomic Writes via Temporary Files
Status: Accepted (2025-09-18)
Context:
The quality goal REL-1 (Atomic Writes) is critical to prevent file corruption during update operations. A failure (e.g., disk full, application crash) during a file write could leave a document in an unrecoverable, partially-written state.
Decision:
The File System Handler component will implement atomic writes using a backup-and-replace strategy:
-
Create a backup of the original file (e.g.,
doc.adoc→doc.adoc.bak). -
Write all intended changes to a new temporary file (e.g.,
doc.adoc.tmp). -
If the write is successful, atomically rename/move the temporary file to replace the original file.
-
Delete the backup file.
-
If any step fails, restore the original file from the backup and delete the temporary file.
Consequences:
-
Guarantees that the primary file is never in a corrupted state.
-
Relatively simple to implement and understand.
-
Slightly higher I/O overhead for each write operation (copy, write, move). This is an acceptable trade-off for the gain in reliability.
Pugh Matrix: Write Strategy
| Criterion | Temp File + Backup (Baseline) | Journaling | In-Place with Locking |
|---|---|---|---|
Crash Safety |
0 |
+ |
- |
Implementation Simplicity |
0 |
- - |
+ |
I/O Overhead |
0 |
- |
+ |
Power Loss Protection |
0 |
+ |
- |
Debugging/Recovery |
0 |
- |
0 |
Total |
0 |
-2 |
0 |
Temp File + Backup chosen for its balance of reliability and implementation simplicity. In-Place with Locking was rejected due to crash/power loss vulnerability.
ADR-005: Custom Parser for Include Resolution
Status: Accepted (2025-09-18)
Context:
A core feature is the ability to map a hierarchical path (e.g., chapter-1.section-2) to a precise location in a source file. This is complicated by AsciiDoc’s include::[] directive, as content from multiple files is logically part of one document. Existing parsers often flatten the document, losing this critical source-map information.
Decision: A custom document parser will be developed. This parser will be responsible for:
-
Parsing the AsciiDoc/Markdown syntax.
-
Recognizing and recursively resolving
include::[]directives. -
Building an Abstract Syntax Tree (AST) that retains the original file path and line numbers for every single element of the document.
Consequences:
-
Provides full control over the parsing process, ensuring the crucial source-map information is preserved.
-
Allows for tailored error handling of malformed documents or circular includes.
-
Significant development and maintenance effort compared to using an off-the-shelf library. This is the most complex component of the system.
Pugh Matrix: Parsing Strategy
| Criterion | Custom Parser (Baseline) | Existing Library (e.g., asciidoctor.py) |
|---|---|---|
Source-Map Preservation |
0 |
- - |
Include Resolution Control |
0 |
- |
Circular Include Detection |
0 |
- |
Development Effort |
0 |
+ |
Maintenance Effort |
0 |
+ |
Community Support |
0 |
+ |
Total |
0 |
0 |
Despite equal scoring, Custom Parser was chosen because source-map preservation is a hard requirement. Existing libraries fundamentally cannot provide this capability, making them unsuitable regardless of other benefits.
ADR-006: uv for Python Package Management
Status: Accepted (2026-01-20)
Context: Python projects require dependency management for reproducible builds. Traditional tools like pip, pipenv, and poetry have limitations in speed, reliability, or lock file handling. The project needs fast dependency resolution for efficient CI/CD and a deterministic lock file for reproducible deployments.
Decision:
Use uv (https://github.com/astral-sh/uv) as the Python package manager and virtual environment tool. All dependencies are defined in pyproject.toml and locked in uv.lock.
Consequences:
-
10-100x faster dependency resolution compared to pip/poetry.
-
Deterministic
uv.lockensures identical environments across development, CI, and production. -
Integrated virtual environment management (
uv synccreates venv automatically). -
Compatible with standard
pyproject.tomlformat. -
Relatively new tool (2024), but rapidly maturing with strong community adoption.
-
Requires uv installation on developer machines and CI environments.
Pugh Matrix: Package Manager
| Criterion | uv (Baseline) | pip + venv | Poetry | Pipenv |
|---|---|---|---|---|
Resolution Speed |
0 |
- - |
- |
- |
Lock File Quality |
0 |
- - |
0 |
0 |
Reproducibility |
0 |
- |
0 |
0 |
pyproject.toml Support |
0 |
- |
0 |
- |
Ecosystem Maturity |
0 |
+ |
+ |
0 |
CI/CD Integration |
0 |
0 |
0 |
- |
Total |
0 |
-5 |
0 |
-2 |
uv chosen for its superior speed and deterministic builds. Poetry is a viable alternative but slower.
ADR-007: dataclasses for Data Models
Status: Accepted (2026-01-20)
Context:
The core data models (SourceLocation, Section, Element, CrossReference, Document) need to be defined for the parser and API layers. These models must be serializable to JSON for API responses and should be type-safe. Two main options exist in the Python ecosystem: Python’s built-in dataclasses module and the third-party pydantic library.
Decision:
Use Python’s built-in dataclasses for all core data models. JSON serialization will be achieved using dataclasses.asdict() combined with custom serialization for Path objects.
Consequences:
-
Zero additional dependencies (dataclasses is part of the standard library since Python 3.7).
-
Consistent with the specification documents which use dataclasses in all examples.
-
Simpler, more lightweight implementation.
-
Type hints are supported and enforced by IDE/mypy.
-
Manual validation required (no automatic validation like Pydantic provides).
-
Custom serialization needed for non-JSON-native types (Path, datetime).
-
If advanced validation becomes necessary, migration to Pydantic is straightforward since both use similar patterns.
Pugh Matrix: Data Model Implementation
| Criterion | dataclasses (Baseline) | Pydantic | attrs |
|---|---|---|---|
Standard Library |
0 |
- |
- |
Spec Conformity |
0 |
- |
- |
JSON Serialization |
0 |
+ |
0 |
Automatic Validation |
0 |
+ |
0 |
Learning Curve |
0 |
- |
- |
Runtime Overhead |
0 |
- |
0 |
FastAPI Integration |
0 |
+ |
- |
Total |
0 |
0 |
-2 |
dataclasses chosen for simplicity, zero dependencies, and spec conformity. Pydantic scored equally but adds complexity not needed for the current scope. Migration to Pydantic remains possible if advanced validation becomes necessary.
ADR-008: Cross-Document Path Uniqueness Strategy
Status: Accepted (2026-01-22)
Context: The current path generation creates section paths based solely on heading titles (slugified). This works well within a single document, but when multiple documents are indexed together, sections with identical titles receive identical paths. This causes:
-
Only the first section with a given path is indexed; subsequent ones are rejected with a warning
-
Search results are incomplete (Issue #131)
-
Sections become inaccessible via API (Issue #130)
-
Root documents all have empty path
""(Issue #129)
Example: Both guides/installation.adoc and guides/advanced.adoc have a section == Prerequisites, resulting in path collision at prerequisites.
Issue #123 (now fixed) addressed within-document duplicates by appending -2, -3 suffixes. This ADR addresses the cross-document case.
Note: Backwards compatibility is not a requirement for this decision.
Decision: We will implement Option A: File Prefix in Path as the solution.
Section paths will include the relative file path (without extension) as a prefix, separated by a colon:
For root sections (document titles), the path will be the file path alone:
guides/installation guides/advanced
Consequences:
Positive:
-
All sections are guaranteed uniquely addressable (file paths are always unique)
-
Path clearly indicates file location - useful for navigation
-
Simple mental model:
file:sectionformat -
Search returns all matching sections
-
LLM-friendly: paths are self-documenting
-
Single identifier (no composite keys needed)
Negative:
-
All existing paths change (acceptable since backwards compatibility not required)
-
Paths become longer
-
Clients need to handle the new format
Pugh Matrix: Path Uniqueness Strategy
Note: Backwards compatibility removed as criterion per stakeholder decision.
| Criterion | Current (Baseline) | A: File Prefix | B: Doc Title Prefix | C: Global Disambig | D: File Filter |
|---|---|---|---|---|---|
Unique Addressability |
0 |
+ |
0 1 |
+ |
+ |
Path Readability |
0 |
0 |
+ |
- |
0 |
API Simplicity |
0 |
0 |
0 |
0 |
- |
Implementation Complexity |
0 |
0 |
- 2 |
0 |
- |
Search Correctness |
0 |
+ |
+ |
+ |
+ |
LLM Usability |
0 |
+ |
+ |
- |
0 |
Total |
0 |
+3 |
+2 |
0 |
0 |
Legend: + better than baseline, 0 same as baseline, - worse than baseline
1 Document titles may not be unique, causing potential collisions 2 Requires handling of duplicate document titles
Option Details
Option A: File Prefix in Path (Selected)
guides/installation:prerequisites guides/advanced:prerequisites
Paths include relative file path (without extension) as prefix, separated by colon. Guarantees uniqueness since file paths are always unique. Clear indication of file location. Best score in Pugh Matrix.
Option B: Document Title Prefix
installation-guide.prerequisites advanced-guide.prerequisites
Uses parent document title (slugified) as prefix. More readable but depends on unique document titles - if two files have the same title, collisions remain. Requires additional disambiguation logic.
Option C: Global Auto-Disambiguation
prerequisites prerequisites-2
Extends Issue #123 fix globally across all documents. Simple but arbitrary ordering based on parse order. Path meaning unclear without context. Poor LLM usability.
Option D: File-Scoped Paths with Filter
Path: prerequisites File: guides/installation.adoc
Keep current path format, add file parameter to APIs. Requires composite key (file + path) for uniqueness. More complex API. No longer recommended since backwards compatibility is not required.
Path Format Specification
<relative-file-path-without-extension>:<section-path>
Examples:
guides/installation:prerequisites
guides/installation:prerequisites.python-version
api/reference:endpoints.get-section
index (root section of index.adoc)
guides/advanced (root section)
Implementation Notes
-
Path generation in parsers:
-
Compute relative path from docs-root to file
-
Remove file extension
-
Prepend to existing section path with
:separator -
Root sections (level 0) use file path only (no trailing
:)
-
-
StructureIndex changes:
-
Paths are now guaranteed unique
-
Remove duplicate detection warnings for paths
-
Update path-based lookups
-
-
API changes:
-
All endpoints accept new path format
-
Response
location.filebecomes redundant but kept for clarity
-
ADR-009: No Built-In LLM-Based Ask Feature
Status: Accepted (2026-02-07)
Context:
Issue #186 proposed adding an experimental dacli ask "question" command that uses an LLM to answer questions about the documentation. The idea was to iterate through all documentation files, pass each file’s content together with the question and accumulated findings to an LLM, and consolidate the results into a final answer.
Several approaches were evaluated during implementation:
-
Section-based iteration: Iterate through all sections (~460 in a typical project), one LLM call per section. Result: ~460 LLM calls, far too slow.
-
File-based iteration: Iterate through all files (~35-50), one LLM call per file. Result: ~50 LLM calls. With Claude Code CLI (~12s per call) this took ~10 minutes. With the Anthropic API (~4s per call) still ~3-4 minutes.
-
Two-call approach: Send all section titles to the LLM to select relevant ones, then send only those sections for answering. Faster, but essentially replicates what the calling LLM already does.
The fundamental insight: dacli is designed to be used by LLMs (both via MCP and CLI). The calling LLM can already:
-
Call
get_structure()/dacli structureto see all section titles -
Call
get_section()/dacli section <path>to read relevant sections -
Answer the question itself with full context
The ask command is therefore redundant — it implements a slower, less capable version of what the calling LLM already does natively.
Decision: We will not implement a built-in LLM-based ask feature. Instead, we rely on the calling LLM to use dacli’s existing navigation and content access tools to answer questions.
A future RAG-based approach (Retrieval-Augmented Generation) could be considered as a separate feature. RAG would use vector embeddings for fast similarity search and only require a single LLM call for the final answer. This would be significantly faster but would not cover the entire documentation (only the top-k matching chunks).
Consequences:
Positive:
-
No additional LLM dependency in dacli (no API keys, no subprocess calls)
-
No performance bottleneck from iterative LLM calls
-
Simpler codebase — fewer moving parts
-
The calling LLM has better context and can make more informed decisions about relevance
Negative:
-
CLI users without LLM integration cannot ask natural language questions directly
-
No single-command convenience for documentation Q&A
Pugh Matrix: Documentation Q&A Strategy
| Criterion | No Feature (Baseline) | A: Iterative LLM | B: Two-Call LLM | C: RAG |
|---|---|---|---|---|
Response Time |
0 |
— |
0 |
+ |
Answer Quality |
0 |
0 |
0 |
- |
Coverage (% of docs checked) |
0 |
+ |
0 |
- |
Implementation Complexity |
0 |
— |
- |
- |
External Dependencies |
0 |
— |
— |
- |
Redundancy with Calling LLM |
0 |
— |
- |
0 |
Total |
0 |
-7 |
-3 |
-2 |
Legend: + better than baseline, 0 same, - worse, — much worse
Option Details
No Feature (Selected)
Rely on the calling LLM to use existing dacli tools (get_structure, get_section, search) to answer questions. Zero additional complexity. Works today.
Option A: Iterative LLM (Rejected) Pass every file/section to an LLM one by one. Complete coverage but prohibitively slow (3-10 minutes). Requires LLM provider configuration. Essentially duplicates the calling LLM’s job.
Option B: Two-Call LLM (Rejected) First call: LLM selects relevant sections from title list. Second call: LLM answers from selected content. Faster than A but still redundant with calling LLM behavior.
Option C: RAG (Future Consideration) Build vector index of documentation chunks, retrieve top-k similar chunks for a question, answer in one LLM call. Fast (~2-3 seconds) but incomplete coverage. Could be a valuable future addition for human CLI users.
ADR-010: No Web UI — dacli is CLI and MCP Only
Status: Accepted (2026-02-07)
Context: The original architecture documents (Quality Requirements, Concepts) assumed a Web UI component for dacli — specifically a document structure visualization (#12) and a real-time diff display (#13). Quality scenario USAB-3 described a "web UI" showing red/green diffs after modifications.
However, dacli has evolved into a pure CLI tool and MCP server designed for LLM integration. The primary consumers are:
-
LLMs using dacli via MCP (Model Context Protocol)
-
LLMs using dacli via CLI/Bash tools
-
Developers using the CLI directly
A Web UI would be a fundamentally different product — it requires frontend development (HTML/CSS/JS), serving static files, browser compatibility, and ongoing maintenance. This is outside dacli’s core mission of providing structured, programmatic access to documentation.
Tools like docToolchain already provide web-based documentation rendering. Adding a Web UI to dacli would duplicate that effort without clear benefit.
Decision: dacli will not include a Web UI. The project scope is limited to CLI and MCP server interfaces. References to a Web UI in existing architecture documents will be removed.
Consequences:
Positive:
-
Clear, focused scope — CLI and MCP only
-
No frontend dependencies or maintenance burden
-
Simpler deployment (no static file serving, no browser compatibility)
-
Architecture documents accurately reflect the actual system
Negative:
-
No visual diff display for documentation changes (LLMs and developers use the API/CLI output instead)
-
No interactive tree visualization (LLMs use
get_structureprogrammatically)
ADR-011: Risk Classification - dacli CLI (Tier 2)
Status: Accepted (2026-02-11)
Deciders: Development Team + Claude Code
Context:
The dacli CLI module requires risk classification according to the Risk Radar framework to determine appropriate security and quality assurance measures. The assessment evaluates five dimensions:
| Dimension | Score | Level | Evidence |
|---|---|---|---|
Code Type |
2 |
Business Logic |
Click commands, service layer orchestration ( |
Language |
2 |
Dynamically typed |
Python 3.12+ — 100% |
Deployment |
1 |
Internal tool |
Command-line tool for documentation teams |
Data Sensitivity |
1 |
Internal business data |
Operates on internal documentation |
Blast Radius |
2 |
Data loss (recoverable) |
Could corrupt docs, recoverable from git |
Decision:
Classify dacli CLI as Tier 2 — Extended Assurance (determined by max(Code Type=2, Language=2, Blast Radius=2)).
This tier requires:
-
Tier 1 measures: Linter & formatter, pre-commit hooks, dependency vulnerability scanning, CI with automated tests
-
Tier 2 measures: SAST (CodeQL), property-based testing (Hypothesis), code quality gates (SonarCloud), AI-assisted code review, PR review policy with sampling
Consequences:
Positive:
-
Clear security baseline established for the project
-
Comprehensive testing strategy (713 automated tests + property-based tests with 1,100+ generated cases)
-
Automated gates prevent common vulnerabilities (dependency CVEs, code quality issues)
-
SonarCloud integration provides continuous quality monitoring
-
PR review policy balances thoroughness with development velocity (20-30% sampling)
Negative:
-
Additional CI pipeline duration (~2-3 minutes for SAST and quality gate checks)
-
Developer onboarding overhead (pre-commit hooks, review policy understanding)
-
Maintenance burden for Tier 2 tooling (CodeQL queries, SonarCloud configuration)
Pugh Matrix: Risk Tier Selection
| Criterion | Tier 2 (Baseline) | Tier 1 (Lower) | Tier 3 (Higher) |
|---|---|---|---|
Code Complexity Coverage |
0 |
- |
+ |
Language Risk Mitigation |
0 |
- |
+ |
Data Loss Prevention |
0 |
- |
+ |
Development Velocity |
0 |
+ |
- |
CI/CD Pipeline Complexity |
0 |
+ |
- |
Total |
0 |
-1 |
0 |
Legend: + better than baseline, 0 same as baseline, - worse than baseline
Tier 1 rejected: Insufficient coverage for business logic complexity and blast radius (data loss risk). Missing SAST and property-based testing would leave gaps in quality assurance.
Tier 3 rejected: Not cost-justified. The module is an internal tool without public-facing deployment or sensitive PII. Branch protection and fuzzing would be overkill for the current risk profile.
Tier 2 selected: Balanced approach matching the actual risk profile (business logic + dynamic typing + recoverable data loss). Provides strong automated gates without excessive overhead.
ADR-012: Risk Classification - dacli-mcp (Tier 2)
Status: Accepted (2026-02-11)
Deciders: Development Team + Claude Code
Context:
The dacli-mcp MCP server module requires risk classification according to the Risk Radar framework to determine appropriate security and quality assurance measures. The assessment evaluates five dimensions:
| Dimension | Score | Level | Evidence |
|---|---|---|---|
Code Type |
2 |
Business Logic |
MCP tools, service layer ( |
Language |
2 |
Dynamically typed |
Python 3.12+ — 100% |
Deployment |
1 |
Internal tool |
MCP server for LLM integration in internal workflows |
Data Sensitivity |
1 |
Internal business data |
Operates on internal documentation |
Blast Radius |
2 |
Data loss (recoverable) |
Could corrupt docs, recoverable from git |
Note: Initial consideration was given to Code Type score 3 (API/Database Queries) since the module exposes API endpoints (MCP tools). However, user confirmed score 2 as these are internal service APIs without public exposure or direct database access.
Decision:
Classify dacli-mcp as Tier 2 — Extended Assurance (determined by max(Code Type=2, Language=2, Blast Radius=2)).
This tier requires the same mitigation measures as dacli CLI (both modules share the same codebase):
-
Tier 1 measures: Linter & formatter, pre-commit hooks, dependency vulnerability scanning, CI with automated tests
-
Tier 2 measures: SAST (CodeQL), property-based testing (Hypothesis), code quality gates (SonarCloud), AI-assisted code review, PR review policy with sampling
Consequences:
Positive:
-
Consistent risk management across both module entry points (CLI and MCP server)
-
Shared codebase benefits from unified quality gates and testing strategy
-
MCP tools benefit from the same 713 automated tests + property-based tests
-
FastMCP framework integration validated by comprehensive test suite
Negative:
-
MCP-specific edge cases may need additional test coverage beyond shared tests
-
Tool invocation patterns (JSON-RPC) differ from CLI patterns, requiring careful validation
Pugh Matrix: Risk Tier Selection
| Criterion | Tier 2 (Baseline) | Tier 1 (Lower) | Tier 3 (Higher) |
|---|---|---|---|
API Exposure Risk |
0 |
- |
+ |
Language Risk Mitigation |
0 |
- |
+ |
Data Loss Prevention |
0 |
- |
+ |
Development Velocity |
0 |
+ |
- |
Tool Integration Complexity |
0 |
+ |
- |
Total |
0 |
-1 |
0 |
Legend: + better than baseline, 0 same as baseline, - worse than baseline
Tier 1 rejected: Insufficient for an API-like interface (MCP tools). Missing SAST and property-based testing would leave gaps in tool invocation validation and edge case coverage.
Tier 3 rejected: Not cost-justified. The MCP server is for internal LLM integration, not public-facing. The deployment context (internal tool) doesn’t warrant branch protection, fuzzing, or penetration testing.
Tier 2 selected: Appropriate for internal API-like interfaces with business logic. Provides SAST coverage for potential injection vulnerabilities and property-based tests for tool parameter validation without excessive overhead.
Shared codebase note: Both dacli CLI and dacli-mcp modules share the same source code (src/dacli/). Entry points differ (dacli.cli:cli vs dacli.main:main), but risk profile and protection measures are identical. All mitigations are applied repository-wide.
ADR-013: Security Mitigations - Tier 2 Implementation
Status: Accepted (2026-02-11)
Deciders: Development Team + Claude Code
Context:
Following the Tier 2 risk classification for both dacli CLI and dacli-mcp modules (see ADR-011 and ADR-012), the project requires implementation of comprehensive security and quality assurance measures. The Risk Radar framework mandates cumulative mitigations: all Tier 1 measures plus all Tier 2 measures.
Both modules share the same codebase (src/dacli/), so mitigations are applied repository-wide rather than per module.
Decision:
Implement all required Tier 1 and Tier 2 mitigation measures as repository-wide protections:
Tier 1 — Automated Gates:
-
Linter & Formatter: Ruff configured in
pyproject.tomlwith enforced rules (E, F, I, N, W, UP) and 100-character line length -
Pre-Commit Hooks: Configured via
.pre-commit-config.yaml(commit 68d6ae4) with Ruff checks -
Dependency Vulnerability Scanning:
pip-auditintegrated in CI pipeline (commit fee56b6) -
CI Build & Unit Tests: GitHub Actions workflow (
.github/workflows/test.yml) running 713 automated tests with coverage reporting
Tier 2 — Extended Assurance:
-
SAST (Static Application Security Testing): CodeQL workflow with
security-extendedquery suite (commit fead47e), runs on upstream repository only -
AI-Assisted Code Review: Claude Code review workflow (
.github/workflows/claude-code-review.yml) for automated PR analysis -
Property-Based Testing: Hypothesis framework (commit 87a965d) with 11 property-based tests generating 1,100+ test cases (
tests/test_property_based.py) -
Code Quality Gate: SonarCloud integration (commit fb4c8ad) via
.github/workflows/sonarcloud.ymlandsonar-project.properties -
PR Review Policy with Sampling: Risk-based review policy documented in
.github/PR_REVIEW_POLICY.md(commit efb868f):-
100% review for security-sensitive changes, breaking changes, architecture changes
-
20-30% sampling for bug fixes, refactoring, tests, documentation
-
Auto-merge eligible: non-security dependency updates, formatting fixes, PATCH version bumps
-
Security Fixes Applied:
-
cryptographyupgraded from 46.0.3 → 46.0.5 (CVE-2026-26007 mitigation) -
pipupgraded from 24.0 → 26.0.1 -
Commit: 7766e90
Consequences:
Positive:
-
100% mitigation coverage for both Tier 1 (4/4 measures) and Tier 2 (5/5 measures)
-
Zero known vulnerabilities (pip-audit clean)
-
Comprehensive test coverage: 713 unit/integration tests + 11 property-based tests (1,100+ generated cases)
-
Continuous quality monitoring: SonarCloud provides ongoing code quality metrics and technical debt tracking
-
Automated security scanning: CodeQL runs on every push to main, catching potential vulnerabilities before production
-
Efficient review process: Sampling policy (20-30%) balances thoroughness with development velocity
-
Developer experience: Pre-commit hooks catch issues locally before CI, reducing feedback loop time
Negative:
-
CI pipeline duration increase: ~2-3 minutes added for SAST (CodeQL) and quality gate (SonarCloud) checks
-
Developer onboarding overhead: New contributors must understand pre-commit hooks, review policy, and quality standards
-
Maintenance burden:
-
CodeQL query suite updates needed for new Python versions
-
SonarCloud project configuration requires manual setup and token management
-
Hypothesis tests may need strategy refinement as edge cases are discovered
-
-
External service dependencies:
-
SonarCloud outages block PRs (mitigated by making check non-blocking in fork workflow)
-
CodeQL only runs on upstream repository (not on fork PRs)
-
-
False positive handling: SAST tools may flag intentional patterns (e.g., dynamic code in MCP server), requiring suppression annotations
Pugh Matrix: Mitigation Strategy
| Criterion | Repository-wide (Baseline) | Module-specific | Tier 1 Only |
|---|---|---|---|
Implementation Simplicity |
0 |
- |
+ |
Coverage Completeness |
0 |
0 |
- |
Maintenance Burden |
0 |
- |
+ |
Risk Mitigation Effectiveness |
0 |
0 |
- |
Compliance with Tier 2 |
0 |
0 |
- |
Total |
0 |
-2 |
-1 |
Legend: + better than baseline, 0 same as baseline, - worse than baseline
Module-specific approach rejected: Both modules share the same codebase (src/dacli/). Applying mitigations per module would duplicate CI checks, complicate maintenance, and provide no additional risk reduction.
Tier 1 only rejected: Insufficient coverage for Tier 2 classification. Missing SAST, property-based testing, and quality gates would leave critical gaps in security and quality assurance.
Repository-wide Tier 1+2 selected: Simplest implementation, consistent protection across all entry points (CLI and MCP), compliant with Risk Radar tier requirements.
Alternative Mitigation Measures Considered
Static Type Checking (mypy):
-
Rejected for Tier 1: Python project without strict typing. Retrofitting type annotations to 64+ files would be high effort with moderate benefit. FastMCP framework uses dynamic features that complicate type checking.
-
Future consideration: May be added incrementally as codebase matures, but not required for current Tier 2 classification.
Fuzzing (AFL, cargo-fuzz):
-
Not required for Tier 2: Fuzzing is a Tier 3 measure. The project’s internal tool deployment context and recoverable data loss blast radius don’t justify the complexity and CI time cost of continuous fuzzing.
Branch Protection:
-
Not required for Tier 2: Branch protection (required status checks, mandatory reviews) is a Tier 3 measure. Current PR review policy with sampling (20-30%) provides adequate oversight for internal tool risk profile.
Implementation Timeline
All mitigations implemented between 2026-02-09 and 2026-02-11 as part of PR #279:
-
Pre-commit hooks: commit 68d6ae4
-
Dependency vulnerability scanning (pip-audit): commit fee56b6
-
Security fixes (cryptography, pip): commit 7766e90
-
CodeQL SAST workflow: commit fead47e
-
Property-based tests (Hypothesis): commit 87a965d
-
SonarCloud quality gate: commit fb4c8ad
-
PR review policy: commit efb868f
Verification: All 713 tests passing, CI green, pip-audit clean, CodeQL and SonarCloud integrated.
Module-Specific Notes
PR Review Policy - Differential Application:
While most mitigations are truly repository-wide, the PR review policy applies differentially based on change type (not module):
-
100% mandatory review:
-
Security-sensitive changes (auth, crypto, file system ops with user paths)
-
Breaking changes (public API, CLI interface, configuration format)
-
Architecture changes (new components, core parsers, data model)
-
Release preparation (MINOR/MAJOR version bumps)
-
-
20-30% sampling review:
-
Bug fixes (prioritize critical bugs)
-
Internal refactoring (prioritize complex changes)
-
Test additions (prioritize property-based/integration tests)
-
Documentation updates (prioritize user-facing docs)
-
-
Auto-merge eligible:
-
Dependency updates (PATCH, non-security, passing CI)
-
Formatting/linting fixes (no logic changes)
-
PATCH version bumps (small fixes, no API changes)
-
This differential approach ensures critical changes receive thorough review while maintaining development velocity for lower-risk changes.
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.