agent-receipt-verify 0.2.6 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. package/bench/methodefout.mjs +204 -0
  2. package/bin/cli.mjs +6 -0
  3. package/package.json +5 -3
  4. package/src/attempt-identity.mjs +278 -0
  5. package/src/x402-conformance-check.mjs +9 -1
  6. package/vectors/attempt-identity/all-paths-agree.json +126 -0
  7. package/vectors/attempt-identity/changed-amount-announced.json +86 -0
  8. package/vectors/attempt-identity/changed-amount-no-event.json +82 -0
  9. package/vectors/attempt-identity/changed-amount-silent-collapse.json +81 -0
  10. package/vectors/attempt-identity/client-supplied-key-unverified.json +29 -0
  11. package/vectors/attempt-identity/countersignature-matches.json +28 -0
  12. package/vectors/attempt-identity/countersigned-despite-mismatch.json +29 -0
  13. package/vectors/attempt-identity/derive-once-honoured.json +122 -0
  14. package/vectors/attempt-identity/idempotency-key-bound.json +28 -0
  15. package/vectors/attempt-identity/key-order-changes-identity.json +63 -0
  16. package/vectors/attempt-identity/key-order-stable-identity.json +62 -0
  17. package/vectors/attempt-identity/nothing-recorded-not-measured.json +21 -0
  18. package/vectors/attempt-identity/outcome-folded-into-identity.json +35 -0
  19. package/vectors/attempt-identity/outcome-kept-out-of-identity.json +34 -0
  20. package/vectors/attempt-identity/paths-disagree.json +81 -0
  21. package/vectors/attempt-identity/reference-canonicalized-first.json +28 -0
  22. package/vectors/attempt-identity/reference-over-transmitted.json +26 -0
  23. package/vectors/attempt-identity/settle-rederives-downstream.json +103 -0
  24. package/vectors/attempt-identity/settled-despite-mismatch.json +29 -0
