Skip to main content

Execute and govern GoCPG architecture checks

Interpret architecture gate profiles, baselines, waivers and incremental checks. Bind release decisions to complete, trusted evidence.

GoCPG

An architecture check identifies dependencies that violate agreed component boundaries and connects each finding to source code. The team can inspect results first, record existing debt, and then block new violations in CI. This guide explains how an analysis result becomes a release decision. It is for repository owners and CI operators who already have a validated model and a CPG for the revision they intend to check. Start with model and rules to define components and contracts. Use interfaces and results to inspect the output.

The gocpg architecture validate command only checks the rule’s schema and language applicability. The production CI path needs a project policy, a project key, a release-control document and an output artifact. The system must also prove that the graph, facts, model and rules belong to the evaluated commit.

Choose a profile for your rollout stage

Start with discovery while defining components. Use advisory to review findings and tune the policy with the team. After recording baseline debt, enforce_new checks new violations. Choose enforce_all when releases must account for existing debt as well, or strict for the full check matrix, including empty selections.

The profile determines how findings affect the decision. Input completeness determines whether the system can make an enforcing decision.

Release Gate Profiles

The gate evaluates evidence completeness before counting findings. It needs a fresh CPG, a complete full or equivalent incremental run, every active rule’s outcome, complete required capabilities, known dependency denominators, classification and resolution coverage, and trusted governance.

Release Gate Profiles
Profile What it does with findings Gate decision
discovery Helps build and classify the model. NOT_APPLICABLE
advisory Reports findings while teams review the policy. NOT_APPLICABLE
enforce_new Blocks new unwaived findings. PASS or FAIL_VIOLATIONS on complete evidence.
enforce_all Blocks new and known unwaived findings. PASS or FAIL_VIOLATIONS on complete evidence.
strict Enforces the full matrix, including empty-selection checks. PASS or FAIL_VIOLATIONS on complete evidence.

Any enforcing profile returns BLOCKED_INCOMPLETE when its inputs are stale, partial, unknown or untrusted. Only PASS sets release_allowed=true. NOT_APPLICABLE means no enforcing decision was requested. A run with zero resolved dependency facts needs a known denominator; it cannot claim 100% resolution coverage from an empty result.

For a working repository, a CI call has this shape. Run from the repository root in PowerShell, with gocpg on PATH, a Git base ref and all four paths configured for the same project. Replace the paths and project key with your own; the policy is a repository policy manifest, not the standalone kind: architecture rule used by architecture validate.

For the first run, create the output directory and parse a CPG from the same repository root. ci-update uses the CPG’s stored source binding; an architecture run with a missing database returns git_source_scope_unavailable.

New-Item -ItemType Directory -Force .gocpg | Out-Null
gocpg parse --input . --output .gocpg/cpg.duckdb --lang python

Then update the graph and produce the architecture artifact:

