Skip to content

Guided tour

This tour answers seven questions people ask about services they did not build. Each answer uses one or two pacto commands on a fixed set of example contracts and explains the output. The tour covers contracts and the checks on them. Delivery has its own demos.

Before you start

Everything here runs offline except pacto dashboard. The fixture is committed to the repository, so you need no cluster, registry, running service or network.

You need the Pacto CLI on your PATH and a clone:

git clone https://github.com/TrianaLab/pacto.git && cd pacto

Paste every command below from the repository root.

To drive a real stack, see the Docker Compose demo. It needs neither a clone nor the CLI.

The tour shows runtime conformance (checking what a service really does on the network) as a recorded finding only: story 2 shows one the kernel saw. To watch it live, use the Compose demo or the node agent on a Kubernetes cluster.

The fleet

Thirty-six services are in the snapshot. Nine carry the stories:

Service Owner Its part
payments-service payments-team six revisions; the change stories 3 and 4 analyse
orders-service commerce-team declares a dependency on payments, and is observed calling it
api-gateway platform-foundations declares the same dependency, and has an expired readiness assessment
auth-service identity-team deployed and never observed: the Unknown target
audit-log platform-foundations-security calls payments and declares nothing: the shadow consumer
storefront-web frontend-team a production environment whose values fail the chart schema
catalog-service catalog-team an enum value added behind a $ref
shipping-rates logistics-team a raised compatibilityEpoch
inventory-service catalog-team stops promising a guarantee two consumers rely on

A target is one running deployment of a service. A readiness assessment is a dated list of claims in a contract, and a shadow consumer is a service seen calling another that no contract declares; the glossary has these and the other words the tour uses.

A contract says what a service exposes, what it needs and how it behaves. How the service is deployed stays in its own files. The contract reference has every section.

Story 1: List an inherited fleet and its state

Enumerate everything from contracts alone, then find out what state it is in.

pacto fleet search --local examples/demo/bundles
36 of 36 service(s):
  analytics-pipeline           NotEvaluated owner=data-platform  revs=2 targets=0
  api-gateway                  NotEvaluated owner=platform-foundations revs=3 targets=0
  audit-log                    NotEvaluated owner=platform-foundations-security revs=1 targets=0
  auth-service                 NotEvaluated owner=identity-team  revs=1 targets=0
  campaign-service             NotEvaluated owner=growth-team    revs=2 targets=0
  carrier-gateway              NotEvaluated owner=logistics-team revs=2 targets=0
  catalog-service              NotEvaluated owner=catalog-team   revs=3 targets=0
  email-provider               NotEvaluated owner=-              revs=1 targets=0
  event-bus                    NotEvaluated owner=data-platform  revs=2 targets=0
  experiment-service           NotEvaluated owner=growth-team    revs=1 targets=0
  feature-store                NotEvaluated owner=data-platform  revs=1 targets=0
  fraud-service                NotEvaluated owner=payments-team  revs=1 targets=0
  frontend                     NotEvaluated owner=frontend-team  revs=1 targets=0
  fulfillment-service          NotEvaluated owner=logistics-team revs=2 targets=0
  inventory-service            NotEvaluated owner=catalog-team   revs=3 targets=0
  loyalty-service              NotEvaluated owner=growth-team    revs=2 targets=0
  notification-worker          NotEvaluated owner=commerce-team  revs=1 targets=0
  orders-service               NotEvaluated owner=commerce-team  revs=3 targets=0
  pacto-demo                   NotEvaluated owner=platform-foundations revs=1 targets=0
  payments-service             NotEvaluated owner=payments-team  revs=6 targets=0
  platform-app-config          NotEvaluated owner=platform-foundations revs=1 targets=0
  platform-http-policy         NotEvaluated owner=platform-foundations revs=1 targets=0
  platform-worker-config       NotEvaluated owner=platform-foundations revs=1 targets=0
  postgresql                   NotEvaluated owner=infra-data     revs=1 targets=0
  pricing-service              NotEvaluated owner=catalog-team   revs=2 targets=0
  recommendations              NotEvaluated owner=growth-team    revs=2 targets=0
  redis                        NotEvaluated owner=infra-data     revs=1 targets=0
  reporting-api                NotEvaluated owner=data-platform  revs=1 targets=0
  returns-service              NotEvaluated owner=logistics-team revs=2 targets=0
  search-indexer               NotEvaluated owner=catalog-team   revs=1 targets=0
  shipping-rates               NotEvaluated owner=logistics-team revs=3 targets=0
  storefront-web               Invalid      owner=frontend-team  revs=3 targets=0
  stripe-api                   NotEvaluated owner=external/stripe revs=1 targets=0
  support-assistant            NotEvaluated owner=support-team   revs=1 targets=0
  ticketing-service            NotEvaluated owner=support-team   revs=2 targets=0
  warehouse-scanner            NotEvaluated owner=logistics-team revs=1 targets=0

