bindable 0.1.0__tar.gz → 0.1.1__tar.gz
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.
- {bindable-0.1.0 → bindable-0.1.1}/PKG-INFO +2 -2
- {bindable-0.1.0 → bindable-0.1.1}/bindable/_canonical_record.py +4 -4
- bindable-0.1.1/bindable/_version.py +1 -0
- {bindable-0.1.0 → bindable-0.1.1}/pyproject.toml +1 -1
- {bindable-0.1.0 → bindable-0.1.1}/tests/crypto/vectors.json +1 -1
- bindable-0.1.1/tests/fixtures/KEY_PROVENANCE.md +1 -0
- bindable-0.1.1/tests/fixtures/bindable-public-2026-07.pem +4 -0
- bindable-0.1.1/tests/fixtures/receipt_public_blocked_rc3.json +1 -0
- bindable-0.1.1/tests/fixtures/receipt_public_released_rc3.json +1 -0
- {bindable-0.1.0 → bindable-0.1.1}/tests/test_canonical_vectors.py +4 -4
- {bindable-0.1.0 → bindable-0.1.1}/tests/test_client.py +3 -1
- {bindable-0.1.0 → bindable-0.1.1}/tests/test_verify_receipt.py +70 -56
- {bindable-0.1.0 → bindable-0.1.1}/tests/test_version.py +1 -1
- bindable-0.1.0/bindable/_version.py +0 -1
- bindable-0.1.0/tests/fixtures/receipt_public_key.pem +0 -4
- bindable-0.1.0/tests/fixtures/receipt_released.json +0 -30
- {bindable-0.1.0 → bindable-0.1.1}/.gitignore +0 -0
- {bindable-0.1.0 → bindable-0.1.1}/LICENSE +0 -0
- {bindable-0.1.0 → bindable-0.1.1}/NOTICE +0 -0
- {bindable-0.1.0 → bindable-0.1.1}/README.md +0 -0
- {bindable-0.1.0 → bindable-0.1.1}/bindable/__init__.py +0 -0
- {bindable-0.1.0 → bindable-0.1.1}/bindable/client.py +0 -0
- {bindable-0.1.0 → bindable-0.1.1}/bindable/crypto/__init__.py +0 -0
- {bindable-0.1.0 → bindable-0.1.1}/bindable/crypto/canonical.py +0 -0
- {bindable-0.1.0 → bindable-0.1.1}/bindable/crypto/hash.py +0 -0
- {bindable-0.1.0 → bindable-0.1.1}/bindable/crypto/keys.py +0 -0
- {bindable-0.1.0 → bindable-0.1.1}/bindable/crypto/sign.py +0 -0
- {bindable-0.1.0 → bindable-0.1.1}/bindable/errors.py +0 -0
- {bindable-0.1.0 → bindable-0.1.1}/bindable/py.typed +0 -0
- {bindable-0.1.0 → bindable-0.1.1}/bindable/verify.py +0 -0
- {bindable-0.1.0 → bindable-0.1.1}/tests/__init__.py +0 -0
- {bindable-0.1.0 → bindable-0.1.1}/tests/crypto/sign_vectors.json +0 -0
- {bindable-0.1.0 → bindable-0.1.1}/tests/test_signature_verify.py +0 -0
|
@@ -38,7 +38,7 @@ def _canonical_hash_input(receipt: Mapping[str, Any]) -> dict[str, Any]:
|
|
|
38
38
|
"""Return the canonical hash input dict per MVP spec §12.3.
|
|
39
39
|
|
|
40
40
|
Included fields (canonical_json sorts alphabetically):
|
|
41
|
-
action_type,
|
|
41
|
+
action_type, proposal_payload, contract_id, contract_version,
|
|
42
42
|
counterparty_identity, failed_rule, nonce,
|
|
43
43
|
persistent_agent_id, previous_hash, proposed_action_id,
|
|
44
44
|
referenced_evidence, sponsor_identity,
|
|
@@ -46,8 +46,8 @@ def _canonical_hash_input(receipt: Mapping[str, Any]) -> dict[str, Any]:
|
|
|
46
46
|
|
|
47
47
|
Excluded per §12.3:
|
|
48
48
|
receipt_id, signature, signing_key_id, signed_at,
|
|
49
|
-
chain_hash (self-reference), reason_detail,
|
|
50
|
-
(derivable from canonical
|
|
49
|
+
chain_hash (self-reference), reason_detail, proposal_digest
|
|
50
|
+
(derivable from canonical proposal_payload).
|
|
51
51
|
|
|
52
52
|
Args:
|
|
53
53
|
receipt: A receipt as a plain mapping (e.g. parsed JSON from
|
|
@@ -61,7 +61,7 @@ def _canonical_hash_input(receipt: Mapping[str, Any]) -> dict[str, Any]:
|
|
|
61
61
|
"""
|
|
62
62
|
return {
|
|
63
63
|
"action_type": receipt["action_type"],
|
|
64
|
-
"
|
|
64
|
+
"proposal_payload": receipt["proposal_payload"],
|
|
65
65
|
"contract_id": receipt["contract_id"],
|
|
66
66
|
"contract_version": receipt["contract_version"],
|
|
67
67
|
"counterparty_identity": receipt["counterparty_identity"],
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.1"
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
},
|
|
73
73
|
{
|
|
74
74
|
"name": "production_shape_receipt",
|
|
75
|
-
"description": "Approximates the receipt record shape from Interlock MVP Spec v0.1.2 §6.2.",
|
|
75
|
+
"description": "LEGACY SHAPE (pre-rc3 claim_* wire). Approximates the receipt record shape from Interlock MVP Spec v0.1.2 §6.2. Pins canonicalization math only; the rc3 wire uses proposal_* vocabulary.",
|
|
76
76
|
"record": {
|
|
77
77
|
"claim_payload": {
|
|
78
78
|
"po_number": "PO-44821",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Key provenance: bindable-public-2026-07.pem was recovered via ECDSA public-key recovery from the two production receipts in this directory and cross-validated against the hosted POST /v1/verify. Authoritative distribution of landscape verification keys will move to a read-only platform endpoint.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"receipt_id": "f92558c09edc44e7a141ea5c1b5cc855", "proposal_digest": "6b317920863fdbebaeda9e824daf5aeb781d87b2307f209f1ae25188fce2fe62", "proposal_payload": {"matter_id": "MER-2026-014", "call_cost_usd": 126, "records_requested": 400, "purpose": "full-market data sweep"}, "contract_id": "demo-diligence-spend-public-founding", "contract_version": "1.0.0", "sponsor_identity": "did:web:bindbl.ai:sponsor:b4047dfe7c4244dda32a3c8b4a02fc8e", "sponsor_identity_context": "ctx:public", "counterparty_identity": null, "persistent_agent_id": "did:web:bindbl.ai:agent:9dd08b5846564a1fbe674b1834fb887d", "action_type": "paid_data_call", "proposed_action_id": "pf-blocked-public-founding", "verification_outcome": "failed", "failed_rule": "r-2", "reason_detail": "126 lte 58 not satisfied", "referenced_evidence": ["demo-budget-public-founding"], "verified_at": "2026-08-13T14:09:07.031Z", "nonce": "d61480f760d63f14d1350c21573d1a8b", "chain_hash": "63b052eef0f449d7330dfd897891385e48ef372d8c69e9ddd05eaa84247ea036", "previous_hash": null, "signature": "30460221009273d5a94dbca61570dff9f7729d7576e852c0a681a396998db5da1e27885ed4022100ada78e5200865db572d78663a07b871cfd6013f84550c34bb3242cad07c44e9e", "signing_key_id": "bindable-public-2026-07", "signed_at": "2026-08-13T14:09:07.031Z", "intervention_artifacts": null}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"receipt_id": "9b6e8bca643444e09dfe0ef4015244d1", "proposal_digest": "9fe07610a2f335a96454d710259145e5ed1ef546234e21c279f4d6cff79f570d", "proposal_payload": {"matter_id": "MER-2026-014", "call_cost_usd": 12, "records_requested": 40, "purpose": "comparable-transactions pull"}, "contract_id": "demo-diligence-spend-public-founding", "contract_version": "1.0.0", "sponsor_identity": "did:web:bindbl.ai:sponsor:b4047dfe7c4244dda32a3c8b4a02fc8e", "sponsor_identity_context": "ctx:public", "counterparty_identity": null, "persistent_agent_id": "did:web:bindbl.ai:agent:9dd08b5846564a1fbe674b1834fb887d", "action_type": "paid_data_call", "proposed_action_id": "pf-released-public-founding", "verification_outcome": "passed", "failed_rule": null, "reason_detail": null, "referenced_evidence": ["demo-budget-public-founding"], "verified_at": "2026-08-13T14:08:59.607Z", "nonce": "b38265567729c89b020e6afe8a420f70", "chain_hash": "61374b353d242e9ddc523b09b537506a16f84852a1352d4bbf03643e486fe99d", "previous_hash": null, "signature": "3045022100d4dc8ff4ce26b24d0725016b7243b41e96ed2bc9074f21ba40c618ca459e0b01022027065e8d3e342393d70a270d43886145b2373418f2dc6ef7fc12b7b66860a12b", "signing_key_id": "bindable-public-2026-07", "signed_at": "2026-08-13T14:08:59.621Z", "intervention_artifacts": null}
|
|
@@ -55,7 +55,7 @@ def test_canonical_json_rejects_nested_float() -> None:
|
|
|
55
55
|
_FULL_RECEIPT: dict[str, Any] = {
|
|
56
56
|
# 15 included fields
|
|
57
57
|
"action_type": "release_payment",
|
|
58
|
-
"
|
|
58
|
+
"proposal_payload": {"po_number": "PO-44821", "invoice_amount_cents": 12500000},
|
|
59
59
|
"contract_id": "contract-acme-xyz-invoice",
|
|
60
60
|
"contract_version": "1.0.0",
|
|
61
61
|
"counterparty_identity": "acme-vendor",
|
|
@@ -76,12 +76,12 @@ _FULL_RECEIPT: dict[str, Any] = {
|
|
|
76
76
|
"signed_at": "2026-05-19T14:00:01.000Z",
|
|
77
77
|
"chain_hash": "0" * 64,
|
|
78
78
|
"reason_detail": "all good",
|
|
79
|
-
"
|
|
79
|
+
"proposal_digest": "f" * 64,
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
_EXPECTED_KEYS = {
|
|
83
83
|
"action_type",
|
|
84
|
-
"
|
|
84
|
+
"proposal_payload",
|
|
85
85
|
"contract_id",
|
|
86
86
|
"contract_version",
|
|
87
87
|
"counterparty_identity",
|
|
@@ -115,7 +115,7 @@ def test_canonical_hash_input_excludes_signature_and_chain_fields() -> None:
|
|
|
115
115
|
"signed_at",
|
|
116
116
|
"chain_hash",
|
|
117
117
|
"reason_detail",
|
|
118
|
-
"
|
|
118
|
+
"proposal_digest",
|
|
119
119
|
):
|
|
120
120
|
assert excluded not in result
|
|
121
121
|
|
|
@@ -29,7 +29,9 @@ _OUTCOME_RELEASED: dict[str, Any] = {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
_FIXTURE_RECEIPT: dict[str, Any] = json.loads(
|
|
32
|
-
(Path(__file__).parent / "fixtures" / "
|
|
32
|
+
(Path(__file__).parent / "fixtures" / "receipt_public_released_rc3.json").read_text(
|
|
33
|
+
encoding="utf-8"
|
|
34
|
+
)
|
|
33
35
|
)
|
|
34
36
|
|
|
35
37
|
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
"""verify_receipt — end-to-end gate against
|
|
2
|
-
|
|
3
|
-
The
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
"""verify_receipt — end-to-end gate against real engine-minted receipts.
|
|
2
|
+
|
|
3
|
+
The fixtures (tests/fixtures/receipt_public_released_rc3.json and
|
|
4
|
+
receipt_public_blocked_rc3.json) are REAL production receipts minted by the
|
|
5
|
+
engine @ 0.1.0rc3 on the Public landscape, ECDSA-signed by key
|
|
6
|
+
bindable-public-2026-07 (public half at tests/fixtures/
|
|
7
|
+
bindable-public-2026-07.pem). The chain-hash gates below prove the
|
|
8
|
+
de-pydantic thin-client builder reproduces the engine's chain_hash
|
|
9
|
+
byte-for-byte on the proposal_* wire. If one fails, the builder diverged —
|
|
10
|
+
STOP and report; never edit the fixtures.
|
|
8
11
|
"""
|
|
9
12
|
|
|
10
13
|
from __future__ import annotations
|
|
@@ -14,120 +17,135 @@ import json
|
|
|
14
17
|
from pathlib import Path
|
|
15
18
|
from typing import Any
|
|
16
19
|
|
|
20
|
+
import pytest
|
|
21
|
+
|
|
17
22
|
from bindable import ReceiptVerification, load_public_key, verify_receipt
|
|
18
23
|
from bindable._canonical_record import _canonical_hash_input
|
|
19
24
|
from bindable.crypto import canonical_sha256
|
|
20
25
|
|
|
21
26
|
_FIX = Path(__file__).parent / "fixtures"
|
|
22
|
-
|
|
23
|
-
(_FIX / "
|
|
27
|
+
_RELEASED: dict[str, Any] = json.loads(
|
|
28
|
+
(_FIX / "receipt_public_released_rc3.json").read_text(encoding="utf-8")
|
|
29
|
+
)
|
|
30
|
+
_BLOCKED: dict[str, Any] = json.loads(
|
|
31
|
+
(_FIX / "receipt_public_blocked_rc3.json").read_text(encoding="utf-8")
|
|
24
32
|
)
|
|
25
|
-
_PUBLIC_KEY_PEM_BYTES = (_FIX / "
|
|
33
|
+
_PUBLIC_KEY_PEM_BYTES = (_FIX / "bindable-public-2026-07.pem").read_bytes()
|
|
26
34
|
_PUBLIC_KEY_PEM_STR = _PUBLIC_KEY_PEM_BYTES.decode("utf-8")
|
|
27
35
|
|
|
28
|
-
# A valid P-256 key that did NOT sign
|
|
36
|
+
# A valid P-256 key that did NOT sign these receipts (from the sign vectors).
|
|
29
37
|
_WRONG_KEY_PEM = json.loads(
|
|
30
38
|
(Path(__file__).parent / "crypto" / "sign_vectors.json").read_text(encoding="utf-8")
|
|
31
39
|
)[0]["public_key_pem"]
|
|
32
40
|
|
|
41
|
+
_BOTH = pytest.mark.parametrize(
|
|
42
|
+
"fixture", [_RELEASED, _BLOCKED], ids=["released", "blocked"]
|
|
43
|
+
)
|
|
44
|
+
|
|
33
45
|
|
|
34
|
-
def _receipt() -> dict[str, Any]:
|
|
35
|
-
return copy.deepcopy(
|
|
46
|
+
def _receipt(fixture: dict[str, Any]) -> dict[str, Any]:
|
|
47
|
+
return copy.deepcopy(fixture)
|
|
36
48
|
|
|
37
49
|
|
|
38
50
|
# ─── GATE: builder reproduces the engine chain_hash ────────────────────
|
|
39
51
|
|
|
40
52
|
|
|
41
|
-
|
|
53
|
+
@_BOTH
|
|
54
|
+
def test_chain_hash_gate_builder_matches_fixture(fixture: dict[str, Any]) -> None:
|
|
42
55
|
"""canonical_sha256(_canonical_hash_input(receipt)) == receipt['chain_hash']."""
|
|
43
|
-
recomputed = canonical_sha256(_canonical_hash_input(_receipt()))
|
|
44
|
-
assert recomputed ==
|
|
56
|
+
recomputed = canonical_sha256(_canonical_hash_input(_receipt(fixture)))
|
|
57
|
+
assert recomputed == fixture["chain_hash"], (
|
|
45
58
|
f"Builder diverged from the engine: recomputed {recomputed!r} != pinned "
|
|
46
|
-
f"{
|
|
59
|
+
f"{fixture['chain_hash']!r}. STOP — do NOT edit the fixture."
|
|
47
60
|
)
|
|
48
61
|
|
|
49
62
|
|
|
50
63
|
# ─── Happy path (key as PEM str and as loaded object) ──────────────────
|
|
51
64
|
|
|
52
65
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
66
|
+
@_BOTH
|
|
67
|
+
def test_valid_with_pem_string(fixture: dict[str, Any]) -> None:
|
|
68
|
+
result = verify_receipt(_receipt(fixture), public_key=_PUBLIC_KEY_PEM_STR)
|
|
56
69
|
assert result.valid is True
|
|
57
70
|
assert result.chain_hash_valid is True
|
|
58
71
|
assert result.signature_valid is True
|
|
59
72
|
assert result.reason is None
|
|
60
73
|
|
|
61
74
|
|
|
62
|
-
|
|
75
|
+
@_BOTH
|
|
76
|
+
def test_valid_with_loaded_public_key_object(fixture: dict[str, Any]) -> None:
|
|
63
77
|
pub = load_public_key(_PUBLIC_KEY_PEM_BYTES)
|
|
64
|
-
result = verify_receipt(_receipt(), public_key=pub)
|
|
78
|
+
result = verify_receipt(_receipt(fixture), public_key=pub)
|
|
65
79
|
assert result.valid is True
|
|
80
|
+
assert result.chain_hash_valid is True
|
|
66
81
|
assert result.signature_valid is True
|
|
82
|
+
assert result.reason is None
|
|
67
83
|
|
|
68
84
|
|
|
69
|
-
# ─── Tamper
|
|
85
|
+
# ─── Tamper and wrong-key paths ────────────────────────────────────────
|
|
70
86
|
|
|
71
87
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
r
|
|
88
|
+
@_BOTH
|
|
89
|
+
def test_tampered_canonical_field_breaks_chain(fixture: dict[str, Any]) -> None:
|
|
90
|
+
r = _receipt(fixture)
|
|
91
|
+
r["proposal_payload"] = {"matter_id": "MER-EVIL", "call_cost_usd": 999_999}
|
|
75
92
|
result = verify_receipt(r, public_key=_PUBLIC_KEY_PEM_STR)
|
|
76
|
-
assert result.chain_hash_valid is False
|
|
77
93
|
assert result.valid is False
|
|
78
|
-
assert
|
|
94
|
+
assert result.chain_hash_valid is False
|
|
95
|
+
assert result.reason == "chain_hash mismatch"
|
|
79
96
|
|
|
80
97
|
|
|
81
98
|
def test_tampered_signature_fails_signature_check() -> None:
|
|
82
|
-
r = _receipt()
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
r["signature"] =
|
|
99
|
+
r = _receipt(_RELEASED)
|
|
100
|
+
# Flip the last hex digit of the DER signature.
|
|
101
|
+
last = r["signature"][-1]
|
|
102
|
+
r["signature"] = r["signature"][:-1] + ("0" if last != "0" else "1")
|
|
86
103
|
result = verify_receipt(r, public_key=_PUBLIC_KEY_PEM_STR)
|
|
104
|
+
assert result.valid is False
|
|
87
105
|
assert result.chain_hash_valid is True
|
|
88
106
|
assert result.signature_valid is False
|
|
89
|
-
assert result.valid is False
|
|
90
107
|
|
|
91
108
|
|
|
92
109
|
def test_wrong_key_fails_signature_check() -> None:
|
|
93
|
-
result = verify_receipt(_receipt(), public_key=_WRONG_KEY_PEM)
|
|
110
|
+
result = verify_receipt(_receipt(_RELEASED), public_key=_WRONG_KEY_PEM)
|
|
111
|
+
assert result.valid is False
|
|
94
112
|
assert result.chain_hash_valid is True
|
|
95
113
|
assert result.signature_valid is False
|
|
96
|
-
assert result.valid is False
|
|
97
114
|
assert result.reason == "signature invalid"
|
|
98
115
|
|
|
99
116
|
|
|
100
|
-
# ───
|
|
117
|
+
# ─── Signature-optional matrix ─────────────────────────────────────────
|
|
101
118
|
|
|
102
119
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
assert result.chain_hash_valid is True
|
|
120
|
+
@_BOTH
|
|
121
|
+
def test_no_key_skips_signature(fixture: dict[str, Any]) -> None:
|
|
122
|
+
result = verify_receipt(_receipt(fixture)) # public_key=None
|
|
107
123
|
assert result.valid is True
|
|
124
|
+
assert result.chain_hash_valid is True
|
|
125
|
+
assert result.signature_valid is None
|
|
108
126
|
assert result.reason is None
|
|
109
127
|
|
|
110
128
|
|
|
111
129
|
def test_key_set_but_no_signature_field() -> None:
|
|
112
|
-
r = _receipt()
|
|
113
|
-
r
|
|
130
|
+
r = _receipt(_RELEASED)
|
|
131
|
+
r["signature"] = None
|
|
114
132
|
result = verify_receipt(r, public_key=_PUBLIC_KEY_PEM_STR)
|
|
115
|
-
assert result.signature_valid is False
|
|
116
|
-
assert result.chain_hash_valid is True
|
|
117
133
|
assert result.valid is False
|
|
118
|
-
assert result.
|
|
134
|
+
assert result.chain_hash_valid is True
|
|
135
|
+
assert result.signature_valid is False
|
|
119
136
|
|
|
120
137
|
|
|
121
138
|
def test_invalid_hex_signature_is_handled() -> None:
|
|
122
|
-
r = _receipt()
|
|
123
|
-
r["signature"] = "
|
|
139
|
+
r = _receipt(_RELEASED)
|
|
140
|
+
r["signature"] = "zz-not-hex"
|
|
124
141
|
result = verify_receipt(r, public_key=_PUBLIC_KEY_PEM_STR)
|
|
125
142
|
assert result.valid is False
|
|
143
|
+
assert result.chain_hash_valid is True
|
|
126
144
|
assert result.signature_valid is False
|
|
127
145
|
assert "error" in (result.reason or "").lower()
|
|
128
146
|
|
|
129
147
|
|
|
130
|
-
# ───
|
|
148
|
+
# ─── Robustness: verify_receipt never raises ───────────────────────────
|
|
131
149
|
|
|
132
150
|
|
|
133
151
|
def test_empty_dict_never_raises() -> None:
|
|
@@ -138,14 +156,14 @@ def test_empty_dict_never_raises() -> None:
|
|
|
138
156
|
|
|
139
157
|
|
|
140
158
|
def test_garbage_dict_never_raises() -> None:
|
|
141
|
-
result = verify_receipt({"chain_hash": 123, "
|
|
159
|
+
result = verify_receipt({"chain_hash": 123, "proposal_payload": object()}) # type: ignore[dict-item]
|
|
142
160
|
assert isinstance(result, ReceiptVerification)
|
|
143
161
|
assert result.valid is False
|
|
144
162
|
assert result.reason is not None
|
|
145
163
|
|
|
146
164
|
|
|
147
165
|
def test_missing_chain_hash_never_raises() -> None:
|
|
148
|
-
r = _receipt()
|
|
166
|
+
r = _receipt(_RELEASED)
|
|
149
167
|
r.pop("chain_hash")
|
|
150
168
|
result = verify_receipt(r)
|
|
151
169
|
assert result.valid is False
|
|
@@ -154,12 +172,8 @@ def test_missing_chain_hash_never_raises() -> None:
|
|
|
154
172
|
|
|
155
173
|
|
|
156
174
|
def test_receipt_verification_is_frozen() -> None:
|
|
157
|
-
rv = verify_receipt(_receipt())
|
|
175
|
+
rv = verify_receipt(_receipt(_RELEASED))
|
|
158
176
|
import dataclasses
|
|
159
177
|
|
|
160
|
-
|
|
178
|
+
with pytest.raises(dataclasses.FrozenInstanceError):
|
|
161
179
|
rv.valid = False # type: ignore[misc]
|
|
162
|
-
except dataclasses.FrozenInstanceError:
|
|
163
|
-
pass
|
|
164
|
-
else: # pragma: no cover - frozen dataclass must reject assignment
|
|
165
|
-
raise AssertionError("ReceiptVerification must be frozen")
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.1.0"
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"receipt_id": "rcpt_test_001",
|
|
3
|
-
"claim_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
|
4
|
-
"claim_payload": {
|
|
5
|
-
"po_number": "PO-001",
|
|
6
|
-
"amount": 1000
|
|
7
|
-
},
|
|
8
|
-
"contract_id": "ctr_001",
|
|
9
|
-
"contract_version": "1.0.0",
|
|
10
|
-
"sponsor_identity": "testco",
|
|
11
|
-
"sponsor_identity_context": "ctx:testco:procurement",
|
|
12
|
-
"counterparty_identity": "supplier-a",
|
|
13
|
-
"persistent_agent_id": "agent_testco",
|
|
14
|
-
"action_type": "send_invoice",
|
|
15
|
-
"proposed_action_id": "claim_001",
|
|
16
|
-
"verification_outcome": "passed",
|
|
17
|
-
"failed_rule": null,
|
|
18
|
-
"reason_detail": null,
|
|
19
|
-
"referenced_evidence": [
|
|
20
|
-
"evidence-001"
|
|
21
|
-
],
|
|
22
|
-
"verified_at": "2026-05-25T14:22:05.123Z",
|
|
23
|
-
"nonce": "abc123def456",
|
|
24
|
-
"chain_hash": "fa37feb6c264ae089c44a2667eddfa91492712b425c2e5c60091797051e90ca6",
|
|
25
|
-
"previous_hash": null,
|
|
26
|
-
"signature": "304502202ff0c4527fa3be3208732971e75ac939edb486da248107ffab6f10faa7ef0baa02210096dec776e158633c9e1f88dd5f7e184d0461a7574d6772a8eae89661ea1921d2",
|
|
27
|
-
"signing_key_id": "test_signer",
|
|
28
|
-
"signed_at": null,
|
|
29
|
-
"intervention_artifacts": null
|
|
30
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|