gocpg ci-update --input . --output .gocpg/cpg.duckdb `
  --base-ref origin/main --head-ref HEAD --lang python `
  --architecture-policy .gocpg/architecture/policy.yaml `
  --architecture-project-key my-repository `
  --architecture-release-control .gocpg/architecture/release-control.yaml `
  --architecture-artifact .gocpg/architecture/result.json --json

The four architecture flags must be supplied together. A minimal release control for reviewing findings can select advisory, but it still must satisfy the current release-control schema and any required repository evidence. Inspect the artifact’s run identity, completeness, rule outcomes and gate status. A successful CLI exit alone does not establish PASS. The adoption guide explains the broader rollout sequence.

Teaching run with complete inputs

Use PowerShell 7, Git with an author name and email configured, gocpg on PATH, and a CodeGraph source checkout. Replace the two paths below. Create a new directory for the example. Two Python modules provide a real dependency for coverage checks; the shipped registry resolves python/module-boundaries@1.0.0.

$CodeGraphSource = "<path-to-codegraph-source>"
$DemoRoot = "<new-empty-directory>"
New-Item -ItemType Directory -Path $DemoRoot | Out-Null
Set-Location $DemoRoot
New-Item -ItemType Directory -Force app, .gocpg/architecture/fixtures, gocpg/configs/architecture/presets | Out-Null
Copy-Item -Recurse "$CodeGraphSource/gocpg/configs/architecture/presets/v1" gocpg/configs/architecture/presets/v1
@'
from app.helpers import format_greeting

def greet(name):
    return format_greeting(name)
'@ | Set-Content -Encoding utf8 app/service.py
@'
def format_greeting(name):
    return "Hello, " + name
'@ | Set-Content -Encoding utf8 app/helpers.py
@'
.gocpg/cpg.duckdb*
.gocpg/architecture/result.json
'@ | Set-Content -Encoding utf8 .gitignore

Create a single-component policy and a release-control document selecting discovery. The fixtures paths identify the rule source pair; qualifying that pair is a separate step when preparing a production policy.

@'
schema_version: architecture_policy.v1
id: teaching-service/python
version: 1.0.0
preset_ref: python/module-boundaries@1.0.0
source_paths:
- app/*.py
- app/**/*.py
vocabularies:
  layer:
  - service
dispositions:
- id: include-production
  action: include
  paths:
  - app/*.py
  - app/**/*.py
  owner: Architecture Team
  reason: Teaching source population.
components:
- uid: service
  id: service
  name: Service
  kind: leaf
  tags:
    layer:
    - service
classifiers:
- id: service-files
  component: service
  priority: 100
  paths:
  - app/**
  entity_kinds:
  - file
  - function
  - method
  - type
rules:
- id: TEAM-CLASSIFICATION-01
  version: 1.0.0
  contract: exhaustive_classification
  mode: discovery
  owner: Architecture Team
  severity: high
  applicability: python
  rationale: Classify every source entity.
  fixtures:
    valid: fixtures/valid.py
    invalid: fixtures/invalid.py
  spec:
    contract: exhaustive_classification
    languages:
    - python
    minimum_coverage: 1
'@ | Set-Content -Encoding utf8 .gocpg/architecture/policy.yaml
@'
schema_version: architecture_release_control.v1
profile: discovery
scope: enforce_new
previous_modes: {}
governance_manifest_path: ''
advisory_cycles_path: ''
rollback_requested: false
rollback_policy_disabled: false
rollback_from_policy_id: ''
rollback_from_policy_version: ''
rollback_from_policy_digest: ''
'@ | Set-Content -Encoding utf8 .gocpg/architecture/release-control.yaml
"def valid(): return 1" | Set-Content -Encoding utf8 .gocpg/architecture/fixtures/valid.py
"def invalid(): return 0" | Set-Content -Encoding utf8 .gocpg/architecture/fixtures/invalid.py

Commit the inputs, parse the CPG, and run the architecture check. The local origin/main ref represents the same teaching revision.

git init -b main
git add .
git commit -m "Teaching architecture input"
git update-ref refs/remotes/origin/main HEAD
gocpg parse --input . --output .gocpg/cpg.duckdb --lang python
gocpg ci-update --input . --output .gocpg/cpg.duckdb `
  --base-ref origin/main --head-ref HEAD --lang python `
  --architecture-policy .gocpg/architecture/policy.yaml `
  --architecture-project-key teaching-service `
  --architecture-release-control .gocpg/architecture/release-control.yaml `
  --architecture-artifact .gocpg/architecture/result.json --json
$result = Get-Content .gocpg/architecture/result.json -Raw | ConvertFrom-Json
$result | Select-Object status, complete, release_allowed, reason_codes
$result.binding | Select-Object project_key, repository_revision, cpg_repository_revision

Expect exit code 0, status=NOT_APPLICABLE, complete=true, release_allowed=false, and reason non_enforcing_profile. The binding names teaching-service; repository_revision matches cpg_repository_revision and git rev-parse HEAD. Both active_rule_ids and rule_outcome_ids contain TEAM-CLASSIFICATION-01. The discovery profile prepares analysis results for team review.

Create and explain a finding

Continue in the same teaching repository. Assign app/service.py to the service component and app/helpers.py to storage. Rule TEAM-BOUNDARY-01 forbids direct imports from the first component to the second. The existing import on line 1 of app/service.py will produce one finding.

Replace the policy with the following file and add two samples for the new rule. Keep the discovery profile to collect results and inspect dependencies.

@'
schema_version: architecture_policy.v1
id: teaching-service/python
version: 1.0.0
preset_ref: python/module-boundaries@1.0.0
source_paths:
- app/*.py
- app/**/*.py
vocabularies:
  layer:
  - service
  - storage
dispositions:
- id: include-production
  action: include
  paths:
  - app/*.py
  - app/**/*.py
  owner: Architecture Team
  reason: Teaching source population.
components:
- uid: service
  id: service
  name: Service
  kind: leaf
  tags:
    layer:
    - service
- uid: storage
  id: storage
  name: Storage
  kind: leaf
  tags:
    layer:
    - storage
classifiers:
- id: service-files
  component: service
  priority: 100
  paths:
  - app/service.py
  entity_kinds:
  - file
  - function
  - method
  - type