Thirty-six services, each with its owner, how many contract revisions it has published and how many deployed targets are running it. NotEvaluated means Pacto knows no deployment of the service yet, so it has nothing to evaluate.

--target-state folds in what a platform observed about the running deployments. Three of its targets, abridged:

schemaVersion: pacto.dev/fleet-targets/v1
targets:
  - { scope: production-eu, kind: kubernetes-workload, name: payments/payments-service,
      service: payments-service, compliance: Compliant,
      coverage: { evaluated: 6, required: 6 }, evidenceAt: 2026-07-29T09:40:00Z }

  - scope: production-eu
    kind: kubernetes-workload
    name: commerce/orders-service
    service: orders-service
    compliance: NonCompliant
    coverage: { evaluated: 5, required: 5 }
    evidenceAt: 2026-07-29T09:40:00Z
    findings:
      - code: STATELESS_PERSISTENT_CONFLICT
        severity: error
        category: RuntimeDrift
        message: declared stateless but the observed workload mounts a persistent volume
      - code: INTERFACE_ABSENT
        severity: error
        category: RuntimeDrift
        message: nothing listens on the declared http interface
        evidenceRefs: [{ source: k8s, collector: k8s-observer+ebpf }]

  - { scope: production-eu, kind: kubernetes-workload, name: identity/auth-service,
      service: auth-service, compliance: Unknown,
      coverage: { evaluated: 1, required: 4 } }
pacto fleet search --local examples/demo/bundles --target-state examples/demo/fleet-targets.yaml
36 of 36 service(s):
  analytics-pipeline           NotEvaluated owner=data-platform  revs=2 targets=0
  api-gateway                  NotEvaluated owner=platform-foundations revs=3 targets=0
  audit-log                    NotEvaluated owner=platform-foundations-security revs=1 targets=0
  auth-service                 Unknown      owner=identity-team  revs=1 targets=1
  campaign-service             NotEvaluated owner=growth-team    revs=2 targets=0
  carrier-gateway              NotEvaluated owner=logistics-team revs=2 targets=0
  catalog-service              NotEvaluated owner=catalog-team   revs=3 targets=0
  email-provider               NotEvaluated owner=-              revs=1 targets=0
  event-bus                    NotEvaluated owner=data-platform  revs=2 targets=0
  experiment-service           NotEvaluated owner=growth-team    revs=1 targets=0
  feature-store                NotEvaluated owner=data-platform  revs=1 targets=0
  fraud-service                Compliant    owner=payments-team  revs=1 targets=1
  frontend                     NotEvaluated owner=frontend-team  revs=1 targets=0
  fulfillment-service          NotEvaluated owner=logistics-team revs=2 targets=0
  inventory-service            NotEvaluated owner=catalog-team   revs=3 targets=0
  loyalty-service              NotEvaluated owner=growth-team    revs=2 targets=0
  notification-worker          NotEvaluated owner=commerce-team  revs=1 targets=0
  orders-service               NonCompliant owner=commerce-team  revs=3 targets=1
  pacto-demo                   NotEvaluated owner=platform-foundations revs=1 targets=0
  payments-service             Compliant    owner=payments-team  revs=6 targets=1
  platform-app-config          NotEvaluated owner=platform-foundations revs=1 targets=0
  platform-http-policy         NotEvaluated owner=platform-foundations revs=1 targets=0
  platform-worker-config       NotEvaluated owner=platform-foundations revs=1 targets=0
  postgresql                   NotEvaluated owner=infra-data     revs=1 targets=0
  pricing-service              NotEvaluated owner=catalog-team   revs=2 targets=0
  recommendations              NotEvaluated owner=growth-team    revs=2 targets=0
  redis                        NotEvaluated owner=infra-data     revs=1 targets=0
  reporting-api                NotEvaluated owner=data-platform  revs=1 targets=0
  returns-service              NotEvaluated owner=logistics-team revs=2 targets=0
  search-indexer               NotEvaluated owner=catalog-team   revs=1 targets=0
  shipping-rates               NotEvaluated owner=logistics-team revs=3 targets=0
  storefront-web               Invalid      owner=frontend-team  revs=3 targets=0
  stripe-api                   NotEvaluated owner=external/stripe revs=1 targets=0
  support-assistant            NotEvaluated owner=support-team   revs=1 targets=0
  ticketing-service            NotEvaluated owner=support-team   revs=2 targets=0
  warehouse-scanner            NotEvaluated owner=logistics-team revs=1 targets=0

