bindable 0.1.1__tar.gz → 0.1.2__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.1 → bindable-0.1.2}/PKG-INFO +1 -1
- {bindable-0.1.1 → bindable-0.1.2}/bindable/_canonical_record.py +4 -4
- bindable-0.1.2/bindable/_version.py +1 -0
- {bindable-0.1.1 → bindable-0.1.2}/bindable/client.py +9 -4
- {bindable-0.1.1 → bindable-0.1.2}/pyproject.toml +1 -1
- bindable-0.1.2/tests/fixtures/KEY_PROVENANCE.md +7 -0
- bindable-0.1.2/tests/fixtures/bindable-test-rc4-2026-08.pem +4 -0
- bindable-0.1.2/tests/fixtures/receipt_public_blocked_rc4.json +1 -0
- bindable-0.1.2/tests/fixtures/receipt_public_released_rc4.json +1 -0
- {bindable-0.1.1 → bindable-0.1.2}/tests/test_canonical_vectors.py +2 -2
- {bindable-0.1.1 → bindable-0.1.2}/tests/test_client.py +4 -4
- {bindable-0.1.1 → bindable-0.1.2}/tests/test_verify_receipt.py +16 -11
- {bindable-0.1.1 → bindable-0.1.2}/tests/test_version.py +1 -1
- bindable-0.1.1/bindable/_version.py +0 -1
- bindable-0.1.1/tests/fixtures/KEY_PROVENANCE.md +0 -1
- bindable-0.1.1/tests/fixtures/bindable-public-2026-07.pem +0 -4
- bindable-0.1.1/tests/fixtures/receipt_public_blocked_rc3.json +0 -1
- bindable-0.1.1/tests/fixtures/receipt_public_released_rc3.json +0 -1
- {bindable-0.1.1 → bindable-0.1.2}/.gitignore +0 -0
- {bindable-0.1.1 → bindable-0.1.2}/LICENSE +0 -0
- {bindable-0.1.1 → bindable-0.1.2}/NOTICE +0 -0
- {bindable-0.1.1 → bindable-0.1.2}/README.md +0 -0
- {bindable-0.1.1 → bindable-0.1.2}/bindable/__init__.py +0 -0
- {bindable-0.1.1 → bindable-0.1.2}/bindable/crypto/__init__.py +0 -0
- {bindable-0.1.1 → bindable-0.1.2}/bindable/crypto/canonical.py +0 -0
- {bindable-0.1.1 → bindable-0.1.2}/bindable/crypto/hash.py +0 -0
- {bindable-0.1.1 → bindable-0.1.2}/bindable/crypto/keys.py +0 -0
- {bindable-0.1.1 → bindable-0.1.2}/bindable/crypto/sign.py +0 -0
- {bindable-0.1.1 → bindable-0.1.2}/bindable/errors.py +0 -0
- {bindable-0.1.1 → bindable-0.1.2}/bindable/py.typed +0 -0
- {bindable-0.1.1 → bindable-0.1.2}/bindable/verify.py +0 -0
- {bindable-0.1.1 → bindable-0.1.2}/tests/__init__.py +0 -0
- {bindable-0.1.1 → bindable-0.1.2}/tests/crypto/sign_vectors.json +0 -0
- {bindable-0.1.1 → bindable-0.1.2}/tests/crypto/vectors.json +0 -0
- {bindable-0.1.1 → bindable-0.1.2}/tests/test_signature_verify.py +0 -0
|
@@ -14,7 +14,7 @@ verified_at handling (de-pydantic departure, behaviour-preserving):
|
|
|
14
14
|
In the engine, ``Receipt.verified_at`` is a ``datetime`` and the
|
|
15
15
|
builder calls ``_serialize_wire_datetime`` to produce the wire
|
|
16
16
|
string (YYYY-MM-DDTHH:MM:SS.sssZ). In the thin client the receipt
|
|
17
|
-
arrives from /v1/
|
|
17
|
+
arrives from /v1/review as JSON, so ``verified_at`` is ALREADY the
|
|
18
18
|
wire string. It is used verbatim — passing it back through a
|
|
19
19
|
datetime serializer would be a double-serialization bug. No
|
|
20
20
|
``datetime`` object can reach this path, so ``_serialize_wire_datetime``
|
|
@@ -42,7 +42,7 @@ def _canonical_hash_input(receipt: Mapping[str, Any]) -> dict[str, Any]:
|
|
|
42
42
|
counterparty_identity, failed_rule, nonce,
|
|
43
43
|
persistent_agent_id, previous_hash, proposed_action_id,
|
|
44
44
|
referenced_evidence, sponsor_identity,
|
|
45
|
-
sponsor_identity_context,
|
|
45
|
+
sponsor_identity_context, verdict, verified_at.
|
|
46
46
|
|
|
47
47
|
Excluded per §12.3:
|
|
48
48
|
receipt_id, signature, signing_key_id, signed_at,
|
|
@@ -51,7 +51,7 @@ def _canonical_hash_input(receipt: Mapping[str, Any]) -> dict[str, Any]:
|
|
|
51
51
|
|
|
52
52
|
Args:
|
|
53
53
|
receipt: A receipt as a plain mapping (e.g. parsed JSON from
|
|
54
|
-
/v1/
|
|
54
|
+
/v1/review). All 15 included fields must be present;
|
|
55
55
|
nullable fields carry ``None``. ``verified_at`` must be
|
|
56
56
|
the wire-format string already.
|
|
57
57
|
|
|
@@ -73,6 +73,6 @@ def _canonical_hash_input(receipt: Mapping[str, Any]) -> dict[str, Any]:
|
|
|
73
73
|
"referenced_evidence": receipt["referenced_evidence"],
|
|
74
74
|
"sponsor_identity": receipt["sponsor_identity"],
|
|
75
75
|
"sponsor_identity_context": receipt["sponsor_identity_context"],
|
|
76
|
-
"
|
|
76
|
+
"verdict": receipt["verdict"],
|
|
77
77
|
"verified_at": receipt["verified_at"],
|
|
78
78
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.2"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Transport client for the hosted Bindable Action Gate.
|
|
2
2
|
|
|
3
|
-
``ActionGate.decide()`` submits a Proposal to ``POST /v1/
|
|
3
|
+
``ActionGate.decide()`` submits a Proposal to ``POST /v1/review`` and returns
|
|
4
4
|
the hosted Decision (the Outcome dict) verbatim. The client is pure transport:
|
|
5
5
|
it does NOT evaluate, canonicalize, or decide anything locally — all
|
|
6
6
|
proof-rule evaluation happens on the hosted gate. To verify the returned
|
|
@@ -24,7 +24,7 @@ from bindable.errors import BindableAPIError
|
|
|
24
24
|
class ActionGate:
|
|
25
25
|
"""Thin, token-authenticated httpx client for the hosted Bindable Action Gate.
|
|
26
26
|
|
|
27
|
-
Transports a Proposal to the hosted ``/v1/
|
|
27
|
+
Transports a Proposal to the hosted ``/v1/review`` endpoint and returns the
|
|
28
28
|
Decision verbatim as a dict. The client performs no local evaluation,
|
|
29
29
|
canonicalization, or decision-making; offline receipt verification is a
|
|
30
30
|
separate concern handled by ``bindable.verify_receipt``.
|
|
@@ -55,13 +55,18 @@ class ActionGate:
|
|
|
55
55
|
proposed_action_id: str,
|
|
56
56
|
nonce: str | None = None,
|
|
57
57
|
) -> dict[str, Any]:
|
|
58
|
-
"""Submit a Proposal to ``/v1/
|
|
58
|
+
"""Submit a Proposal to ``/v1/review`` and return the hosted Outcome dict.
|
|
59
59
|
|
|
60
60
|
Sends exactly the five wire fields (persistent_agent_id, action_type,
|
|
61
61
|
payload, proposed_action_id, nonce) with bearer-token auth. The Outcome
|
|
62
62
|
is returned verbatim; its ``receipt`` field (when present) can be passed
|
|
63
63
|
to ``verify_receipt`` for offline verification.
|
|
64
64
|
|
|
65
|
+
The endpoint moved from ``/v1/decide`` to ``/v1/review`` in 0.1.2. The
|
|
66
|
+
platform keeps BOTH paths live for a deprecation window, so an older
|
|
67
|
+
client pinned to ``/v1/decide`` keeps working; new callers get
|
|
68
|
+
``/v1/review``.
|
|
69
|
+
|
|
65
70
|
Args:
|
|
66
71
|
persistent_agent_id: The stable agent identity.
|
|
67
72
|
action_type: The action being proposed.
|
|
@@ -87,7 +92,7 @@ class ActionGate:
|
|
|
87
92
|
"nonce": nonce,
|
|
88
93
|
}
|
|
89
94
|
response = self._client.post(
|
|
90
|
-
f"{self._base_url}/v1/
|
|
95
|
+
f"{self._base_url}/v1/review",
|
|
91
96
|
json=body,
|
|
92
97
|
headers={
|
|
93
98
|
"Authorization": f"Bearer {self._token}",
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Key provenance: `bindable-test-rc4-2026-08.pem` is the public half of a keypair generated by the engine (0.1.0rc4) when the two receipts in this directory were minted through its own decide path, for the verdict-form cutover. The private half was never written anywhere and is not needed to verify.
|
|
2
|
+
|
|
3
|
+
These fixtures replace the retired `*_rc3.json` pair, which were real Public-landscape production receipts signed by `bindable-public-2026-07` under the pre-cutover `verification_outcome` canonical form. That form no longer exists, so those receipts could not be carried forward: hand-editing them would invalidate their `chain_hash` and signature, and re-signing them with the production key is impossible here — its private half is Key-Vault-injected and never leaves the platform. `bindable-public-2026-07.pem` (recovered previously via ECDSA public-key recovery from those receipts) was retired with them.
|
|
4
|
+
|
|
5
|
+
What the fixtures prove is unchanged: a receipt minted by the engine under the current signed canonical form verifies offline through this client's hand-maintained port, chain hash and signature both. Only the signing key's provenance changed — production-minted to engine-minted-with-a-test-key.
|
|
6
|
+
|
|
7
|
+
Worth doing once the Public landscape runs rc4: re-mint this pair from real production output and restore end-to-end production provenance. Authoritative distribution of landscape verification keys will move to a read-only platform endpoint.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"receipt_id": "1d8ef197c63c4a4fbb4f9523d25f4e88", "proposal_digest": "01d3430e4b046b20fb7f2e0de22d3bab5febaae1832601020109c55b81d73b64", "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", "verdict": "failed", "failed_rule": "r-2", "reason_detail": "126 lte 58 not satisfied", "referenced_evidence": ["demo-budget-public-founding"], "verified_at": "2026-08-21T14:52:16.607Z", "nonce": "d61480f760d63f14d1350c21573d1a8b", "chain_hash": "485d281c0590813d2fd943114004280cbadf95c56bb9f2c8caba22f26c8ac1a2", "previous_hash": null, "signature": "3045022022f79c8897615256b53d3364db8f743eb524541a766329674e25b0ca1eba3d83022100b057d504c0cdb43474b091aa03f72a0bf18f8d11e24f05fab40baa2e497d40c1", "signing_key_id": "bindable-test-rc4-2026-08", "signed_at": "2026-08-21T14:52:16.607Z", "intervention_artifacts": null}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"receipt_id": "ac2c3b3e7a6c4d59b972fa297a9f4821", "proposal_digest": "f35eb78be1073174427cf7884bccecbf7a2c8b776ecb932939a2219fa5d17c16", "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", "verdict": "passed", "failed_rule": null, "reason_detail": null, "referenced_evidence": ["demo-budget-public-founding"], "verified_at": "2026-08-21T14:52:16.604Z", "nonce": "b38265567729c89b020e6afe8a420f70", "chain_hash": "d7449f9b6874f2774ab4902dbe18ae2093de6a4cd223ab53802d39aace2a38e4", "previous_hash": null, "signature": "3045022014974d1ae715e5f16fdc2307d82cff116414ca4ac176d5566e2f72d7385cf580022100e56c5a09184c0fe57c32e5a400c55ac59d18c35d34ffd1b95c8bf2e3f4e7c86d", "signing_key_id": "bindable-test-rc4-2026-08", "signed_at": "2026-08-21T14:52:16.607Z", "intervention_artifacts": null}
|
|
@@ -67,7 +67,7 @@ _FULL_RECEIPT: dict[str, Any] = {
|
|
|
67
67
|
"referenced_evidence": ["audit-001", "audit-002"],
|
|
68
68
|
"sponsor_identity": "sponsor-001",
|
|
69
69
|
"sponsor_identity_context": "treasury",
|
|
70
|
-
"
|
|
70
|
+
"verdict": "passed",
|
|
71
71
|
"verified_at": "2026-05-19T14:00:00.000Z",
|
|
72
72
|
# excluded fields that must NOT leak into the canonical input
|
|
73
73
|
"receipt_id": "receipt-zzz",
|
|
@@ -93,7 +93,7 @@ _EXPECTED_KEYS = {
|
|
|
93
93
|
"referenced_evidence",
|
|
94
94
|
"sponsor_identity",
|
|
95
95
|
"sponsor_identity_context",
|
|
96
|
-
"
|
|
96
|
+
"verdict",
|
|
97
97
|
"verified_at",
|
|
98
98
|
}
|
|
99
99
|
|
|
@@ -21,7 +21,7 @@ _TOKEN = "test-token"
|
|
|
21
21
|
|
|
22
22
|
_OUTCOME_RELEASED: dict[str, Any] = {
|
|
23
23
|
"status": "released",
|
|
24
|
-
"
|
|
24
|
+
"verdict": "passed",
|
|
25
25
|
"reason": None,
|
|
26
26
|
"reason_code": None,
|
|
27
27
|
"receipt": None,
|
|
@@ -29,7 +29,7 @@ _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_rc4.json").read_text(
|
|
33
33
|
encoding="utf-8"
|
|
34
34
|
)
|
|
35
35
|
)
|
|
@@ -64,7 +64,7 @@ def test_decide_posts_five_field_body_with_bearer() -> None:
|
|
|
64
64
|
)
|
|
65
65
|
|
|
66
66
|
assert captured["method"] == "POST"
|
|
67
|
-
assert captured["url"] == "https://gate.example.com/v1/
|
|
67
|
+
assert captured["url"] == "https://gate.example.com/v1/review"
|
|
68
68
|
assert captured["auth"] == "Bearer test-token"
|
|
69
69
|
assert captured["content_type"] == "application/json"
|
|
70
70
|
# EXACTLY the 5 frozen wire fields, nothing more.
|
|
@@ -95,7 +95,7 @@ def test_base_url_trailing_slash_stripped() -> None:
|
|
|
95
95
|
proposed_action_id="p",
|
|
96
96
|
)
|
|
97
97
|
|
|
98
|
-
assert captured["url"] == "https://gate.example.com/v1/
|
|
98
|
+
assert captured["url"] == "https://gate.example.com/v1/review"
|
|
99
99
|
|
|
100
100
|
|
|
101
101
|
def test_nonce_auto_generated_when_omitted() -> None:
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
"""verify_receipt — end-to-end gate against real engine-minted receipts.
|
|
2
2
|
|
|
3
|
-
The fixtures (tests/fixtures/
|
|
4
|
-
|
|
5
|
-
engine @ 0.1.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
STOP and report; never edit the fixtures.
|
|
3
|
+
The fixtures (tests/fixtures/receipt_public_released_rc4.json and
|
|
4
|
+
receipt_public_blocked_rc4.json) are REAL receipts minted and ECDSA-signed
|
|
5
|
+
by the engine @ 0.1.0rc4 through its own decide path, under the verdict
|
|
6
|
+
canonical form (public half at tests/fixtures/bindable-test-rc4-2026-08.pem).
|
|
7
|
+
The chain-hash gates below prove the de-pydantic thin-client builder
|
|
8
|
+
reproduces the engine's chain_hash byte-for-byte. If one fails, the hand
|
|
9
|
+
port has drifted from the engine's signed form — that failure is the desync
|
|
10
|
+
alarm: STOP and report; never edit the fixtures.
|
|
11
|
+
|
|
12
|
+
They are minted with a test signing key rather than the production
|
|
13
|
+
bindable-public-2026-07 key, whose private half lives in Key Vault. What
|
|
14
|
+
they prove is unchanged: the ported canonical form and the engine's agree
|
|
15
|
+
byte-for-byte.
|
|
11
16
|
"""
|
|
12
17
|
|
|
13
18
|
from __future__ import annotations
|
|
@@ -25,12 +30,12 @@ from bindable.crypto import canonical_sha256
|
|
|
25
30
|
|
|
26
31
|
_FIX = Path(__file__).parent / "fixtures"
|
|
27
32
|
_RELEASED: dict[str, Any] = json.loads(
|
|
28
|
-
(_FIX / "
|
|
33
|
+
(_FIX / "receipt_public_released_rc4.json").read_text(encoding="utf-8")
|
|
29
34
|
)
|
|
30
35
|
_BLOCKED: dict[str, Any] = json.loads(
|
|
31
|
-
(_FIX / "
|
|
36
|
+
(_FIX / "receipt_public_blocked_rc4.json").read_text(encoding="utf-8")
|
|
32
37
|
)
|
|
33
|
-
_PUBLIC_KEY_PEM_BYTES = (_FIX / "bindable-
|
|
38
|
+
_PUBLIC_KEY_PEM_BYTES = (_FIX / "bindable-test-rc4-2026-08.pem").read_bytes()
|
|
34
39
|
_PUBLIC_KEY_PEM_STR = _PUBLIC_KEY_PEM_BYTES.decode("utf-8")
|
|
35
40
|
|
|
36
41
|
# A valid P-256 key that did NOT sign these receipts (from the sign vectors).
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.1.1"
|
|
@@ -1 +0,0 @@
|
|
|
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.
|
|
@@ -1 +0,0 @@
|
|
|
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}
|
|
@@ -1 +0,0 @@
|
|
|
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}
|
|
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
|