- id: storage-files
  component: storage
  priority: 100
  paths:
  - app/helpers.py
  entity_kinds:
  - file
  - function
  - method
  - type
rules:
- id: TEAM-CLASSIFICATION-01
  version: 1.0.0
  contract: exhaustive_classification
  mode: discovery
  owner: Architecture Team
  severity: high
  applicability: python
  rationale: Classify every source entity.
  fixtures:
    valid: fixtures/valid.py
    invalid: fixtures/invalid.py
  spec:
    contract: exhaustive_classification
    languages:
    - python
    minimum_coverage: 1
- id: TEAM-BOUNDARY-01
  version: 1.0.0
  contract: forbidden_dependency
  mode: discovery
  owner: Architecture Team
  severity: high
  applicability: python
  rationale: Teaching forbidden service-to-storage import.
  fixtures:
    valid: fixtures/boundary-valid.py
    invalid: fixtures/boundary-invalid.py
  spec:
    contract: forbidden_dependency
    from:
      components:
      - service
    to:
      components:
      - storage
    relation:
      edge_types:
      - import
      reachability: direct
'@ | Set-Content -Encoding utf8 .gocpg/architecture/policy.yaml
@'
def greet(name):
    return name
'@ | Set-Content -Encoding utf8 .gocpg/architecture/fixtures/boundary-valid.py
@'
from app.helpers import format_greeting

def greet(name):
    return format_greeting(name)
'@ | Set-Content -Encoding utf8 .gocpg/architecture/fixtures/boundary-invalid.py

Commit the new revision, rebuild the CPG and run the analysis again. These commands read the run ID and fingerprint from the artifact, then explain the finding:

git add .gocpg/architecture
git commit -m "Teaching forbidden dependency"
git update-ref refs/remotes/origin/main HEAD
gocpg parse --input . --output .gocpg/cpg.duckdb --lang python
gocpg ci-update --input . --output .gocpg/cpg.duckdb `
  --base-ref origin/main --head-ref HEAD --lang python `
  --architecture-policy .gocpg/architecture/policy.yaml `
  --architecture-project-key teaching-service `
  --architecture-release-control .gocpg/architecture/release-control.yaml `
  --architecture-artifact .gocpg/architecture/result.json --json
$artifact = Get-Content .gocpg/architecture/result.json -Raw | ConvertFrom-Json
$RunId = $artifact.binding.run_id
$Fingerprint = $artifact.current_advisory_cycle.rule_fingerprints.'TEAM-BOUNDARY-01'[0]
gocpg architecture explain --db .gocpg/cpg.duckdb --run-id $RunId --fingerprint $Fingerprint --json

Expect finding_count: 1, complete: true and status NOT_APPLICABLE in the artifact. In the explain response, rule_id is TEAM-BOUNDARY-01, the components are service and storage, and the primary source location is app/service.py, line 1. This connects the boundary rule to the specific import and related file.

Use this $RunId to inspect and export results; $Fingerprint selects the finding to explain.

Fix the source and repeat the check

In the same teaching directory, move the simple formatting into greet. The function still returns Hello, followed by the name, while the direct service to storage import disappears. Commit the change and rebuild the graph:

@'
def greet(name):
    return "Hello, " + name
'@ | Set-Content -Encoding utf8 app/service.py
git add app/service.py
git commit -m "Remove forbidden storage dependency"
git update-ref refs/remotes/origin/main HEAD
gocpg parse --input . --output .gocpg/cpg.duckdb --lang python
gocpg ci-update --input . --output .gocpg/cpg.duckdb `
  --base-ref origin/main --head-ref HEAD --lang python `
  --architecture-policy .gocpg/architecture/policy.yaml `
  --architecture-project-key teaching-service `
  --architecture-release-control .gocpg/architecture/release-control.yaml `
  --architecture-artifact .gocpg/architecture/result.json --json
$fixed = Get-Content .gocpg/architecture/result.json -Raw | ConvertFrom-Json
$fixed | Select-Object finding_count, complete, status

The new artifact contains finding_count: 0, complete: true, and status: BLOCKED_INCOMPLETE. Its reason_codes includes canonical_coverage_incomplete: removing the only import leaves this minimal project with no relationships for calculating coverage. The rule ran and the finding was removed; the release check evaluates graph coverage separately. The revisions in binding.repository_revision and binding.cpg_repository_revision must match git rev-parse HEAD.

Act on the result

Act on the result
Result Next action
PASS Include the architecture result in the release acceptance package.
FAIL_VIOLATIONS Review blocking findings: fix the dependencies or obtain a waiver decision for the current scope. Then rerun the analysis.
BLOCKED_INCOMPLETE Check the CPG revision, rule-result completeness, and governance trust. Refresh missing inputs and rerun.
NOT_APPLICABLE Review the discovery/advisory results. Select an enforcing profile when the policy is ready.