Five states in one screen. Compliant is a verdict backed by evidence, NonCompliant is a confirmed contradiction, Unknown is evidence that never arrived and NotEvaluated means nobody is deployed there to evaluate. Invalid means the newest contract fails validation; story 7 shows why.

Story 2: Find the reason behind an Unknown

Get the reason behind a verdict. Missing evidence counts as Unknown.

pacto fleet explain identity/auth-service --local examples/demo/bundles --target-state examples/demo/fleet-targets.yaml --freshness 24h
target production-eu/kubernetes-workload/identity%2Fauth-service: Unknown
  [EVIDENCE_MISSING] no evidence has been observed for this target
pacto explain examples/demo/bundles/api-gateway/v1.2.0
Service: api-gateway@1.2.0
Owner: platform-foundations
Pacto Version: 2.0

Workload: service

State:
  Type: stateless
  Persistence: local/ephemeral
  Data Criticality: low

Interfaces (1):
  - http (openapi: interfaces/openapi.json, public)

Dependencies (3):
  - auth-service: oci://ghcr.io/trianalab/pacto/auth-service (^1.0.0, required)
  - orders-service: oci://ghcr.io/trianalab/pacto/orders-service (^1.0.0, required)
  - payments-service: oci://ghcr.io/trianalab/pacto/payments-service (^1.0.0, required)

Readiness:
  Score: 0
  Gate: FAIL (score 0 / minScore 75)
  Earned Weight: 0
  Total Weight: 150
  Expires: 2025-01-01 (EXPIRED)
  Status: 5 done, 0 partial, 0 not-done, 0 deferred

  Checks:
    - gateway-dashboard observability  done      weight=30 earned=0 evidence=https://grafana.acme.com/d/api-gateway
    - rate-limiting    infrastructure done      weight=25 earned=0 evidence=ENG-1234
    - security-review  security       done      weight=40 earned=0 evidence=SEC-2045
    - load-test        testing        done      weight=35 earned=0 evidence=https://k6.acme.com/api-gateway-load-2024-12
    - runbook          documentation  done      weight=20 earned=0 evidence=docs/runbooks/api-gateway.md

EVIDENCE_MISSING describes the observer and leaves the service's own verdict open. api-gateway declares five readiness claims, every one marked done with evidence attached, and the assessment earns zero because it expired on 2025-01-01. Pacto fails closed on both.

A confirmed violation reads differently:

pacto fleet get --target commerce/orders-service --local examples/demo/bundles --target-state examples/demo/fleet-targets.yaml
Target: production-eu/kubernetes-workload/commerce%2Forders-service
Service: orders-service
Compliance: NonCompliant
Revision: orders-service@sha256:abf75f569260779a932066089e72cf0c2e6c8ac61a22fcf849cced91aaa75736 (inferred)
Stale: false
Coverage: 5/5 evaluated
  finding [error] STATELESS_PERSISTENT_CONFLICT: declared stateless but the observed workload mounts a persistent volume
  finding [error] INTERFACE_ABSENT: nothing listens on the declared http interface (seen by k8s-observer+ebpf)

Coverage: 5/5 evaluated marks the difference from the Unknown above: every check ran, so the verdict is a conclusion. Each finding names one exact contradiction. The contract declares the workload stateless and the observation found a persistent volume mounted. The contract declares an http interface and nothing listens on its port: seen by k8s-observer+ebpf says the node agent saw that in the kernel, and Kubernetes agreed. Each finding is one fact that disagrees with one declaration.

Story 3: Check a change before you ship it

Classify a change from two contracts, with nothing running, then gate CI on it.

