From d61ba327fa575269a0ea2d7372cd3c62a10d0cc9 Mon Sep 17 00:00:00 2001 From: tegwick Date: Sat, 3 Oct 2026 22:25:47 +0200 Subject: [PATCH] Accept governed boolean leaves in memo envelopes. type() excluded bool even though bool subclasses int, so schema fields requires_new_bind, required_ack, and sealed failed as invalid_type. Unknown leaves still fail closed. No submitter, token, or write-back change. Assistant: grok Assistant-Session: 01a10286-9185-7bb2-b411-89cf6ab4c0c6 --- api/services/informed_decision_contract.py | 3 ++- tests/test_memo_forwarding.py | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/api/services/informed_decision_contract.py b/api/services/informed_decision_contract.py index f5c8b48..8c1f0f2 100644 --- a/api/services/informed_decision_contract.py +++ b/api/services/informed_decision_contract.py @@ -359,7 +359,8 @@ def _bounded_tree(value: Any) -> None: elif isinstance(node, list): for child in node: walk(child, key) - elif type(node) in (int, float) or node is None: + elif isinstance(node, bool) or type(node) in (int, float) or node is None: + # type() excludes bool; bool subclasses int, so it must be named first. return else: raise ContractError("invalid_type") diff --git a/tests/test_memo_forwarding.py b/tests/test_memo_forwarding.py index e09a7b7..79d2d29 100644 --- a/tests/test_memo_forwarding.py +++ b/tests/test_memo_forwarding.py @@ -132,6 +132,25 @@ def test_envelope_is_bounded_and_does_not_echo_secrets(): assert unknown.value.code == "unknown_field" +def test_governed_boolean_leaves_round_trip(): + checked = envelope(memo={ + "id": MEMO, + "question": "Accept the forwarded draft?", + "sealed": False, + "binding": {"target": {"requires_new_bind": True}}, + "highlights": [{"required_ack": True}], + }) + memo = checked["memo"] + assert memo["sealed"] is False + assert memo["binding"]["target"]["requires_new_bind"] is True + assert memo["highlights"][0]["required_ack"] is True + rejected = envelope() + rejected["memo"]["marker"] = object() + with pytest.raises(ContractError) as error: + validate_envelope(rejected) + assert error.value.code == "invalid_type" + + def test_pose_replay_conflict_and_asserted_provenance(tmp_path): clock = Clock() transport = Transport([(201, receipt())])