Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Private working notes — never publish
# Private working notes. Never publish.
PRIVATE-*
*.private.md
notes/
Expand All @@ -8,3 +8,8 @@ scratch/
*.pyc
__pycache__/
.DS_Store

# Local-only Claude Code context. Never publish.
CLAUDE.md
CLAUDE.local.md
.claude/
61 changes: 50 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ closes that gap:
- **Agree** — a co-signed **Verifiable Task Contract (VTC)** binds parties
(DIDs), scope (hash-committed TaskSpec), price, verification method, and
liability; funds lock in escrow.
- **Complete** — optimistic settlement: pay at delivery, with a fraud-proof
challenge window; valid challengers are paid from the slashed bond.
- **Complete** — settlement gated on verification, with a fraud-proof
challenge window and challengers paid from the slashed bond.
Verification is graded: re-execution → TEE attestation (RATS/EAT) →
zkML proof → staked jury.
zkML proof → staked jury. (The -00 makes release optimistic by default.
Two audit rounds found that this does not close economically, and -01
changes the default. See Status below.)
- **Trust** — settlement emits co-signed **Work Attestations**: reputation
as the exhaust of settlement — unforgeable without funding real, bonded
contracts. Contracts compose into **Merkle contract trees** with
Expand All @@ -31,9 +33,33 @@ closes that gap:
· [plain text](draft/draft-laxsharma-pact-00.txt)
· [XML source](draft/draft-laxsharma-pact-00.xml)
- This is a **-00 strawman, published for demolition.** Issues and PRs
welcome especially "here is where this breaks." Feedback is collected
welcome, especially "here is where this breaks." Feedback is collected
for the next revision in the
[-01 changelog issue](https://github.com/pact-spec/spec/issues).
[-01 changelog issue](https://github.com/pact-spec/spec/issues/1).

### Known defects in -00, and what -01 changes

External review and two adversarial audit rounds found that the -00's
settlement economics do not close. Everything is logged in
[issue #1](https://github.com/pact-spec/spec/issues/1), with dispositions.
The three that matter most if you are reading the draft today:

1. **A defrauded buyer recovers nothing from the bond.** Section 4.3
directs the slashed bond to the challenger and then to a neutral sink
"rather than to any party to the dispute", and the buyer is a party to
the dispute. The bond is a fine, not collateral.
2. **Optimistic release exceeds the bond, so defection dominates.**
Honest performance requires roughly `q * ((P - E) + B) >= C`. On the
worked example's own numbers a 10 percent bond needs a 91 percent
detection rate, which nothing in -00 supplies.
3. **Challenger reimbursement is capped by the bond** while re-execution
verification costs about what execution costs, so the reimbursement
requirement in 4.3 is unsatisfiable in the common case.

-01 is targeted for mid-September 2026 and reworks the settlement core,
adds a Delivery object and a Verifier role, and corrects the x402, A2A
and AP2 bindings. The repository is being corrected ahead of it where a
fix does not depend on those design decisions.
- Not endorsed by the IETF; an individual submission with no formal
standing in the standards process.

Expand All @@ -53,19 +79,31 @@ closes that gap:

- `cfb.json` / `vtc.json` `spec_hash` = SHA-256 over the
JCS-canonicalized (RFC 8785) `taskspec.json`
- `criteria_hash` = SHA-256 over `acceptance-tests.txt`
- `criteria_hash`, and `taskspec.acceptance.harness_hash`, = SHA-256 over
the JCS-canonicalized manifest of `examples/acceptance-harness/`, which
maps each file's relative path to the SHA-256 of its bytes
- `bid.json` `commitment` = SHA-256 over the JCS-canonicalized reveal in
`bid-reveal.json`
- `attestation.json` `vtc_hash` = SHA-256 over the VTC minus its
`signatures` member

The validator also checks the rules JSON Schema cannot express (parties
are distinct, one signature per named party, protected headers carry
`alg`/`kid`/`typ` with an allowed algorithm) and runs negative vectors
that must be rejected.

```
pip install jsonschema referencing
python3 tools/validate.py
```

(Signature values are illustrative placeholders; producing real JWS
signatures requires party keys.)
Two honest caveats. Signature values are illustrative placeholders, since
producing real JWS signatures requires party keys. And `jcs()` in
`tools/validate.py` is a restricted RFC 8785 implementation that is
correct for the value types these examples use but is not a conforming
general one, so a green run evidences self-consistency of these examples
rather than canonicalization interoperability with another
implementation.

## Building the draft

Expand All @@ -76,9 +114,10 @@ xml2rfc --text --html draft/draft-laxsharma-pact-00.xml

## Relationship to other work

PACT composes A2A, x402 (as a proposed `pact-escrow` payment scheme),
AP2, OAuth token exchange (RFC 8693), RATS/EAT (RFC 9334/9711), and JCS
(RFC 8785). It differs from marketplace-mediated escrow (VCAP), transport
PACT composes A2A, x402 (as a proposed `pact-escrow` release-policy
profile over the merged `auth-capture` scheme, which the -00 text
inaccurately calls a payment scheme), AP2, OAuth token exchange
(RFC 8693), RATS/EAT (RFC 9334/9711), and JCS (RFC 8785). It differs from marketplace-mediated escrow (VCAP), transport
negotiation (AGTP), and passport formats (ATEP, ERC-8004) — and cites and
positions against each in Section 1.2 of the draft. Lineage: the Contract
Net Protocol (Smith, 1980), finally runnable among untrusting parties.
Expand Down
28 changes: 28 additions & 0 deletions examples/acceptance-harness/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Acceptance instrument (worked example)

This directory is the acceptance instrument committed by
`verification.criteria_hash` in `cfb.json` and `vtc.json`.

`criteria_hash` is SHA-256 over the JCS-canonicalized manifest of this
directory: a JSON object mapping each file's path, relative to this
directory, to the SHA-256 of its bytes. `tools/validate.py` recomputes it
on every run and CI fails if it drifts.

Committing a manifest of per-file digests rather than a single archive
digest means the commitment is reproducible without depending on tar or
zip metadata (timestamps, ordering, permissions), which are not stable
across producers.

## Why this replaced a text file

Until August 2026 this instrument was a single 85-byte file whose entire
content was a sentence describing the tests. The commitment therefore
covered a description of the acceptance criteria rather than the criteria
themselves, so a party hosting the real harness could swap its bytes
after signature and manufacture a valid fraud proof. See
https://github.com/pact-spec/spec/issues/1 entry 6.

The general rule this example now demonstrates: a hash commitment covers
exactly the octets hashed. Any URI inside hash-committed content whose
bytes are consumed during bidding, execution, or verification needs its
own sibling hash member.
87 changes: 87 additions & 0 deletions examples/acceptance-harness/test_acceptance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
"""Acceptance instrument for the worked example (T0-reexec).

This is the executable instrument committed by `verification.criteria_hash`
in cfb.json and vtc.json. It is deliberately real code rather than a
description of code: the commitment must cover the bytes a verifier will
run, not a sentence about them.

Thresholds are not hardcoded here. They are read from the TaskSpec that
`task.spec_hash` commits to, so that the instrument and the thresholds
cannot drift apart. Usage:

pytest test_acceptance.py --taskspec ../taskspec.json --delivery out.csv
"""

import csv
import json
import pathlib

import pytest

ISO_3166_ALPHA2_LEN = 2


def pytest_addoption(parser):
parser.addoption("--taskspec", required=True, help="Path to the committed TaskSpec")
parser.addoption("--delivery", required=True, help="Path to the delivered artifact")


@pytest.fixture(scope="session")
def thresholds(request):
spec = json.loads(pathlib.Path(request.config.getoption("--taskspec")).read_text())
return spec["acceptance"]["thresholds"]


@pytest.fixture(scope="session")
def rows(request):
path = pathlib.Path(request.config.getoption("--delivery"))
with path.open(newline="", encoding="utf-8") as fh:
return list(csv.DictReader(fh))


def test_delivery_is_non_empty(rows):
assert rows, "delivered artifact contains no data rows"


def test_duplicate_rate_within_threshold(rows, thresholds):
keys = [(r.get("customer_id") or "").strip().lower() for r in rows]
populated = [k for k in keys if k]
assert populated, "no populated customer_id values in delivery"
duplicates = len(populated) - len(set(populated))
dup_rate = duplicates / len(populated)
assert dup_rate <= thresholds["dup_rate_max"], (
f"duplicate rate {dup_rate:.6f} exceeds "
f"threshold {thresholds['dup_rate_max']}"
)


def test_country_fields_are_iso_3166_alpha2(rows, thresholds):
countries = [(r.get("country") or "").strip() for r in rows]
valid = [
c for c in countries
if len(c) == ISO_3166_ALPHA2_LEN and c.isalpha() and c.isupper()
]
rate = len(valid) / len(countries)
assert rate >= thresholds["schema_valid_rate"], (
f"ISO-3166 alpha-2 conformance {rate:.6f} is below "
f"required {thresholds['schema_valid_rate']}"
)


def test_no_row_lost_relative_to_declared_input(rows, request):
"""Completeness check.

A threshold set that a degenerate delivery can satisfy is not an
acceptance instrument. Deduplication may only remove duplicates, so
the output row count has a floor: the distinct-key count of the
input. The declared input size is committed in the TaskSpec.
"""
spec = json.loads(pathlib.Path(request.config.getoption("--taskspec")).read_text())
declared = spec.get("inputs", {}).get("size_hint", {}).get("rows")
if declared is None:
pytest.skip("TaskSpec declares no input size_hint")
floor = declared * 0.5
assert len(rows) >= floor, (
f"delivery has {len(rows)} rows against a declared input of "
f"{declared}; deduplication cannot account for a reduction this large"
)
1 change: 0 additions & 1 deletion examples/acceptance-tests.txt

This file was deleted.

10 changes: 4 additions & 6 deletions examples/attestation.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"pact": "0.1",
"type": "WorkAttestation",
"vtc_id": "vtc_9f2c11",
"vtc_hash": "sha256:2a145c996a34a43661d93e9bf2fca24c02a9d1376493e4a165aee2688be3564f",
"vtc_hash": "sha256:3184cbd50cb56304a36a7b3f247148fab5bbeea0e4ff091b48becd1dc8e1b705",
"outcome": "settled",
"amount_settled": "180.00",
"currency": "USDC",
Expand All @@ -11,14 +11,12 @@
"children_merkle_root": null,
"signatures": [
{
"kid": "did:web:buyer.example:agents:procure-1#k1",
"protected": "eyJhbGciOiJFUzI1NiJ9",
"protected": "eyJhbGciOiJFUzI1NiIsImtpZCI6ImRpZDp3ZWI6YnV5ZXIuZXhhbXBsZTphZ2VudHM6cHJvY3VyZS0xI2sxIiwidHlwIjoiYXBwbGljYXRpb24vcGFjdC1hdHRlc3RhdGlvbitqc29uIn0",
"signature": "ILLUSTRATIVE-NOT-A-REAL-SIGNATURE"
},
{
"kid": "did:web:dataforge.example:agents:etl-3#k1",
"protected": "eyJhbGciOiJFUzI1NiJ9",
"protected": "eyJhbGciOiJFUzI1NiIsImtpZCI6ImRpZDp3ZWI6ZGF0YWZvcmdlLmV4YW1wbGU6YWdlbnRzOmV0bC0zI2sxIiwidHlwIjoiYXBwbGljYXRpb24vcGFjdC1hdHRlc3RhdGlvbitqc29uIn0",
"signature": "ILLUSTRATIVE-NOT-A-REAL-SIGNATURE"
}
]
}
}
5 changes: 2 additions & 3 deletions examples/bid.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@
"commitment": "sha256:47a6350b62e49313d14bdaab8dde94505991675a5e729227855b4c795cbbdee9",
"signatures": [
{
"kid": "did:web:dataforge.example:agents:etl-3#k1",
"protected": "eyJhbGciOiJFUzI1NiJ9",
"protected": "eyJhbGciOiJFUzI1NiIsImtpZCI6ImRpZDp3ZWI6ZGF0YWZvcmdlLmV4YW1wbGU6YWdlbnRzOmV0bC0zI2sxIiwidHlwIjoiYXBwbGljYXRpb24vcGFjdC1iaWQranNvbiJ9",
"signature": "ILLUSTRATIVE-NOT-A-REAL-SIGNATURE"
}
]
}
}
24 changes: 17 additions & 7 deletions examples/cfb.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,27 @@
"id": "cfb_7d41a2",
"buyer": "did:web:buyer.example:agents:procure-1",
"task": {
"spec_hash": "sha256:20304805d7f4698ccda0b61bd59a3d0db5fd348c01587c7e4e748dd5d7a7949c",
"spec_hash": "sha256:bb0e87ce522479b7c2f7bcfa26df7ecd7ff67aeb8b415bbd70c22d97c47adf35",
"spec_uri": "https://buyer.example/specs/taskspec.json",
"deadline": "2026-08-01T00:00:00Z"
},
"max_price": { "amount": "220.00", "currency": "USDC" },
"verification": { "tier": "T0-reexec", "criteria_hash": "sha256:5446fc206aaee6a0c94199cee62df8ef5cb607d2ec043a8cb3f84d7a329fddd6" },
"max_price": {
"amount": "220.00",
"currency": "USDC"
},
"verification": {
"tier": "T0-reexec",
"criteria_hash": "sha256:d9205d4f2922afd55c0a2dc4ab00d8ee5a512343430bcf5e9abf0c76d66c69f7"
},
"bid_deadline": "2026-07-25T12:00:00Z",
"challenge": { "window_seconds": 3600, "max_dispute_seconds": 86400 },
"challenge": {
"window_seconds": 3600,
"max_dispute_seconds": 86400
},
"signatures": [
{ "kid": "did:web:buyer.example:agents:procure-1#k1",
"protected": "eyJhbGciOiJFUzI1NiJ9",
"signature": "ILLUSTRATIVE-NOT-A-REAL-SIGNATURE" }
{
"protected": "eyJhbGciOiJFUzI1NiIsImtpZCI6ImRpZDp3ZWI6YnV5ZXIuZXhhbXBsZTphZ2VudHM6cHJvY3VyZS0xI2sxIiwidHlwIjoiYXBwbGljYXRpb24vcGFjdC1jZmIranNvbiJ9",
"signature": "ILLUSTRATIVE-NOT-A-REAL-SIGNATURE"
}
]
}
15 changes: 12 additions & 3 deletions examples/taskspec.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,27 @@
"inputs": {
"schema_uri": "https://buyer.example/specs/customers.schema.json",
"sample_uri": "https://buyer.example/specs/sample-10k.csv",
"size_hint": { "rows": 2100000, "bytes": 480000000 }
"size_hint": {
"rows": 2100000,
"bytes": 480000000
}
},
"deliverable": {
"format": "csv",
"schema_uri": "https://buyer.example/specs/output.schema.json"
},
"acceptance": {
"harness_uri": "https://buyer.example/specs/acceptance-tests.tar",
"thresholds": { "dup_rate_max": 0.001, "schema_valid_rate": 1.0 }
"thresholds": {
"dup_rate_max": 0.001,
"schema_valid_rate": 1.0
},
"harness_hash": "sha256:d9205d4f2922afd55c0a2dc4ab00d8ee5a512343430bcf5e9abf0c76d66c69f7"
},
"constraints": {
"tools_prohibited": ["external-APIs"],
"tools_prohibited": [
"external-APIs"
],
"confidential": false
}
}
40 changes: 27 additions & 13 deletions examples/vtc.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,36 @@
"seller": "did:web:dataforge.example:agents:etl-3"
},
"task": {
"spec_hash": "sha256:20304805d7f4698ccda0b61bd59a3d0db5fd348c01587c7e4e748dd5d7a7949c",
"spec_hash": "sha256:bb0e87ce522479b7c2f7bcfa26df7ecd7ff67aeb8b415bbd70c22d97c47adf35",
"spec_uri": "https://buyer.example/specs/taskspec.json",
"deadline": "2026-08-01T00:00:00Z"
},
"price": { "amount": "180.00", "currency": "USDC",
"settlement": "pact-escrow", "channel": "ch_88a1" },
"verification": { "tier": "T0-reexec", "criteria_hash": "sha256:5446fc206aaee6a0c94199cee62df8ef5cb607d2ec043a8cb3f84d7a329fddd6",
"arbiter": "did:web:arbiter.example" },
"liability": { "seller_bond": "18.00" },
"challenge": { "window_seconds": 3600, "max_dispute_seconds": 86400 },
"price": {
"amount": "180.00",
"currency": "USDC",
"settlement": "pact-escrow",
"channel": "ch_88a1"
},
"verification": {
"tier": "T0-reexec",
"criteria_hash": "sha256:d9205d4f2922afd55c0a2dc4ab00d8ee5a512343430bcf5e9abf0c76d66c69f7",
"arbiter": "did:web:arbiter.example"
},
"liability": {
"seller_bond": "18.00"
},
"challenge": {
"window_seconds": 3600,
"max_dispute_seconds": 86400
},
"signatures": [
{ "kid": "did:web:buyer.example:agents:procure-1#k1",
"protected": "eyJhbGciOiJFUzI1NiJ9",
"signature": "ILLUSTRATIVE-NOT-A-REAL-SIGNATURE" },
{ "kid": "did:web:dataforge.example:agents:etl-3#k1",
"protected": "eyJhbGciOiJFUzI1NiJ9",
"signature": "ILLUSTRATIVE-NOT-A-REAL-SIGNATURE" }
{
"protected": "eyJhbGciOiJFUzI1NiIsImtpZCI6ImRpZDp3ZWI6YnV5ZXIuZXhhbXBsZTphZ2VudHM6cHJvY3VyZS0xI2sxIiwidHlwIjoiYXBwbGljYXRpb24vcGFjdC1jb250cmFjdCtqc29uIn0",
"signature": "ILLUSTRATIVE-NOT-A-REAL-SIGNATURE"
},
{
"protected": "eyJhbGciOiJFUzI1NiIsImtpZCI6ImRpZDp3ZWI6ZGF0YWZvcmdlLmV4YW1wbGU6YWdlbnRzOmV0bC0zI2sxIiwidHlwIjoiYXBwbGljYXRpb24vcGFjdC1jb250cmFjdCtqc29uIn0",
"signature": "ILLUSTRATIVE-NOT-A-REAL-SIGNATURE"
}
]
}
Loading
Loading