pacto diff examples/demo/bundles/payments-service/v1.2.1 examples/demo/bundles/payments-service/v2.0.1
Classification: BREAKING
Required bump: major
Changes (39):
  [NON_BREAKING] service.version (modified): service.version modified [1.2.1 -> 2.0.1]
  [POTENTIAL_BREAKING] capabilities (removed): capabilities removed [- extension (payments.trianalab.com/refund)]
  [NON_BREAKING] capabilities (added): capabilities added [+ extension (payments.trianalab.com/dispute)]
  [POTENTIAL_BREAKING] dependencies.required (modified): dependencies.required modified [fraud-service: required=false -> fraud-service: required=true]
  [BREAKING] asyncapi.channels[payment.completed] (removed): channel payment.completed removed [- payment.completed]
  [BREAKING] asyncapi.channels[payment.failed] (removed): channel payment.failed removed [- payment.failed]
  [BREAKING] asyncapi.channels[payment.refunded].publish.message.payload.properties.charge_id (removed): asyncapi.channels[payment.refunded].publish.message.payload.properties.charge_id removed [- map[type:string]]
  [NON_BREAKING] asyncapi.channels[payment.refunded].publish.message.payload.properties.payment_intent_id (added): asyncapi.channels[payment.refunded].publish.message.payload.properties.payment_intent_id added [+ map[type:string]]
  [BREAKING] asyncapi.channels[payment.refunded].publish.message.payload.required[payment_intent_id] (added): asyncapi.channels[payment.refunded].publish.message.payload.required payment_intent_id added [+ payment_intent_id]
  [BREAKING] asyncapi.channels[payment.refunded].publish.message.payload.required[charge_id] (removed): asyncapi.channels[payment.refunded].publish.message.payload.required charge_id removed [- charge_id]
  [NON_BREAKING] asyncapi.channels[payment.intent.created] (added): channel payment.intent.created added [+ payment.intent.created]
  [NON_BREAKING] asyncapi.channels[payment.intent.failed] (added): channel payment.intent.failed added [+ payment.intent.failed]
  [NON_BREAKING] asyncapi.channels[payment.intent.succeeded] (added): channel payment.intent.succeeded added [+ payment.intent.succeeded]
  [BREAKING] openapi.paths[/charges] (removed): API path /charges removed [- /charges]
  [BREAKING] openapi.paths[/charges/{id}] (removed): API path /charges/{id} removed [- /charges/{id}]
  [POTENTIAL_BREAKING] openapi.paths[/refunds].methods[POST].request-body.content.application/json.schema.properties.charge_id (removed): openapi.paths[/refunds].methods[POST].request-body.content.application/json.schema.properties.charge_id removed [- map[type:string]]
  [NON_BREAKING] openapi.paths[/refunds].methods[POST].request-body.content.application/json.schema.properties.payment_intent_id (added): openapi.paths[/refunds].methods[POST].request-body.content.application/json.schema.properties.payment_intent_id added [+ map[type:string]]
  [BREAKING] openapi.paths[/refunds].methods[POST].request-body.content.application/json.schema.required[payment_intent_id] (added): openapi.paths[/refunds].methods[POST].request-body.content.application/json.schema.required payment_intent_id added [+ payment_intent_id]
  [NON_BREAKING] openapi.paths[/refunds].methods[POST].request-body.content.application/json.schema.required[charge_id] (removed): openapi.paths[/refunds].methods[POST].request-body.content.application/json.schema.required charge_id removed [- charge_id]
  [NON_BREAKING] openapi.paths[/refunds].methods[POST].responses[404].description (modified): openapi.paths[/refunds].methods[POST].responses[404].description changed [Charge not found -> Payment intent not found]
  [POTENTIAL_BREAKING] openapi.paths[/webhooks/stripe].methods[POST].request-body (removed): POST /webhooks/stripe request body removed [- POST /webhooks/stripe]
  [NON_BREAKING] openapi.paths[/webhooks/stripe].methods[POST].responses[400].description (modified): openapi.paths[/webhooks/stripe].methods[POST].responses[400].description changed [Invalid webhook signature -> Invalid signature]
  [NON_BREAKING] openapi.paths[/payment-intents] (added): API path /payment-intents added [+ /payment-intents]
  [NON_BREAKING] openapi.paths[/payment-intents/{id}] (added): API path /payment-intents/{id} added [+ /payment-intents/{id}]
  [NON_BREAKING] openapi.paths[/payment-intents/{id}/cancel] (added): API path /payment-intents/{id}/cancel added [+ /payment-intents/{id}/cancel]
  [NON_BREAKING] openapi.paths[/payment-intents/{id}/confirm] (added): API path /payment-intents/{id}/confirm added [+ /payment-intents/{id}/confirm]
  [POTENTIAL_BREAKING] schema.properties.ENABLE_REFUNDS (removed): schema.properties.ENABLE_REFUNDS removed [- map[default:true description:Feature flag to enable/disable refund processing type:boolean]]
  [POTENTIAL_BREAKING] schema.properties.FRAUD_CHECK_ENABLED.default (modified): schema.properties.FRAUD_CHECK_ENABLED.default changed [false -> true]
  [NON_BREAKING] schema.properties.FRAUD_CHECK_ENABLED.description (modified): schema.properties.FRAUD_CHECK_ENABLED.description changed [Enable fraud detection checks before processing payments -> Fraud detection is now enabled by default and required]
  [POTENTIAL_BREAKING] schema.properties.STRIPE_API_KEY (removed): schema.properties.STRIPE_API_KEY removed [- map[description:Stripe API secret key for payment processing type:string]]
  [POTENTIAL_BREAKING] schema.properties.STRIPE_SECRET_KEY (added): schema.properties.STRIPE_SECRET_KEY added [+ map[description:Stripe secret key (renamed from STRIPE_API_KEY) type:string]]
  [NON_BREAKING] schema.properties.WEBHOOK_SECRET.description (modified): schema.properties.WEBHOOK_SECRET.description changed [Stripe webhook signing secret for signature verification -> Stripe webhook signing secret (now required)]
  [BREAKING] schema.required[STRIPE_SECRET_KEY] (added): schema.required STRIPE_SECRET_KEY added [+ STRIPE_SECRET_KEY]
  [BREAKING] schema.required[WEBHOOK_SECRET] (added): schema.required WEBHOOK_SECRET added [+ WEBHOOK_SECRET]
  [BREAKING] schema.required[STRIPE_API_KEY] (removed): schema.required STRIPE_API_KEY removed [- STRIPE_API_KEY]
  [NON_BREAKING] configurations[app].values.ENABLE_REFUNDS (removed): configurations[app].values.ENABLE_REFUNDS removed [- true]
  [NON_BREAKING] configurations[app].values.FRAUD_CHECK_ENABLED (modified): configurations[app].values.FRAUD_CHECK_ENABLED changed [false -> true]
  [NON_BREAKING] configurations[app].values.STRIPE_API_KEY (removed): configurations[app].values.STRIPE_API_KEY removed [- ${STRIPE_API_KEY}]
  [NON_BREAKING] configurations[app].values.STRIPE_SECRET_KEY (added): configurations[app].values.STRIPE_SECRET_KEY added [+ ${STRIPE_SECRET_KEY}]

