A completion contract is a machine-readable definition of "done" for one operation. It names the intent, the subject, the action, the correlation keys, the positive and negative postconditions, the timing policy, and the result on timeout.
Contracts are immutable, versioned, and hashable. Every receipt records the contract id, version, and hash. A contract is data. Code interprets it and holds no provider rule.
Where does a contract come from?
Provely compiles it from the task. You pass the agent prompt, the context and the tools. The compiler returns the steps, the evidence, the correlation keys and the negative postconditions. You confirm the result before it runs.
A part of the task that no contract can check is marked REQUIRES_REVIEW, and it is never dropped in silence. Read intent compilation.
What does a contract contain?
| Member | Purpose | Example |
|---|---|---|
intent | What the user asked for, and the input schema. | Refund the charge to the customer. |
subject | The real-world thing the contract talks about. | stripe.refund identified by $action.result.id. |
action | The effect class and the idempotency rule. | money.refund with Idempotency-Key: $operation.id. |
correlation | How evidence ties to this exact operation. | Match the refund id from the action response. |
evidence_policy | The channels and the minimum evidence level. | Readback at E2, event at E3, independent channel required. |
completion.all_of | The positive postconditions. Every one must hold. | status eq succeeded, amount eq $input.amount_cents. |
negative_conditions.any_of | The false-success blockers. One gives CONTRADICTED. | More than one refund for this operation. |
verdict_map | Observed state to verdict, before completion. | pending gives PENDING. failed gives FAILED. |
timing | Poll interval, backoff, deadline, stale-read window. | Poll every 5 s, stop after 7 days. |
on_timeout | The verdict when the deadline passes. VERIFIED is never allowed. | UNVERIFIABLE, escalated to a person. |
valid_for | The provider API versions the semantics are proven for. | >=2025-03-31. |
overlays | Version differences as a merge patch. One contract, no copies. | The 2026-08-26 overlay adds requires_action. |
What does the Stripe refund contract look like?
schema_version: '1.0.0'
id: stripe.refund.succeeded
version: 1.4.2
service: stripe
completion_level: succeeded
intent:
statement: Refund the charge to the customer.
subject:
type: stripe.refund
identity: { refund_id: $action.result.id }
action:
canonical_effect: money.refund
idempotency: { strategy: idempotency_key, key: $operation.id, header: Idempotency-Key }
correlation:
strategies:
- { name: resource_id, assurance: strong, required: true,
keys: [{ name: refund_id, source: $action.result.id }] }
evidence_policy:
minimum_evidence_level: E2
require_independent_channel: true
channels:
- { name: provider_readback, evidence_level: E2, independence: provider_readback,
verifier: { name: http, version: '^1' } }
completion:
all_of:
- { id: refund_status_succeeded, path: $observed.provider_readback.status,
operator: eq, expected: succeeded, evidence: [provider_readback] }
- { id: refund_amount_matches, path: $observed.provider_readback.amount,
operator: eq, expected: $input.amount_cents, evidence: [provider_readback] }
negative_conditions:
any_of:
- { id: duplicate_refund_present, class: duplicate_side_effect, reason_code: duplicate_refund,
path: $observed.provider_readback.operation_refund_count, operator: gt, expected: 1,
evidence: [provider_readback] }
timing: { poll_interval_ms: 5000, timeout_ms: 604800000, stale_read_window_ms: 20000 }
on_timeout: { result: UNVERIFIABLE, reason_code: evidence_unavailable_before_timeout,
escalate_to_human_review: true }
valid_for: { provider_api_versions: ['>=2025-03-31'] }The Stripe contract page renders every condition from the signed manifest.
Why are completion levels separate contracts?
Because a service makes more than one promise. Resend accepts, sends, and delivers an email as three events. Each level is one contract, so an agent cannot upgrade the promise it made.
resend.email.accepted,resend.email.sent,resend.email.delivered, andresend.email.bouncedare four contracts.shopify.refund.createdandshopify.refund.financially_completedare two contracts, because a Refund object does not prove money moved.stripe.refund.createdandstripe.refund.succeededare two contracts, becausependingis notsucceeded.
Which rules does the schema enforce?
- Every condition names an evidence channel that the contract declares.
- A channel cannot claim a level above its path. A reading of the action response is E1 at most.
- A weak correlation strategy states a time window. An Official contract needs one strong strategy.
- A PENDING result on timeout must escalate to a person. An operation always ends.
verdict_mapandon_timeoutcan never give VERIFIED. Only the completion block can.- An overlay adds a condition. It cannot remove one, change the identity, or weaken the evidence policy.
- The document rejects an unknown member with a clear error.
The published schema is completion-contract.schema.json, JSON Schema draft 2020-12, in the @provely/core package.
Source: Provely core schemas · retrieved 2026-09-05
How does composition work?
A contract can use other contracts as sub-contracts. The runtime combines their verdicts with frozen precedence rules. One CONTRADICTED component contradicts the whole. One PENDING component keeps the whole PENDING. VERIFIED needs every component VERIFIED.
Can I write a contract by hand?
Yes. The schema and the loader are public. The Integration Compiler writes the Official contracts, because it records provenance and runs the conformance matrix.
What happens when the provider ships a new API version?
The drift monitor fingerprints the schema and the docs, classifies the change, and recompiles only the affected contracts. A version outside valid_for gives UNVERIFIABLE with the reason version_unsupported. The runtime never falls back to "latest".
How is the contract hash computed?
The runtime canonicalizes the document as RFC 8785 JSON without the hash member and takes the SHA-256. TypeScript, Python, and Rust produce the same bytes. The cross-language fixtures prove it.