Architecture case study · open-source project

From Architectural Decisions to Continuous Verification

An ADR can explain why Domain must not depend on Infrastructure. The next challenge is detecting, with verifiable evidence, when the implementation stops respecting that decision.

This is a reproducible repository scenario, not a claim of production adoption or comprehensive architecture certification.

Problem

Documentation preserves reasoning; by itself, it does not preserve a dependency boundary.

As software evolves, a convenient project reference can reverse an intended boundary. The ADR remains accepted and readable while the actual code graph tells a different story.

The consequences emerge later: domain rules learn about persistence, tests require infrastructure, and technical change crosses layers that were meant to evolve separately. The missing link is feedback between the decision and inspectable implementation evidence.

Constraints and quality attributes

Verification must be useful without claiming more than its evidence supports.

01

Traceability

Each rule points to an ADR by stable identity and relative path, preserving the origin of the constraint.

02

Determinism

The same input should produce stable ordering, fingerprints, outcomes, and reports.

03

Safe uncertainty

Missing, dynamic, or ambiguous evidence yields unknown, never implicit approval.

04

Compatibility

Drift, impact, and structural validation have independent contracts without mutating published formats.

AuditabilitySecurityReproducibilityOperabilityEvolvabilityCompatibility
Flow from an accepted ADR to a fitness rule, bounded evidence, four evaluation outcomes, and an advisory or enforce policy.
Policy is applied after evaluation; the four outcomes are not collapsed into a boolean.

Architecture decisions

Four responsibilities answer four different questions.

Structural ADR validation

adr-guard check validates metadata, sections, references, lifecycle, and other document contracts. A valid ADR does not prove that code conforms to it.

CLI reference ↗

Change impact analysis

adr-guard impact correlates changed files with explicit local mappings. It identifies decisions that may need review; it does not issue a compliance verdict.

ADR 0006 ↗

Drift verification

adr-guard drift evaluates opt-in rules associated with effective ADRs. Version 1 proves only exact static project references and consumes reported external tests.

ADR 0007 ↗

Shared internal Core

AdrGuard.Core owns parsing, validation, Git inventory, impact, and drift for the CLI and MCP host. It is not a standalone public package; UI and transport remain at executable boundaries.

ADR 0008 ↗

The outcome vocabulary preserves uncertainty

pass

Complete deterministic evidence proves the declared condition.

violation

Complete deterministic evidence proves the condition false.

unknown

Evidence is insufficient, missing, dynamic, conditional, invalid, or contradictory.

not-applicable

The referenced ADR is not effective under the configured lifecycle policy.

Components and contracts

One shared rule engine, different surfaces.

  1. CLICommands, exit codes, text, JSON/SARIF, and explicit writes.
  2. AdrGuard.CoreInternal deterministic rules and bounded inputs.
  3. GitHub ActionRuns the CLI and publishes reports under its Action contract.
  4. VS CodeExploration, editing, diagnostics, and CLI process execution.
  5. MCP ServerSeven read-only tools, stdio transport, and workspace boundary.
  6. Human ownerAccepts ADRs, selects rules, and reviews baseline changes.

MCP does not approve decisions; AI review is optional and advisory. The existing composite Action did not implicitly gain drift mode: the guide installs the tool and invokes drift in an explicit step.

Reproducible implementation

Domain has no Infrastructure reference—until a change introduces drift.

1. Record the decision

The accepted ADR states: “The Domain project must not reference the Infrastructure project.” Its consequences acknowledge that conditional or dynamically expanded references require human review.

2. Configure the rule

{
  "schemaVersion": "1.0",
  "rules": [{
    "id": "domain-no-infrastructure",
    "decision": {
      "stableId": "ADR-1",
      "path": "docs/adr/0001-protect-domain-boundary.md"
    },
    "scope": { "projects": [
      "src/Domain/Domain.csproj",
      "src/Infrastructure/Infrastructure.csproj"
    ]},
    "check": {
      "type": "forbid-project-reference",
      "from": "src/Domain/Domain.csproj",
      "to": "src/Infrastructure/Infrastructure.csproj"
    },
    "evidence": { "source": "dotnet-project-reference" },
    "failure": { "severity": "error", "onInconclusive": "unknown" },
    "gate": true
  }]
}

3. Verify the initially conforming implementation

adr-guard drift . --rules .adrguard-fitness.json --format json
# Fixture's documented outcome: pass

The committed Domain.csproj contains only its SDK and target framework. The complete static reference set does not contain the forbidden dependency.

4. Introduce the forbidden reference

<ItemGroup>
  <ProjectReference Include="../Infrastructure/Infrastructure.csproj" />
</ItemGroup>

After replacing the project with the violation fixture, the repeated command reports the exact reference and line as a violation.

5. Apply policy without erasing nuance

ExecutionDocumented behaviorWhat it does not mean
--policy advisoryThe default; reports the violation without using it as an enforcement block.It does not turn the violation into success.
--policy enforceA new, gate: true, locally verified deterministic violation exits 4.It does not block every external failure or every violation recorded in a baseline.
unknown outcomeExits 3 under both policies and cannot masquerade as a green check.It is not converted into a violation.
External evidencePreserves provenance as a reported observation.It is not attestation and never authorizes automatic blocking.

6. Fix and verify again

adr-guard drift . --rules .adrguard-fitness.json \
  --baseline drift-baseline.json
# Previous violation: resolved
# Current rule: pass

A baseline is created or replaced only by an explicit command and should be reviewed like code. Removing the forbidden reference restores pass; compared with a snapshot containing the finding, its history becomes resolved.

Alternatives and trade-offs

The design favors narrow proof over broad inference.

Manual review

Essential for interpreting decisions and exceptions, but repetitive drift can escape attention across pull requests.

Use when: a rule has no deterministic representation or is context-sensitive.

Dedicated architecture tests

They can analyze code more deeply but require stack-specific implementation. ADR Guard may consume results only as reported external evidence.

Use when: invariants exceed the static v1 contract.

Evaluate MSBuild

It would resolve more conditional references but execute a complex, potentially untrusted surface. Version 1 deliberately does not invoke MSBuild.

Choice: conditions and properties remain unknown.

Automatic baselines

They reduce immediate friction but can normalize regression. Creation and replacement remain explicit owner actions.

Choice: auditable history over silent acceptance.

Limitations and risks

The report verifies a declared rule, not the whole architecture.

Version 1 inspects only exact static ProjectReference relationships in explicitly scoped .csproj files. Imports, wildcards, properties, conditions, missing files, unsafe XML, and exceeded bounds are inconclusive. It performs no network discovery or arbitrary execution.

A poorly stated rule can be deterministic yet misrepresent its ADR. Partial coverage, legitimate exceptions, and lifecycle changes still need human governance. pass means that one condition was proved under one contract—not that the solution received broad certification.

Lessons and evolution

Making decisions executable starts by making boundaries explicit.

Separating check, impact, and drift prevents “valid document,” “related change,” and “broken constraint” from becoming synonyms. Four outcome states keep missing evidence from becoming compliance.

Extracting Core reduces divergence between CLI and MCP without creating a public API. New versioned checks can evolve the system if they preserve provenance, uncertainty, compatibility, and human authority over decisions and baselines.