SBOM changes (1):
  ~ stripe-go version: 1.0.0 -> 2.0.0
breaking changes detected

Thirty-nine changes, one verdict and a non-zero exit so CI can gate on it. Two API paths removed, two event channels withdrawn, a required request field swapped for a differently-named one, two configuration keys becoming required. A capability dropped, an optional dependency became mandatory and one SBOM package version moved. Pacto found all of it by comparing one contract with another, with no running service. The classification rules are a published table.

The same service, one pair earlier, shows a release that breaks nobody:

pacto diff examples/demo/bundles/payments-service/v1.0.0 examples/demo/bundles/payments-service/v1.1.0
Classification: POTENTIAL_BREAKING
Required bump: minor
Changes (6):
  [NON_BREAKING] service.version (modified): service.version modified [1.0.0 -> 1.1.0]
  [NON_BREAKING] asyncapi.channels[payment.failed] (added): channel payment.failed added [+ payment.failed]
  [NON_BREAKING] openapi.paths[/refunds].methods[POST].request-body.content.application/json.schema.properties.amount.description (modified): openapi.paths[/refunds].methods[POST].request-body.content.application/json.schema.properties.amount.description changed [Refund amount in cents (partial refund if less than charge) -> Refund amount in cents]
  [NON_BREAKING] openapi.paths[/webhooks/stripe] (added): API path /webhooks/stripe added [+ /webhooks/stripe]
  [POTENTIAL_BREAKING] schema.properties.WEBHOOK_SECRET (added): schema.properties.WEBHOOK_SECRET added [+ map[description:Stripe webhook signing secret for signature verification type:string]]
  [NON_BREAKING] configurations[app].values.WEBHOOK_SECRET (added): configurations[app].values.WEBHOOK_SECRET added [+ ${STRIPE_WEBHOOK_SECRET}]

The result is POTENTIAL_BREAKING. A new endpoint, a new event channel and a new optional configuration property break nobody by themselves. Adding a property to a schema can still surprise a consumer that validates strictly. The third classification gives additive changes like these their own verdict.

Story 4: Find who a breaking change affects

Turn a classification into a list of consumers, each graded by how it is known.