Baseline Guide

A baseline identifies debt already reviewed for an exact rule, fingerprint version, finding fingerprint and rule semantic hash. In enforce_new, an exact known finding is excluded from the new-violation count; enforce_all and strict still consider known unwaived debt. A baseline acknowledges the finding. It does not make the dependency acceptable or alter the graph.

Consider a run where UI imports storage. If its fingerprint is reviewed as existing debt, the next identical run can classify it as known. If the rule changes from direct to transitive, its semantic hash changes; the old decision moves to needs_review. If the import disappears and later returns, its lifecycle is regressed, not silently known again.

To register known debt, run a full analysis and review each finding. Obtain a signed governance manifest from the service that stores project decisions, then rerun the analysis bound to that manifest. A repository-local baseline export helps compare findings between runs; the signed manifest determines which decisions the release check applies.

Save the signed JSON manifest received from the control plane inside the repository, for example at .gocpg/architecture/governance.json. In .gocpg/architecture/release-control.yaml, set governance_manifest_path: .gocpg/architecture/governance.json; the path is relative to the repository root. Repeat ci-update with this release-control file. GoCPG loads the manifest and checks its signature and scope when it evaluates the release gate.

Waiver and Trust Model

A waiver is an exception for one finding and an expiry. It records rule owner, requester, approver, reason, decision reference, fingerprint, semantic hash, fingerprint version and scope. Requester and approver must be different actors. A changed fingerprint, rule, repository or revision needs a fresh decision; a symbol alias cannot transfer a waiver.

The Ed25519 governance payload binds project, repository, commit, branch, model and rules hashes, validity window and monotonic revision. Trusted public keys and revocation state come from the control plane or CI configuration. Validation checks signature, key ID, revocation, validity and exact scope. Expired, revoked, unused and stale waivers remain visible as findings. A signature or scope failure blocks enforcement with BLOCKED_INCOMPLETE.

For example, if a waiver was signed for commit A and the policy is evaluated at commit B, do not copy its fingerprint into a local YAML file to suppress the finding. Obtain a decision for the current scope after a complete run.

Incremental Scan Semantics

The planner uses changed paths and affected component UIDs to decide whether to reuse previous work. Model, public API, dependency edge, strongly connected component, baseline and waiver changes expand invalidation. Unknown components or a partial invalidation set are errors. The default full-scan threshold is an affected-component ratio greater than 0.20; a model change always selects a full run.

incremental_complete has release authority only when fingerprints, lifecycle states, baseline and waiver states, coverage, semantic digest and gate decision match a full run for the same inputs. A partial enforcing run gets one synchronous full retry. If that retry is partial, failed, stale or mismatched, the result remains BLOCKED_INCOMPLETE.

For example, moving src/ui/ to a new component changes classification and can affect edges that do not appear in the edited file list. The model change therefore triggers a full scan. Merely scanning the changed files would miss relations whose endpoints were reclassified.

Migration Guide

Legacy checks can be migrate, retain advisory, retire or defer. Discovery and advisory may show labelled legacy evidence, but legacy output cannot create canonical PASS, finding lifecycle state, baseline, waiver or release authority.

  1. Inventory old handlers and rule packs, assigning each one disposition.
  2. Run old and canonical paths in shadow on the same revision; classify each difference by stable fingerprint and source witness.
  3. Qualify golden fixtures and resolve unexplained blocking false negatives.
  4. Observe two real advisory release cycles with stable semantic identity.
  5. Move consumers to persisted canonical results and prove that legacy enforcement is disabled.

Rollback can return enforcement to advisory. It cannot promote an old checker into release authority. A local parity test demonstrates only its fixture scope, not production adoption.

Implementation checks

From the gocpg module root, these tests verify implementation contracts. They are not evidence that an arbitrary repository passed its release gate.

go test ./pkg/architecture -run '^TestSignedGovernanceRequiresExactScopeAndSeparationOfDuties$' -count=1
go test ./pkg/architecture -run '^TestNormativeGovernanceRequiresCompleteScopeRevisionAndRevocationState$' -count=1
go test ./pkg/architecture -run '^TestNormativeReleaseGateFiveProfilesAndStateMatrix$' -count=1
go test ./pkg/architecture -run '^TestStory1240FullIncrementalEquivalence$' -count=1
go test ./pkg/architecture -run '^TestCodeGraphReleaseGateRejectsLegacyAuthorityInEnforceNew$' -count=1