Adopt extended architecture governance¶
Use this evergreen guide when an existing ArchLinterNet policy is moving from a basic strict/audit gate to structured waivers, declared topology, visible contract-surface governance, architecture metrics, external evidence, Architecture Health, PR Markdown, and the Health badge.
The v0.7 to v0.8 transition is the first complete migration boundary for these capabilities, but the page is not a release record. Compatible later tool releases should continue to use this workflow rather than creating another version-named documentation route. For greenfield setup, start with the complete single-tool workflow.
1. Upgrade the tool before changing policy¶
Keep the currently reviewed policy and baseline unchanged, update the repository-local tool, and prove the old workflow still behaves as expected:
dotnet tool update ArchLinterNet.Cli
dotnet tool restore
dotnet arch-linter-net --version
dotnet arch-linter-net policy check --policy architecture/arch.yml
dotnet arch-linter-net --policy architecture/arch.yml --mode strict --ensure-built
Record the reviewed package version and use that exact version for both base and candidate evidence. Do not weaken a rule simply because the newer CLI exposes previously hidden incomplete or stale evidence.
2. Keep v1 compatibility until waiver migration is ready¶
Policy version: 1 preserves compatibility waiver defaults. You can therefore adopt the extended-governance capabilities without immediately converting every legacy manual ignore.
Use this period to inspect canonical waiver lifecycle and policy-inventory output. Under compatibility semantics, matcher-only ignores remain visible as metadata_incomplete debt and retain their prior pass/fail behavior. Migrate each still-legitimate manual ignore to a structured waiver with stable ID, exact target fingerprint, reason, owner, issue/remediation, introduced date, and expiry.
When all manual exceptions are ready for strict lifecycle behavior, move the root policy to:
version: 2
A version-2 policy can explicitly use analysis.waiver_lifecycle_profile: compatibility during a reviewed transition, but the target state is strict lifecycle governance. See Structured waivers.
3. Keep baseline finding debt separate¶
Your existing migration baseline remains the reviewed ledger of known normalized findings. Structured waiver debt is separate and should not be folded into the baseline just to preserve one generic debt count.
Both gate and health require an explicit baseline. If the repository has no reviewed baseline file, create an explicit workflow-local empty v3 baseline instead of interpreting an absent input as zero debt:
ARTIFACTS="$(pwd)/artifacts"
mkdir -p "$ARTIFACTS"
if [[ -f architecture/baseline.arch.yml ]]; then
CURRENT_BASELINE="$(pwd)/architecture/baseline.arch.yml"
dotnet arch-linter-net baseline verify \
--policy architecture/arch.yml \
--baseline "$CURRENT_BASELINE"
else
CURRENT_BASELINE="$ARTIFACTS/empty-baseline.arch.yml"
cat > "$CURRENT_BASELINE" <<'YAML'
version: 3
baseline: {}
metric_baselines: []
YAML
fi
dotnet arch-linter-net gate \
--policy architecture/arch.yml \
--baseline "$CURRENT_BASELINE" \
--mode all
The empty file is explicit zero-debt authority for the invocation, not a hidden mutation of repository policy. If identity changes require migration of a real baseline, use the explicit baseline lifecycle commands and review the resulting diff. CI must not regenerate accepted debt automatically.
4. Add topology in partial mode first¶
Do not claim exhaustive topology before the repository has been reviewed as a complete bounded subject universe.
Capture observations:
dotnet arch-linter-net topology capture \
--policy architecture/arch.yml \
--subject-kind assembly \
--ensure-built \
--format json \
--output artifacts/topology-capture.json
Hand-author the reviewed declaration, initially with mode: partial, then use:
dotnet arch-linter-net topology diff \
--policy architecture/arch.yml \
--mode strict \
--ensure-built \
--format json \
--output artifacts/topology-diff.json
dotnet arch-linter-net topology verify \
--policy architecture/arch.yml \
--mode strict \
--ensure-built \
--format json
Move to mode: exhaustive only after every required first-party subject is mapped exactly or explicitly reviewed out of scope. New unmapped or ambiguous required subjects then become incomplete/unassessable governance evidence rather than silently escaping the declaration.
5. Add visible contract-surface governance deliberately¶
Existing dependency rules do not need to be replaced. Add contract_surface_exposure where a published/protected CLR-visible surface must not disclose domain, persistence, transport, editor-only, or other forbidden types.
When you already use public_api_surface, reuse that reviewed membership as the source. Do not replace a type's existing semantic role with an API-only role just to make exposure rules work.
Use versioned_contract_surface_isolation when locally selected v1/v2 or runtime/implementation surfaces must stay isolated. It reuses the same recursive visible-signature evidence and does not create a second API snapshot or runtime versioning model.
Start in audit if you expect existing leakage, then promote the contract to strict after reviewing or fixing the findings. Runtime serialization, endpoint routing and arbitrary semantic data flow remain outside this static contract. See Contract-surface exposure and Versioned contract-surface isolation.
6. Measure before introducing budgets¶
Declare metrics and inspect them before adding limits:
dotnet arch-linter-net measure \
--policy architecture/arch.yml \
--format json
Then choose one of the delivered budget styles:
- absolute
minimum/maximum; baseline_mode: no_worse_than_baseline;baseline_mode: max_deltaplusmax_delta;- optional absolute
maximumas a hard cap on a relative budget.
Keep scalar metric baselines distinct from finding baseline debt and waiver debt. An incomplete measurement scope is unassessable, not a trustworthy low number.
7. Bind external SARIF only when freshness evidence is explicit¶
If the existing pipeline already runs analyzers, keep those producer steps. Replace repository-owned SARIF interpretation with ArchLinterNet's first-class binding once the producer can supply reliable repository/revision/scope identity.
dotnet arch-linter-net \
--policy architecture/arch.yml \
--external-evidence "id=static-analysis,path=evidence/static-analysis.sarif" \
--evidence-repository "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY" \
--evidence-revision "$GITHUB_SHA" \
--evidence-scope "ci"
A successful current-context zero-result artifact is valid evidence. A missing, failed, malformed, stale, wrong-revision, or wrong-scope required artifact is unassessable. Do not carry forward a script that treats filename, modification time, job name, or mere file presence as freshness proof.
The canonical trust receipt retains the exact consumed-byte SHA-256 together with logical evidence, tool/run, normalized local path and validated repository/revision/scope provenance. Later Health/report consumers do not infer freshness independently.
8. Add policy weakening and architecture change evidence¶
Start in the candidate checkout, keep the absolute ARTIFACTS directory created above, and point BASE_WORKTREE at a reviewed base worktree. Stage the exact local-manifest version into one workflow-owned tool path so entering the base worktree cannot select an older manifest:
BASE_WORKTREE="../architecture-base"
TOOL_PATH="$ARTIFACTS/tool"
ARCHLINTERNET_VERSION="$({
dotnet tool list --local ArchLinterNet.Cli |
awk 'tolower($1) == "archlinternet.cli" { print $2 }'
})"
test -n "$ARCHLINTERNET_VERSION"
dotnet tool install ArchLinterNet.Cli \
--tool-path "$TOOL_PATH" \
--version "$ARCHLINTERNET_VERSION"
export PATH="$TOOL_PATH:$PATH"
arch-linter-net --version
The install uses the repository's configured NuGet sources and credentials. It exposes the already reviewed manifest version through one absolute executable path; it does not choose a new version.
Produce base/current policy and architecture evidence with that one executable:
(
cd "$BASE_WORKTREE"
arch-linter-net policy context \
--policy architecture/arch.yml \
--format json > "$ARTIFACTS/policy-base.json"
baseline_args=()
if [[ -f architecture/baseline.arch.yml ]]; then
baseline_args=(--baseline architecture/baseline.arch.yml)
fi
arch-linter-net change snapshot \
--policy architecture/arch.yml \
--mode strict \
"${baseline_args[@]}" \
--ensure-built \
--output "$ARTIFACTS/architecture-base.json"
)
arch-linter-net policy context \
--policy architecture/arch.yml \
--format json > "$ARTIFACTS/policy-current.json"
baseline_args=()
if [[ -f architecture/baseline.arch.yml ]]; then
baseline_args=(--baseline architecture/baseline.arch.yml)
fi
arch-linter-net change snapshot \
--policy architecture/arch.yml \
--mode strict \
"${baseline_args[@]}" \
--ensure-built \
--output "$ARTIFACTS/architecture-current.json"
arch-linter-net policy weakening \
--base-context "$ARTIFACTS/policy-base.json" \
--current-context "$ARTIFACTS/policy-current.json"
arch-linter-net change report \
--base "$ARTIFACTS/architecture-base.json" \
--current "$ARTIFACTS/architecture-current.json" \
--execution-context pr-123 \
--format json \
--output "$ARTIFACTS/architecture-change.json"
Base and candidate snapshot baselines are selected independently because snapshots describe their own repository revisions. This is where a new/broadened waiver, relaxed exclusion, removed control, new finding debt, or resolved finding becomes explicit change evidence instead of being hidden inside a current-state pass/fail result.
9. Adopt Architecture Health¶
Once current validation, baseline, waiver, topology, metrics, and required external evidence are trustworthy, project Health from the same authority inputs. Include base/current policy contexts so weakening is represented, and repeat every required external-evidence binding because a new CLI process does not inherit evidence from an earlier validation command.
arch-linter-net health \
--policy architecture/arch.yml \
--baseline "$CURRENT_BASELINE" \
--base-context "$ARTIFACTS/policy-base.json" \
--current-context "$ARTIFACTS/policy-current.json" \
--mode strict \
--ensure-built \
--execution-context pr-123 \
--external-evidence "id=static-analysis,path=evidence/static-analysis.sarif" \
--evidence-repository "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY" \
--evidence-revision "$GITHUB_SHA" \
--evidence-scope "ci" \
--format json > "$ARTIFACTS/architecture-health.json"
Omit the external-evidence options only when the policy declares no such requirement. Interpret gate and health separately:
- pass + healthy: all required evidence is assessable, configured authorities pass, and reviewed finding debt, explicit waiver debt, new debt, weakening, and metric regression are absent;
- pass + debt: reviewed debt remains but the current gate passes;
- degrading: regression/change evidence exists, with the owning authority still deciding whether the independent gate blocks;
- fail + failing: a blocking current requirement fails;
- unassessable: required evidence cannot be trusted as complete/current.
A failing or unassessable gate exits 1 or 2 while still being able to produce a valid architecture-health/v1 document. Preserve and schema-check that document for reporting before a separate required gate blocks the pull request; do not convert the architecture decision into success.
See Architecture Health.
10. Replace repository-owned reporting logic¶
If the existing CI uses Python, JavaScript, shell or PowerShell to count rules, classify debt, render architecture sections, or decide badge color, retire that logic after moving to the first-class projections.
Use the JSON change artifact created in step 8 together with the Health artifact carrying the same pr-123 execution context and strict selected mode:
arch-linter-net report pr \
--health "$ARTIFACTS/architecture-health.json" \
--change "$ARTIFACTS/architecture-change.json" \
--max-details 20 \
--output "$ARTIFACTS/architecture-pr-report.md"
--max-details bounds each detailed evidence section while preserving canonical totals and explicit truncation.
Generate the Health badge payload separately:
arch-linter-net badge architecture-health \
--input "$ARTIFACTS/architecture-health.json" \
--output "$ARTIFACTS/architecture-health-badge.json"
CI may validate repository/PR/head/run/schema/size/hash transport metadata and publish these finished bytes. It should not reconstruct their semantics.
11. Keep PR authority and main responsibilities separate¶
For ArchLinterNet's own repository, the intended pattern is:
PR
complete authoritative validation
-> canonical architecture artifacts
-> CLI-generated report/badge payload
-> required merge gate
main quality
focused Linux coverage
-> canonical coverage evidence
-> SonarCloud + Codecov refresh
main packages
development version + monotonic run
-> 0.8.0-main.N
-> GitHub Packages
The PR remains the complete architecture merge authority. Generic main telemetry does not become Architecture Health evidence, and an ordinary merge does not rerun the full architecture matrix merely to refresh a report or badge.
12. Understand main.N correctly¶
0.8.0-main.N is a development/dogfood package identity. It is not an RC and is not the public release candidate.
The public release workflow (release-nuget.yml) creates and validates a fresh immutable candidate, verifies its package/provenance evidence, and only then may publish to NuGet.org and create the GitHub Release. No main.N package is promoted or renamed into a stable release.
Package visibility is not the trust boundary; exact version, source identity, package-set integrity, deterministic restore, and the release workflow's authorization evidence are. The repository retains only the newest five complete four-package main.N sets; stable and other prerelease families are outside that retention selection. See Release process for exact package-set and provenance verification.
13. Documentation publication behavior¶
Ordinary main workflows do not deploy MkDocs/GitHub Pages. Public documentation is deployed by the real public release workflow only when it runs with publish: true.
Therefore source documentation on main may temporarily be newer than the currently published stable documentation site. Do not infer public release status from documentation source changes alone.
Migration completion checklist¶
The extended-governance migration is complete when:
- the pinned CLI runs the repository's unchanged baseline behavior correctly;
- any retained manual ignores have a deliberate structured-waiver migration state;
- policy v2 strict lifecycle is enabled when ready;
- topology completeness claims match the reviewed repository universe;
- contract-surface exposure protects the intended visible boundaries;
- metric budgets are based on inspected canonical measurements;
- required SARIF is bound to current repository/revision/scope evidence;
- finding debt, waiver debt, weakening, metrics, topology, and external evidence remain distinct in Health;
- PR Markdown and the real Health badge come from CLI-owned canonical artifacts;
- CI contains no second architecture-governance/counting/reporting implementation;
main.Nremains dogfood distribution andrelease-nuget.ymlremains public-release authority.