pacto impact examples/demo/bundles/payments-service/v1.2.1 examples/demo/bundles/payments-service/v2.0.1 --local examples/demo/bundles
Impact: payments-service 1.2.1 -> 2.0.1
Classification: BREAKING
Breaking changes: 11
Potentially breaking changes: 8
Affected consumers (4):
  api-gateway                  direct     confidence=contractual  compat=incompatible owner=platform-foundations
  frontend                     transitive confidence=inferred     compat=unknown      owner=frontend-team
  orders-service               direct     confidence=contractual  compat=incompatible owner=commerce-team
  pacto-demo                   transitive confidence=inferred     compat=unknown      owner=platform-foundations
Release gate: unverified
unverified: the change is BREAKING and 4 consumers depend on payments-service, but none is shown to be both incompatible and running. Review each consumer, and add a source that knows where services run (for example --target-state) to tell deployed impact apart

Four consumers. confidence=contractual means the consumer declared this dependency in its own contract; confidence=inferred means it was reached transitively. compat=incompatible is a second judgement: 2.0.1 falls outside the consumer's declared version range. The release gate is unverified and the command exits 2. The bundles say nothing about where anything runs, so Pacto cannot show a consumer is hit and does not call the change safe either.

Contracts find only the consumers that declare the dependency. Add observed traffic:

pacto impact examples/demo/bundles/payments-service/v1.2.1 examples/demo/bundles/payments-service/v2.0.1 --local examples/demo/bundles --traces examples/demo/traces.json
Impact: payments-service 1.2.1 -> 2.0.1
Classification: BREAKING
Breaking changes: 11
Potentially breaking changes: 8
Affected consumers (5):
  api-gateway                  direct     confidence=contractual  compat=incompatible owner=platform-foundations
  audit-log                    direct     confidence=observed     compat=unknown      owner=platform-foundations-security
  frontend                     transitive confidence=inferred     compat=unknown      owner=frontend-team
  orders-service               direct     confidence=corroborated compat=incompatible owner=commerce-team
  pacto-demo                   transitive confidence=inferred     compat=unknown      owner=platform-foundations
Release gate: unverified
unverified: the change is BREAKING and 5 consumers depend on payments-service, but none is shown to be both incompatible and running. Review each consumer, and add a source that knows where services run (for example --target-state) to tell deployed impact apart

Five now. audit-log calls payments-service in production and declares nothing, so only observed traffic finds it. It arrives as confidence=observed. orders-service, which both declared the dependency and was seen using it, is upgraded to confidence=corroborated.

Then ask where the change actually lands:

pacto impact examples/demo/bundles/payments-service/v1.2.1 examples/demo/bundles/payments-service/v2.0.1 --local examples/demo/bundles --traces examples/demo/traces.json --target-state examples/demo/fleet-targets.yaml
Impact: payments-service 1.2.1 -> 2.0.1
Classification: BREAKING
Breaking changes: 11
Potentially breaking changes: 8
Affected consumers (5):
  api-gateway                  direct     confidence=contractual  compat=incompatible owner=platform-foundations
  audit-log                    direct     confidence=observed     compat=unknown      owner=platform-foundations-security
  frontend                     transitive confidence=inferred     compat=unknown      owner=frontend-team
  orders-service               direct     confidence=corroborated compat=incompatible owner=commerce-team
  pacto-demo                   transitive confidence=inferred     compat=unknown      owner=platform-foundations
Active targets (2): [production-eu/kubernetes-workload/commerce%2Forders-service production-eu/kubernetes-workload/payments%2Fpayments-service]
Release gate: blocked
breaking changes affect active consumers, which run on known targets and do not accept payments-service 2.0.1: orders-service

The consumer list is identical. The active targets are new, and the gate turns from unverified to blocked. Two deployed targets sit in its path: orders-service, one of the five consumers, and payments-service itself. The command exits 1. Story 3 refused a change for what it is. This command refuses it for where it lands.

Story 5: Compare declared and observed dependencies

Reconcile the edges contracts declare against the edges something observed.