@@ -0,0 +1,63 @@
1
+ {
2
+ "schema_version": "3.0.0",
3
+ "verifies": "attempt-identity",
4
+ "requires_chain_read": false,
5
+ "provenance": [
6
+ "x402 specification v2 section 5.5, Settlement and Refusal Identity Invariant, as proposed in x402-foundation/x402 pull request 3776 on branch spec/settlement-refusal-identity-invariant and not on main",
7
+ "the gap these cover was named by ruiruii in erc-8004/erc-8004-contracts#99 on 18 October 2026: each of the four evidence layers depends on an attempt identity that none of them defines",
8
+ "section 5.5 closes by referring to \"the normative conformance tests in the specification repository for the required negative cases\"; at the time of writing those did not exist"
9
+ ],
10
+ "id": "attempt-identity-key-order-changes-identity",
11
+ "clause": "own-identity-from-decoded-fields",
12
+ "encodes": "The first of the two inverse traps: an identity that moves when the key order moves, so it came from the bytes.",
13
+ "description": "Two arrivals of the same attempt, same decoded fields, different key order and whitespace, and two different identities. The clause says an attempt is the same attempt either way, and that this is exactly what makes settle and refusal agree. An implementation that hashes the raw body fails here.",
14
+ "input": {
15
+ "wire_variants": [
16
+ {
17
+ "note": "as received first",
18
+ "decoded": {
19
+ "method": "POST",
20
+ "path": "/api/paid/verify-ownership",
21
+ "query": "",
22
+ "body": {
23
+ "endpoint": "https://example.com/api/health",
24
+ "ownerAddress": "0x1111111111111111111111111111111111111111",
25
+ "method": "dns-txt"
26
+ },
27
+ "payTo": "0x2888f36548a900b9b08d584524e1f73bc563ec26",
28
+ "asset": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
29
+ "network": "eip155:8453",
30
+ "amount": "1000",
31
+ "scheme": "exact"
32
+ },
33
+ "identity": "2ed52a521f9762c3adc24c8048a4ee04a3796202a34c335585fc953e15c97196"
34
+ },
35
+ {
36
+ "note": "same fields, keys in another order",
37
+ "decoded": {
38
+ "method": "POST",
39
+ "path": "/api/paid/verify-ownership",
40
+ "query": "",
41
+ "body": {
42
+ "endpoint": "https://example.com/api/health",
43
+ "ownerAddress": "0x1111111111111111111111111111111111111111",
44
+ "method": "dns-txt"
45
+ },
46
+ "payTo": "0x2888f36548a900b9b08d584524e1f73bc563ec26",
47
+ "asset": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
48
+ "network": "eip155:8453",
49
+ "amount": "1000",
50
+ "scheme": "exact"
51
+ },
52
+ "identity": "8a48e6c49a7a94be4d657408c486532e0a141d5e1548e7fabf539afd5809f982"
53
+ }
54
+ ]
55
+ },
56
+ "expected": {
57
+ "outcome": "FAIL",
58
+ "reason_code": "IDENTITY_DERIVED_FROM_WIRE_BYTES"
59
+ },
60
+ "limits": [
61
+ "The two variants are asserted to carry the same decoded fields; a checker verifies that before judging, and reports not-measured when they differ."
62
+ ]
63
+ }
@@ -0,0 +1,62 @@
1
+ {
2
+ "schema_version": "3.0.0",
3
+ "verifies": "attempt-identity",
4
+ "requires_chain_read": false,
5
+ "provenance": [
6
+ "x402 specification v2 section 5.5, Settlement and Refusal Identity Invariant, as proposed in x402-foundation/x402 pull request 3776 on branch spec/settlement-refusal-identity-invariant and not on main",
7
+ "the gap these cover was named by ruiruii in erc-8004/erc-8004-contracts#99 on 18 October 2026: each of the four evidence layers depends on an attempt identity that none of them defines",
8
+ "section 5.5 closes by referring to \"the normative conformance tests in the specification repository for the required negative cases\"; at the time of writing those did not exist"
9
+ ],
10
+ "id": "attempt-identity-key-order-stable-identity",
11
+ "clause": "own-identity-from-decoded-fields",
12
+ "encodes": "The positive control for the same trap.",
13
+ "description": "Same decoded fields, different wire form, one identity. This is the behaviour the clause requires.",
14
+ "input": {
15
+ "wire_variants": [
16
+ {
17
+ "note": "as received first",
18
+ "decoded": {
19
+ "method": "POST",
20
+ "path": "/api/paid/verify-ownership",
21
+ "query": "",
22
+ "body": {
23
+ "endpoint": "https://example.com/api/health",
24
+ "ownerAddress": "0x1111111111111111111111111111111111111111",
25
+ "method": "dns-txt"
26
+ },
27
+ "payTo": "0x2888f36548a900b9b08d584524e1f73bc563ec26",
28
+ "asset": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
29
+ "network": "eip155:8453",
30
+ "amount": "1000",
31
+ "scheme": "exact"
32
+ },
33
+ "identity": "2ed52a521f9762c3adc24c8048a4ee04a3796202a34c335585fc953e15c97196"
34
+ },
35
+ {
36
+ "note": "same fields, keys in another order",
37
+ "decoded": {
38
+ "method": "POST",
39
+ "path": "/api/paid/verify-ownership",
40
+ "query": "",
41
+ "body": {
42
+ "endpoint": "https://example.com/api/health",
43
+ "ownerAddress": "0x1111111111111111111111111111111111111111",
44
+ "method": "dns-txt"
45
+ },
46
+ "payTo": "0x2888f36548a900b9b08d584524e1f73bc563ec26",
47
+ "asset": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
48
+ "network": "eip155:8453",
49
+ "amount": "1000",
50
+ "scheme": "exact"
51
+ },
52
+ "identity": "2ed52a521f9762c3adc24c8048a4ee04a3796202a34c335585fc953e15c97196"
53
+ }
54
+ ]
55
+ },
56
+ "expected": {
57
+ "outcome": "PASS"
58
+ },
59
+ "limits": [
60
+ "This says the identity is stable across wire forms. It does not say the right fields went into it."
61
+ ]
62
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "schema_version": "3.0.0",
3
+ "verifies": "attempt-identity",
4
+ "requires_chain_read": false,
5
+ "provenance": [
6
+ "x402 specification v2 section 5.5, Settlement and Refusal Identity Invariant, as proposed in x402-foundation/x402 pull request 3776 on branch spec/settlement-refusal-identity-invariant and not on main",
7
+ "the gap these cover was named by ruiruii in erc-8004/erc-8004-contracts#99 on 18 October 2026: each of the four evidence layers depends on an attempt identity that none of them defines",
8
+ "section 5.5 closes by referring to \"the normative conformance tests in the specification repository for the required negative cases\"; at the time of writing those did not exist"
9
+ ],
10
+ "id": "attempt-identity-nothing-recorded-not-measured",
11
+ "clause": "all",
12
+ "encodes": "The fourth outcome: a vector that carries no observations is not conformant and not failing, it is unmeasured.",
13
+ "description": "An empty observation set must not pass and must not fail. Reporting PASS here would let an implementation claim conformance by showing nothing, and reporting FAIL would blame an implementation for a record that was never collected. This is the distinction the thread settled on for all four evidence layers.",
14
+ "input": {},
15
+ "expected": {
16
+ "outcome": "NOT_MEASURED"
17
+ },
18
+ "limits": [
19
+ "This is a property of the checker, not of any implementation."
20
+ ]
21
+ }
@@ -0,0 +1,35 @@
1
+ {
2
+ "schema_version": "3.0.0",
3
+ "verifies": "attempt-identity",
4
+ "requires_chain_read": false,
5
+ "provenance": [
6
+ "x402 specification v2 section 5.5, Settlement and Refusal Identity Invariant, as proposed in x402-foundation/x402 pull request 3776 on branch spec/settlement-refusal-identity-invariant and not on main",
7
+ "the gap these cover was named by ruiruii in erc-8004/erc-8004-contracts#99 on 18 October 2026: each of the four evidence layers depends on an attempt identity that none of them defines",
8
+ "section 5.5 closes by referring to \"the normative conformance tests in the specification repository for the required negative cases\"; at the time of writing those did not exist"
9
+ ],
10
+ "id": "attempt-identity-outcome-folded-into-identity",
11
+ "clause": "outcome-not-folded-into-identity",
12
+ "encodes": "Metering data from the outcome layer absorbed into the identity, so a partial delivery becomes a different attempt.",
13
+ "description": "The same attempt delivered in full and in part resolves two identities, because the delivered measurement was folded into the derivation. The clause wants the gap between full and partial delivery to stay visible in the outcome layer; absorbing it into identity makes an incomplete delivery look like an unrelated attempt, which is how a shortfall stops being arguable.",
14
+ "input": {
15
+ "outcome_variants": [
16
+ {
17
+ "note": "full delivery",
18
+ "delivered": 100,
19
+ "identity": "2ed52a521f9762c3adc24c8048a4ee04a3796202a34c335585fc953e15c97196"
20
+ },
21
+ {
22
+ "note": "partial delivery",
23
+ "delivered": 40,
24
+ "identity": "b1b9009e0c9e7d66fc4e47a66303e8ccdddce8cc3462bc9de007abf524855bcb"
25
+ }
26
+ ]
27
+ },
28
+ "expected": {
29
+ "outcome": "FAIL",
30
+ "reason_code": "OUTCOME_FOLDED_INTO_IDENTITY"
31
+ },
32
+ "limits": [
33
+ "This shows identity moving with the outcome. It does not say which of the two identities is the right one."
34
+ ]
35
+ }
@@ -0,0 +1,34 @@
1
+ {
2
+ "schema_version": "3.0.0",
3
+ "verifies": "attempt-identity",
4
+ "requires_chain_read": false,
5
+ "provenance": [
6
+ "x402 specification v2 section 5.5, Settlement and Refusal Identity Invariant, as proposed in x402-foundation/x402 pull request 3776 on branch spec/settlement-refusal-identity-invariant and not on main",
7
+ "the gap these cover was named by ruiruii in erc-8004/erc-8004-contracts#99 on 18 October 2026: each of the four evidence layers depends on an attempt identity that none of them defines",
8
+ "section 5.5 closes by referring to \"the normative conformance tests in the specification repository for the required negative cases\"; at the time of writing those did not exist"
9
+ ],
10
+ "id": "attempt-identity-outcome-kept-out-of-identity",
11
+ "clause": "outcome-not-folded-into-identity",
12
+ "encodes": "The positive control: delivery differs, identity does not.",
13
+ "description": "Full and partial delivery of one attempt resolve one identity, so the shortfall stays a question about the outcome and not about which attempt this was.",
14
+ "input": {
15
+ "outcome_variants": [
16
+ {
17
+ "note": "full delivery",
18
+ "delivered": 100,
19
+ "identity": "2ed52a521f9762c3adc24c8048a4ee04a3796202a34c335585fc953e15c97196"
20
+ },
21
+ {
22
+ "note": "partial delivery",
23
+ "delivered": 40,
24
+ "identity": "2ed52a521f9762c3adc24c8048a4ee04a3796202a34c335585fc953e15c97196"
25
+ }
26
+ ]
27
+ },
28
+ "expected": {
29
+ "outcome": "PASS"
30
+ },
31
+ "limits": [
32
+ "Identity being stable does not mean the delivery was acceptable; that is the work-acceptance layer."
33
+ ]
34
+ }
@@ -0,0 +1,81 @@
1
+ {
2
+ "schema_version": "3.0.0",
3
+ "verifies": "attempt-identity",
4
+ "requires_chain_read": false,
5
+ "provenance": [
6
+ "x402 specification v2 section 5.5, Settlement and Refusal Identity Invariant, as proposed in x402-foundation/x402 pull request 3776 on branch spec/settlement-refusal-identity-invariant and not on main",
7
+ "the gap these cover was named by ruiruii in erc-8004/erc-8004-contracts#99 on 18 October 2026: each of the four evidence layers depends on an attempt identity that none of them defines",
8
+ "section 5.5 closes by referring to \"the normative conformance tests in the specification repository for the required negative cases\"; at the time of writing those did not exist"
9
+ ],
10
+ "id": "attempt-identity-paths-disagree",
11
+ "clause": "all-paths-same-artifact",
12
+ "encodes": "Two paths of one unchanged request resolving different identities, which the clause forbids outright.",
13
+ "description": "The request is byte-for-byte the same on both paths, and the refusal path resolves a different identity than the challenge quote. A holder of two records about this attempt can no longer tell they are about the same attempt, which is the whole failure the clause exists to prevent.",
14
+ "input": {
15
+ "ingress": {
16
+ "snapshot": {
17
+ "method": "POST",
18
+ "path": "/api/paid/verify-ownership",
19
+ "query": "",
20
+ "body": {
21
+ "endpoint": "https://example.com/api/health",
22
+ "ownerAddress": "0x1111111111111111111111111111111111111111",
23
+ "method": "dns-txt"
24
+ },
25
+ "payTo": "0x2888f36548a900b9b08d584524e1f73bc563ec26",
26
+ "asset": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
27
+ "network": "eip155:8453",
28
+ "amount": "1000",
29
+ "scheme": "exact"
30
+ },
31
+ "identity": "2ed52a521f9762c3adc24c8048a4ee04a3796202a34c335585fc953e15c97196"
32
+ },
33
+ "paths": [
34
+ {
35
+ "path": "challenge-quote",
36
+ "snapshot": {
37
+ "method": "POST",
38
+ "path": "/api/paid/verify-ownership",
39
+ "query": "",
40
+ "body": {
41
+ "endpoint": "https://example.com/api/health",
42
+ "ownerAddress": "0x1111111111111111111111111111111111111111",
43
+ "method": "dns-txt"
44
+ },
45
+ "payTo": "0x2888f36548a900b9b08d584524e1f73bc563ec26",
46
+ "asset": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
47
+ "network": "eip155:8453",
48
+ "amount": "1000",
49
+ "scheme": "exact"
50
+ },
51
+ "identity": "2ed52a521f9762c3adc24c8048a4ee04a3796202a34c335585fc953e15c97196"
52
+ },
53
+ {
54
+ "path": "refusal",
55
+ "snapshot": {
56
+ "method": "POST",
57
+ "path": "/api/paid/verify-ownership",
58
+ "query": "",
59
+ "body": {
60
+ "endpoint": "https://example.com/api/health",
61
+ "ownerAddress": "0x1111111111111111111111111111111111111111",
62
+ "method": "dns-txt"
63
+ },
64
+ "payTo": "0x2888f36548a900b9b08d584524e1f73bc563ec26",
65
+ "asset": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
66
+ "network": "eip155:8453",
67
+ "amount": "1000",
68
+ "scheme": "exact"
69
+ },
70
+ "identity": "baaf0cfdbd04db77cacfea2dede2cf0c2bc2967a9f5c0fc52d7169d70eb7fa79"
71
+ }
72
+ ]
73
+ },
74
+ "expected": {
75
+ "outcome": "FAIL",
76
+ "reason_code": "PATHS_DISAGREE_ON_IDENTITY"
77
+ },
78
+ "limits": [
79
+ "This does not say which of the two paths is wrong, only that they cannot both be right."
80
+ ]
81
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "schema_version": "3.0.0",
3
+ "verifies": "attempt-identity",
4
+ "requires_chain_read": false,
5
+ "provenance": [
6
+ "x402 specification v2 section 5.5, Settlement and Refusal Identity Invariant, as proposed in x402-foundation/x402 pull request 3776 on branch spec/settlement-refusal-identity-invariant and not on main",
7
+ "the gap these cover was named by ruiruii in erc-8004/erc-8004-contracts#99 on 18 October 2026: each of the four evidence layers depends on an attempt identity that none of them defines",
8
+ "section 5.5 closes by referring to \"the normative conformance tests in the specification repository for the required negative cases\"; at the time of writing those did not exist"
9
+ ],
10
+ "id": "attempt-identity-reference-canonicalized-first",
11
+ "clause": "reference-over-transmitted-bytes",
12
+ "encodes": "The second inverse trap: a reference to a signed artifact taken over a canonicalized form instead of the transmitted bytes.",
13
+ "description": "The attempt references a separately signed offer. The reference hash matches a key-sorted rewrite of that offer rather than the bytes that were signed, so the reference names an object nobody signed. This is the exact mirror of the key-order case: there, canonicalizing is required; here, it destroys the thing. An implementation that applies one rule to both will fail one of these two vectors, and which one it fails says which half it misread.",
14
+ "input": {
15
+ "signed_artifact_reference": {
16
+ "transmitted": "{\"version\":1,\"amount\":\"1000\",\"payTo\":\"0x2888F36548a900B9b08d584524e1f73BC563eC26\"}",
17
+ "reference_hash": "96ea208143f35d56469ee10bc81467bdcfccc19eecea0e9754f861f2de68fe27",
18
+ "note": "the hash of a key-sorted rewrite, not of what was signed"
19
+ }
20
+ },
21
+ "expected": {
22
+ "outcome": "FAIL",
23
+ "reason_code": "REFERENCE_CANONICALIZED_BEFORE_HASHING"
24
+ },
25
+ "limits": [
26
+ "The checker recomputes both candidate hashes itself rather than trusting the note, so a mislabelled vector cannot pass."
27
+ ]
28
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "schema_version": "3.0.0",
3
+ "verifies": "attempt-identity",
4
+ "requires_chain_read": false,
5
+ "provenance": [
6
+ "x402 specification v2 section 5.5, Settlement and Refusal Identity Invariant, as proposed in x402-foundation/x402 pull request 3776 on branch spec/settlement-refusal-identity-invariant and not on main",
7
+ "the gap these cover was named by ruiruii in erc-8004/erc-8004-contracts#99 on 18 October 2026: each of the four evidence layers depends on an attempt identity that none of them defines",
8
+ "section 5.5 closes by referring to \"the normative conformance tests in the specification repository for the required negative cases\"; at the time of writing those did not exist"
9
+ ],
10
+ "id": "attempt-identity-reference-over-transmitted",
11
+ "clause": "reference-over-transmitted-bytes",
12
+ "encodes": "The positive control for the second trap.",
13
+ "description": "The reference hash is the digest of the artifact exactly as transmitted, which is what the signature covers.",
14
+ "input": {
15
+ "signed_artifact_reference": {
16
+ "transmitted": "{\"version\":1,\"amount\":\"1000\",\"payTo\":\"0x2888F36548a900B9b08d584524e1f73BC563eC26\"}",
17
+ "reference_hash": "a0c86bd7167da34f3f06ae274b8284ab1daea5802f10bf1df11e9b54b24182c5"
18
+ }
19
+ },
20
+ "expected": {
21
+ "outcome": "PASS"
22
+ },
23
+ "limits": [
24
+ "This says the reference names the signed bytes. It does not check that the signature over them is valid."
25
+ ]
26
+ }
@@ -0,0 +1,103 @@
1
+ {
2
+ "schema_version": "3.0.0",
3
+ "verifies": "attempt-identity",
4
+ "requires_chain_read": false,
5
+ "provenance": [
6
+ "x402 specification v2 section 5.5, Settlement and Refusal Identity Invariant, as proposed in x402-foundation/x402 pull request 3776 on branch spec/settlement-refusal-identity-invariant and not on main",
7
+ "the gap these cover was named by ruiruii in erc-8004/erc-8004-contracts#99 on 18 October 2026: each of the four evidence layers depends on an attempt identity that none of them defines",
8
+ "section 5.5 closes by referring to \"the normative conformance tests in the specification repository for the required negative cases\"; at the time of writing those did not exist"
9
+ ],
10
+ "id": "attempt-identity-settle-rederives-downstream",
11
+ "clause": "derive-once-at-ingress",
12
+ "encodes": "The core negative case: identity recomputed downstream from content the facilitator did not capture at ingress.",
13
+ "description": "Three paths take the identity from the ingress snapshot and /settle recomputes it from the request body as it reads it at settle time. The value happens to come out the same here, which is what makes this case worth having: an implementation that only compares values will report conformance, because the defect is in WHERE the value came from, not in what it was this time.",
14
+ "input": {
15
+ "ingress": {
16
+ "snapshot": {
17
+ "method": "POST",
18
+ "path": "/api/paid/verify-ownership",
19
+ "query": "",
20
+ "body": {
21
+ "endpoint": "https://example.com/api/health",
22
+ "ownerAddress": "0x1111111111111111111111111111111111111111",
23
+ "method": "dns-txt"
24
+ },
25
+ "payTo": "0x2888f36548a900b9b08d584524e1f73bc563ec26",
26
+ "asset": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
27
+ "network": "eip155:8453",
28
+ "amount": "1000",
29
+ "scheme": "exact"
30
+ },
31
+ "identity": "2ed52a521f9762c3adc24c8048a4ee04a3796202a34c335585fc953e15c97196"
32
+ },
33
+ "paths": [
34
+ {
35
+ "path": "challenge-quote",
36
+ "snapshot": {
37
+ "method": "POST",
38
+ "path": "/api/paid/verify-ownership",
39
+ "query": "",
40
+ "body": {
41
+ "endpoint": "https://example.com/api/health",
42
+ "ownerAddress": "0x1111111111111111111111111111111111111111",
43
+ "method": "dns-txt"
44
+ },
45
+ "payTo": "0x2888f36548a900b9b08d584524e1f73bc563ec26",
46
+ "asset": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
47
+ "network": "eip155:8453",
48
+ "amount": "1000",
49
+ "scheme": "exact"
50
+ },
51
+ "identity": "2ed52a521f9762c3adc24c8048a4ee04a3796202a34c335585fc953e15c97196",
52
+ "derived_from": "ingress-snapshot"
53
+ },
54
+ {
55
+ "path": "settle",
56
+ "snapshot": {
57
+ "method": "POST",
58
+ "path": "/api/paid/verify-ownership",
59
+ "query": "",
60
+ "body": {
61
+ "endpoint": "https://example.com/api/health",
62
+ "ownerAddress": "0x1111111111111111111111111111111111111111",
63
+ "method": "dns-txt"
64
+ },
65
+ "payTo": "0x2888f36548a900b9b08d584524e1f73bc563ec26",
66
+ "asset": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
67
+ "network": "eip155:8453",
68
+ "amount": "1000",
69
+ "scheme": "exact"
70
+ },
71
+ "identity": "2ed52a521f9762c3adc24c8048a4ee04a3796202a34c335585fc953e15c97196",
72
+ "derived_from": "request-body-at-settle-time"
73
+ },
74
+ {
75
+ "path": "refusal",
76
+ "snapshot": {
77
+ "method": "POST",
78
+ "path": "/api/paid/verify-ownership",
79
+ "query": "",
80
+ "body": {
81
+ "endpoint": "https://example.com/api/health",
82
+ "ownerAddress": "0x1111111111111111111111111111111111111111",
83
+ "method": "dns-txt"
84
+ },
85
+ "payTo": "0x2888f36548a900b9b08d584524e1f73bc563ec26",
86
+ "asset": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
87
+ "network": "eip155:8453",
88
+ "amount": "1000",
89
+ "scheme": "exact"
90
+ },
91
+ "identity": "2ed52a521f9762c3adc24c8048a4ee04a3796202a34c335585fc953e15c97196",
92
+ "derived_from": "ingress-snapshot"
93
+ }
94
+ ]
95
+ },
96
+ "expected": {
97
+ "outcome": "FAIL",
98
+ "reason_code": "IDENTITY_REDERIVED_DOWNSTREAM"
99
+ },
100
+ "limits": [
101
+ "A recorded observation cannot prove where a value came from; this vector tests that a checker reads the declared origin instead of only comparing values."
102
+ ]
103
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "schema_version": "3.0.0",
3
+ "verifies": "attempt-identity",
4
+ "requires_chain_read": false,
5
+ "provenance": [
6
+ "x402 specification v2 section 5.5, Settlement and Refusal Identity Invariant, as proposed in x402-foundation/x402 pull request 3776 on branch spec/settlement-refusal-identity-invariant and not on main",
7
+ "the gap these cover was named by ruiruii in erc-8004/erc-8004-contracts#99 on 18 October 2026: each of the four evidence layers depends on an attempt identity that none of them defines",
8
+ "section 5.5 closes by referring to \"the normative conformance tests in the specification repository for the required negative cases\"; at the time of writing those did not exist"
9
+ ],
10
+ "id": "attempt-identity-settled-despite-mismatch",
11
+ "clause": "countersignature-recompute-or-refuse",
12
+ "encodes": "The money half of the same clause: a mismatch that did not stop settlement.",
13
+ "description": "The counterparty correctly refused to sign, and the interaction settled anyway. The clause says that on a mismatch the interaction MUST NOT settle, so refusing to sign while still taking the money satisfies the letter of one sentence and breaks the one that matters.",
14
+ "input": {
15
+ "countersignature": {
16
+ "ingress_value": "2ed52a521f9762c3adc24c8048a4ee04a3796202a34c335585fc953e15c97196",
17
+ "recomputed": "b1b9009e0c9e7d66fc4e47a66303e8ccdddce8cc3462bc9de007abf524855bcb",
18
+ "signed": false,
19
+ "settled": true
20
+ }
21
+ },
22
+ "expected": {
23
+ "outcome": "FAIL",
24
+ "reason_code": "SETTLED_DESPITE_MISMATCH"
25
+ },
26
+ "limits": [
27
+ "Whether money actually moved is a chain question and is not read here."
28
+ ]
29
+ }