Impact analysis (pacto impact)¶
A semantic diff tells you how a revision changed. The operational graph tells you who depends on the service and where it runs. Impact analysis composes the two to answer the question a reviewer actually asks before merging: if this revision ships, what is the real blast radius?
Impact is framework-independent (pkg/impact). It consumes the pure diff engine
(change classification) and the immutable
operational-graph read model, and imports no Kubernetes,
OCI, dashboard, MCP or HTTP code. The same analysis therefore backs the CLI, an
MCP tool and the dashboard, and given the same snapshot every one of them returns
the identical answer. What differs between the three is where the snapshot may
come from: the CLI takes offline sources only (which sources, exactly), while the MCP
server and the dashboard can also be pointed at a registry or a cluster.
impact is the name of the CLI command, the MCP tool and the Go package; in the
dashboard the same analysis is presented as the Change analysis workspace,
alongside the semantic diff it composes with.
Diff × graph → impact¶
flowchart LR
OLD["Old revision<br/>contract + files"] --> DIFF["Semantic diff<br/>pkg/diff"]
NEW["New revision<br/>contract + files"] --> DIFF
DIFF --> CLASS["Classification<br/>NON_BREAKING · POTENTIAL_BREAKING · BREAKING<br/>breaking and potentially-breaking, kept separate"]
SNAP["Fleet Snapshot<br/>pkg/fleet · immutable read model"] --> GRAPH["Dependents traversal<br/>direct + transitive"]
CLASS --> IMPACT["Impact result"]
GRAPH --> IMPACT
IMPACT --> CONS["Affected consumers<br/>compatibility verdict · confidence"]
IMPACT --> TARGETS["Active targets<br/>where the change lands"]
IMPACT --> OWNERS["Owners to notify"]
The diff is computed once over the old→new revision. The changed service is then looked up in the operational graph and traversed in the dependents direction, direct and transitive. Each dependent becomes an affected consumer, annotated with the evidence Pacto actually has for that edge. The result also rolls up the active targets the change would land in and the owners to notify.
Every answer inherits the snapshot's asOf time, completeness and
limitations, so a partial fleet is never presented as a complete blast radius.
If the changed service is not present in the graph at all, the result says so with
a SERVICE_NOT_IN_FLEET limitation rather than reporting an empty blast radius.
What an affected consumer carries¶
For each dependent the analysis records:
| Field | Meaning |
|---|---|
| service / domain / owner | Who is affected and who owns them. |
| depth / direct / path | depth 1 is a direct dependent, >1 is transitive. path is the dependency chain from the consumer to the changed service. |
| required | Whether the consumer declared this dependency as required. |
| compatibility | The consumer's declared compatibility range against the changed service. |
| compatibilityVerdict | Whether the new version satisfies that range (see below). |
| provenance | Where the edge came from: declared, observed, declared+observed or inferred. |
| confidence | How strongly the evidence supports the claim (see below). |
| status / targets | The consumer's aggregate status and the operational targets it runs in. |
Compatibility verdict¶
The verdict checks the new version against the consumer's declared compatibility range:
| Verdict | When |
|---|---|
| compatible | The consumer declares a range and the new version satisfies it. |
| incompatible | The consumer declares a range and the new version does not satisfy it. |
| unknown | No declared range, or a range or version that cannot be parsed. Absence of a range is not a pass — it is uncertainty. |
Confidence model¶
Confidence grades how strongly the available evidence supports each affected-consumer claim.
| Confidence | Exact meaning |
|---|---|
| contractual | A declared dependency with a usable compatibility range. The consumer's own contract says it depends on this service and pins the versions it accepts. |
| declared | A declared dependency without a usable compatibility range. The dependency is stated, but no version constraint was pinned, so a compatibility verdict cannot be computed. |
| observed | Runtime use of the dependency was observed in a window. Requires --include-observed. |
| corroborated | The declared dependency and an observed one agree — the strongest grade, contract and runtime saying the same thing. |
| inferred | A transitive effect reached through another affected service (depth > 1). It follows from the graph, not from a direct declaration or observation. |
| unknown | A direct edge with no declaration and no observation — the effect is possible but unverified. |
Two rules follow directly from this model and are load-bearing:
An inferred path is not a confirmed runtime impact. A transitive consumer is reached through the graph. It tells you where to look, not that the consumer will break. Treat
inferredas a lead to verify, never as a settled fact.Observed evidence only raises confidence when you opt in. Without
--include-observedthe analysis is declared-only. Runtime observations then let a direct edge becomeobservedorcorroborated.
Observed edges come from OpenTelemetry traces via --traces <file> (which
implies --include-observed). Beyond corroborating declared consumers, traces
surface observed-only (shadow) consumers — services seen calling the changed
service that never declared the dependency. A declared-only analysis cannot see
them; with traces they appear as direct consumers at observed confidence, so a
release check is not blind to undeclared traffic.
CLI¶
<old> and <new> are the two revisions to compare — bundle paths or refs.
pacto impact builds its snapshot from offline sources only — --local
(repeatable, defaults to .), --target-state and --traces. It takes a subset
of the source flags pacto fleet accepts: there is no
--k8s, --oci, --cache or --evidence-url here, and passing one fails with
unknown flag. To analyse a fleet you do not have on disk, pull those bundles
first with pacto pull and point --local at the
directory. The two revisions being compared are separate from the fleet snapshot
and may be oci:// references.
Turn on runtime corroboration with --include-observed, or supply an OTLP/JSON
trace export with --traces (which implies it) so observed traffic raises
consumer confidence and surfaces shadow consumers:
pacto impact ./payments-api@1.4.0 ./payments-api@2.0.0 \
--local ./services \
--traces ./traces.json
The output reports the classification, the breaking and potentially-breaking changes (kept separate — a potential break is never counted as a confirmed one), every affected consumer with its compatibility verdict and confidence, the active targets and the owners to notify — along with the snapshot's completeness and any limitations.
Exit status: what makes a consumer active¶
pacto impact exits 1 only when both halves are true — the change is
BREAKING and at least one incompatible consumer is active. Anything else
exits 0, including a run that prints Classification: BREAKING and a list of
consumers every one of which says compat=incompatible.
Active means the snapshot knows of somewhere that consumer is deployed — at least one operational target. Compatibility is a statement about contracts; active is a statement about the world. A consumer that is incompatible on paper but is running nowhere the snapshot can see is a review item, not a release blocker, so it does not fail the command.
The consequence catches people out, because --local defaults to . and local
bundles declare no targets: a declared-only run can never exit non-zero.
$ pacto impact ./api-v1 ./api-v2 --local ./fleet
Classification: BREAKING
Affected consumers (1):
web direct confidence=contractual compat=incompatible owner=frontend
$ echo $?
0
The Active targets line is the tell — it is printed only when there is at least
one, so no line means no non-zero exit is possible. Give the snapshot a source
that knows where things run and the same command blocks:
$ pacto impact ./api-v1 ./api-v2 --local ./fleet --target-state ./targets.yaml
Classification: BREAKING
Affected consumers (1):
web direct confidence=contractual compat=incompatible owner=frontend
Active targets (1): [production/kubernetes-workload/shop%2Fweb]
breaking changes affect active consumers
$ echo $?
1
In CI that means one of two deliberate choices. Gate on the exit code and you are
gating on deployed impact, which is what you want in a promotion pipeline — but
only if the job actually supplies targets. Gate on the JSON instead
(--output-format json, then classification == "BREAKING") and you are gating
on the contract alone, which is what you want before anything is deployed at all.
That JSON carries schemaVersion: pacto.dev/impact/v1 — the compatibility
contract to branch on before reading any other field.
The file --target-state expects is documented under
target-state fixtures;
pacto impact accepts no live sources, so on a laptop that file is the only way
to make the exit code mean anything.
MCP tool: pacto_impact¶
The same analysis is exposed to agents as the read-only pacto_impact MCP tool.
It belongs to the fleet query family — see
MCP integration —
and shares that family's boundaries: it projects the operational graph, observes
nothing, changes nothing and authorizes nothing. An agent uses pacto_impact to
understand a proposed change's blast radius before recommending a review, never
to act on it. Every answer carries asOf, completeness and limitations, so an
agent can tell how much of the system the answer actually covers.
It is the one fleet tool that does not serve a frozen snapshot: it resolves its
two refs and rebuilds the graph on every call, so its asOf advances while the
pacto_fleet_* tools' stays at the value they were started with. When the two
disagree they are describing two moments, not two systems — see
what a session freezes.
Dashboard: Change analysis¶
In the dashboard this analysis is one half of the Change analysis workspace,
served by the /api/fleet/impact endpoint and returning the same result model the
CLI and MCP tool produce. The workspace answers both halves of a single question
on one screen: what changed between two revisions of a service, and what that
change affects.
Change analysis is contextual: it is entered from the service or revision you are
already looking at (the Compare revisions action), the revision selectors are
populated from that service's known revisions, and the analyzed pair is in the URL
so the answer itself is shareable. It analyzes the currently published
snapshot — the same one the Operational Graph shows — so the answer's snapshotId
matches the graph, never a divergent rebuild. Breaking and potentially-breaking
changes are shown separately, and each consumer carries its path to the changed
service, compatibility range and verdict, and confidence with an explanation.
Because observed evidence must have a real source, the dashboard's
include-observed control is enabled only when the host declares an observation
source (reported by GET /api/capabilities); otherwise it is disabled — the
dashboard never ships a control that would have no effect.
It recommends review, it does not act¶
Impact analysis lists what to review. It never recommends or performs an autonomous action, and it never authorizes one.
- It does not act. Rolling back, blocking a deploy, paging an owner or gating a pipeline is done by external controllers and delivery systems. Impact tells them what is affected and how sure it is.
- It does not authorize. Whether a change may proceed stays with policy and IAM systems. Impact supplies evidence for that decision, not the decision.
- It never overstates certainty. A
partialsnapshot is incomplete knowledge, anunknownverdict is uncertainty and aninferredconsumer is a lead to verify — none of them is a confirmed runtime impact.
See also¶
- Concepts — what a confidence level, a bounded list and an unretrievable revision each mean
- The Pacto Operational Graph — the read model impact projects onto
- Change classification rules — the semantic diff impact composes with the graph
- MCP integration — the fleet query tool family
pacto_impactbelongs to