pacto fleet reconcile --local examples/demo/bundles --traces examples/demo/traces.json
Reconciliation: 1 matched, 50 declared-not-observed, 1 observed-not-declared
  [declared-not-observed] analytics-pipeline -> event-bus (count=0)
  [declared-not-observed] analytics-pipeline -> postgresql (count=0)
  [declared-not-observed] api-gateway -> auth-service (count=0)
  [declared-not-observed] api-gateway -> orders-service (count=0)
  [declared-not-observed] api-gateway -> payments-service (count=0)
  [observed-not-declared] audit-log -> payments-service (count=1)
  [declared-not-observed] auth-service -> redis (count=0)
  [declared-not-observed] campaign-service -> email-provider (count=0)
  [declared-not-observed] campaign-service -> recommendations (count=0)
  [declared-not-observed] carrier-gateway -> parcel-carrier (count=0)
  [declared-not-observed] catalog-service -> postgresql (count=0)
  [declared-not-observed] experiment-service -> redis (count=0)
  [declared-not-observed] feature-store -> redis (count=0)
  [declared-not-observed] fraud-service -> redis (count=0)
  [declared-not-observed] frontend -> api-gateway (count=0)
  [declared-not-observed] fulfillment-service -> event-bus (count=0)
  [declared-not-observed] fulfillment-service -> inventory-service (count=0)
  [declared-not-observed] fulfillment-service -> shipping-rates (count=0)
  [declared-not-observed] inventory-service -> event-bus (count=0)
  [declared-not-observed] inventory-service -> postgresql (count=0)
  [declared-not-observed] loyalty-service -> event-bus (count=0)
  [declared-not-observed] loyalty-service -> postgresql (count=0)
  [declared-not-observed] notification-worker -> email-provider (count=0)
  [declared-not-observed] orders-service -> notification-worker (count=0)
  [matched] orders-service -> payments-service (count=1)
  [declared-not-observed] orders-service -> postgresql (count=0)
  [declared-not-observed] pacto-demo -> frontend (count=0)
  [declared-not-observed] payments-service -> fraud-service (count=0)
  [declared-not-observed] payments-service -> postgresql (count=0)
  [declared-not-observed] payments-service -> stripe-api (count=0)
  [declared-not-observed] pricing-service -> catalog-service (count=0)
  [declared-not-observed] pricing-service -> inventory-service (count=0)
  [declared-not-observed] pricing-service -> redis (count=0)
  [declared-not-observed] recommendations -> catalog-service (count=0)
  [declared-not-observed] recommendations -> experiment-service (count=0)
  [declared-not-observed] recommendations -> feature-store (count=0)
  [declared-not-observed] reporting-api -> analytics-pipeline (count=0)
  [declared-not-observed] reporting-api -> auth-service (count=0)
  [declared-not-observed] returns-service -> shipping-rates (count=0)
  [declared-not-observed] search-indexer -> catalog-service (count=0)
  [declared-not-observed] search-indexer -> inventory-service (count=0)
  [declared-not-observed] shipping-rates -> carrier-gateway (count=0)
  [declared-not-observed] storefront-web -> auth-service (count=0)
  [declared-not-observed] storefront-web -> catalog-service (count=0)
  [declared-not-observed] storefront-web -> inventory-service (count=0)
  [declared-not-observed] support-assistant -> model (count=0)
  [declared-not-observed] support-assistant -> returns-service (count=0)
  [declared-not-observed] support-assistant -> ticketing-service (count=0)
  [declared-not-observed] ticketing-service -> auth-service (count=0)
  [declared-not-observed] ticketing-service -> fulfillment-service (count=0)
  [declared-not-observed] ticketing-service -> postgresql (count=0)
  [declared-not-observed] warehouse-scanner -> inventory-service (count=0)

Three verdicts. Reconciling means comparing each declared edge with what was observed. observed-not-declared is the shadow dependency from story 4, stated as a reconciliation result. declared-not-observed is the opposite risk: an edge in the diagram that no traffic has taken.

Story 6: Give an agent read-only tools

Serve a contract as MCP tools, and check what the default withholds.

pacto mcp turns the read-only operations of a bundle's OpenAPI interface into MCP tools. Point a human and an agent at one process:

pacto dashboard examples/demo/bundles --port 8899
pacto mcp examples/demo/pacto-dashboard --base-url http://127.0.0.1:8899

Then open http://127.0.0.1:8899/#/fleet, and connect your MCP client to the second command. The dashboard is itself a Pacto bundle with a real OpenAPI contract, so the agent's tools are generated from the contract of the very server the human is looking at. The server also registers Pacto's own authoring tools: pacto_create and pacto_edit write contract files to disk.

Serve the demo's payments service, and Pacto drops its five mutating operations, every POST, PUT, PATCH and DELETE, with a warning on stderr:

pacto mcp examples/demo/bundles/payments-service/v2.1.0 --base-url http://127.0.0.1:1

The base URL is a dead address on purpose: Pacto registers and lists the tools and calls none of them.

Add --allow-writes and both the warning and the restriction disappear. --allow-writes governs the interface tools only. To withhold the authoring tools, register this server only where you want contracts written. See MCP integration for the full wiring.

Story 7: Check what a provider promises

Check behavior, retirement schedules and environment values.

pacto validate examples/demo/bundles/storefront-web/v1.2.0
examples/demo/bundles/storefront-web/v1.2.0 is invalid
PASS load
SKIP lock         no pacto.lock
PASS structural
PASS cross-field
PASS policy
FAIL environments
  examples/demo/bundles/storefront-web/v1.2.0/environments/production.yaml:5:3: ERROR [ENVIRONMENT_VALUES_INVALID] environments[1].file: environment production: configuration chart at /chart/ingress: missing property 'host'
  WARN  [ENVIRONMENT_VALUES_UNVERIFIED] environments[2].file: environment preview: cannot check the values for configuration platform: its schema comes from the contract oci://ghcr.io/trianalab/pacto/platform-app-config, and Pacto checks values only against a schema file in this bundle
SKIP guarantees   no dependency declares requires
SKIP readiness    not requested (--readiness)
SKIP closure      not requested (--closure)
Next: fix environments[1].file at examples/demo/bundles/storefront-web/v1.2.0/environments/production.yaml:5:3 [ENVIRONMENT_VALUES_INVALID], then run pacto validate examples/demo/bundles/storefront-web/v1.2.0
validation failed with 1 error(s)

Each stage has its own line, and Next names the first fix: the new chart schema requires an ingress host that production leaves out. preview sets values for platform, whose schema is in another bundle, so Pacto could not check them.

pacto diff examples/demo/bundles/catalog-service/v1.1.0 examples/demo/bundles/catalog-service/v1.2.0
Classification: BREAKING
Required bump: major
Changes (3):
  [NON_BREAKING] pactoVersion (modified): pactoVersion modified [2.1 -> 2.2]
  [NON_BREAKING] service.version (modified): service.version modified [1.1.0 -> 1.2.0]
  [BREAKING] openapi.paths[/products].methods[GET].responses[200].content.application/json.schema.items.properties.status.enum[preorder] (added): openapi.paths[/products].methods[GET].responses[200].content.application/json.schema.items.properties.status.enum preorder added (in #/components/schemas/ProductStatus) [+ preorder]
breaking changes detected
pacto diff examples/demo/bundles/shipping-rates/v1.1.0 examples/demo/bundles/shipping-rates/v2.0.0
Classification: BREAKING
Required bump: major
Changes (2):
  [NON_BREAKING] service.version (modified): service.version modified [1.1.0 -> 2.0.0]
  [BREAKING] interfaces.compatibilityEpoch (modified): interface rates: compatibilityEpoch raised from 0 to 1: its behavior changed incompatibly, so every consumer must review the change [rates: 0 -> rates: 1]
breaking changes detected

Every product response reaches ProductStatus through a $ref, so one new enum value is breaking in a minor release. The rates document did not change, and the raised compatibilityEpoch says its meaning did.

pacto impact examples/demo/bundles/inventory-service/v2.1.0 examples/demo/bundles/inventory-service/v3.0.0 --local examples/demo/bundles
Impact: inventory-service 2.1.0 -> 3.0.0
Classification: BREAKING
Breaking changes: 1
Potentially breaking changes: 0
Affected consumers (7):
  fulfillment-service          direct     confidence=contractual  compat=incompatible owner=logistics-team
    refused: fulfillment-service requires stock-events dedupeWindow >= 5m from inventory-service, which offers nothing
  pricing-service              direct     confidence=contractual  compat=incompatible owner=catalog-team
    refused: pricing-service requires stock-v2 timeout <= 2s from inventory-service, which offers 5s
  search-indexer               direct     confidence=contractual  compat=incompatible owner=catalog-team
  storefront-web               direct     confidence=contractual  compat=incompatible owner=frontend-team
  support-assistant            transitive confidence=inferred     compat=unknown      owner=support-team
  ticketing-service            transitive confidence=inferred     compat=unknown      owner=support-team
  warehouse-scanner            direct     confidence=contractual  compat=incompatible owner=logistics-team
Release gate: unverified
unverified: the change is BREAKING and 7 consumers depend on inventory-service, but none is shown to be both incompatible and running. inventory-service 3.0.0 does not meet a guarantee required by fulfillment-service, pricing-service. Review each consumer, and add a source that knows where services run (for example --target-state) to tell deployed impact apart

3.0.0 drops the dedupe window fulfillment-service requires, and its 5s timeout already refuses pricing-service, which requires 2s.

What's next

The Quickstart takes an empty directory to a validated contract and a caught breaking change in about five minutes. The contract reference is the complete list of sections, and pacto diff is the complete table of what counts as a breaking change.