causal-continuity-engine 0.1.0__py3-none-any.whl

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 (124) hide show
  1. causal_continuity_engine/__init__.py +21 -0
  2. causal_continuity_engine/api.py +1264 -0
  3. causal_continuity_engine/capabilities.py +548 -0
  4. causal_continuity_engine/capsule.py +996 -0
  5. causal_continuity_engine/cli.py +1338 -0
  6. causal_continuity_engine/core.py +468 -0
  7. causal_continuity_engine/engine.py +4354 -0
  8. causal_continuity_engine/evidence.py +971 -0
  9. causal_continuity_engine/extraction.py +263 -0
  10. causal_continuity_engine/github.py +657 -0
  11. causal_continuity_engine/graph.py +809 -0
  12. causal_continuity_engine/invalidation.py +1040 -0
  13. causal_continuity_engine/lamport.py +306 -0
  14. causal_continuity_engine/learning.py +466 -0
  15. causal_continuity_engine/memory.py +610 -0
  16. causal_continuity_engine/ontology.py +108 -0
  17. causal_continuity_engine/partial.py +309 -0
  18. causal_continuity_engine/policy.py +940 -0
  19. causal_continuity_engine/proof.py +920 -0
  20. causal_continuity_engine/py.typed +1 -0
  21. causal_continuity_engine/redaction.py +94 -0
  22. causal_continuity_engine/resume.py +653 -0
  23. causal_continuity_engine/store.py +1174 -0
  24. causal_continuity_engine/verifiers.py +1006 -0
  25. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/SPEC.md +515 -0
  26. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/benchmarks/__init__.py +0 -0
  27. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/benchmarks/continuitybench/__init__.py +0 -0
  28. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/benchmarks/continuitybench/run.py +146 -0
  29. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/benchmarks/continuitybench/scenarios.py +544 -0
  30. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/schemas/cce.anchor.v1.json +62 -0
  31. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/schemas/cce.capsule.v1.json +112 -0
  32. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/schemas/cce.continuity-receipt.v1.json +315 -0
  33. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/schemas/cce.event.v1.json +73 -0
  34. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/schemas/cce.proof-predicate.v1.json +35 -0
  35. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/schemas/cce.proof.v1.json +242 -0
  36. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/schemas/cce.recovery.v1.json +109 -0
  37. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/schemas/cce.resume.v1.json +332 -0
  38. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/__init__.py +1 -0
  39. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/schema_validation.py +61 -0
  40. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_api_contract.py +1051 -0
  41. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_cli.py +664 -0
  42. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_conformance.py +758 -0
  43. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_distribution_environment.py +26 -0
  44. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_engine_e2e.py +286 -0
  45. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_github_redaction.py +193 -0
  46. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_instrument_validation.py +535 -0
  47. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_invalidation_resume.py +205 -0
  48. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_learning_partial_api.py +380 -0
  49. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_memory_extraction.py +151 -0
  50. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions.py +406 -0
  51. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round10_release.py +842 -0
  52. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round11_deep_integrity.py +831 -0
  53. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round12_release_blockers.py +206 -0
  54. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round13_jcs.py +212 -0
  55. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round14_state_integrity.py +1056 -0
  56. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round15_capsule_invalidation_github.py +251 -0
  57. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round15_public_boundaries.py +561 -0
  58. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round15_storage_boundaries.py +707 -0
  59. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round16_cli_api_core_capabilities.py +285 -0
  60. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round2.py +388 -0
  61. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round3.py +490 -0
  62. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round4.py +557 -0
  63. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round5.py +583 -0
  64. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round6.py +559 -0
  65. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round7.py +579 -0
  66. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round8_boundaries.py +558 -0
  67. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round8_engine.py +843 -0
  68. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round8_portability.py +2250 -0
  69. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round8_state.py +705 -0
  70. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round9_integrity.py +885 -0
  71. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round9_scope_freshness.py +179 -0
  72. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_release_boundary_validation.py +676 -0
  73. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_release_controls.py +840 -0
  74. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_sqlite_lifecycle.py +52 -0
  75. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_store_graph.py +172 -0
  76. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_trust.py +398 -0
  77. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/action_intent_wrong_type.json +170 -0
  78. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/actor_wrong_type.json +170 -0
  79. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/created_at_invalid_calendar_date.json +172 -0
  80. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/created_at_noncanonical_offset.json +172 -0
  81. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/empty_required_authoritative_failure.json +114 -0
  82. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/empty_required_authoritative_pass.json +107 -0
  83. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/empty_required_no_results.json +99 -0
  84. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/empty_required_self_asserted_pass.json +105 -0
  85. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/generate.py +498 -0
  86. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/incomplete_failed_check.json +168 -0
  87. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/index.json +240 -0
  88. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/input_item_wrong_type.json +163 -0
  89. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/invalid_result_value.json +170 -0
  90. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/lamport_fingerprint_spoof.json +1458 -0
  91. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/lamport_no_registry.json +1454 -0
  92. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/lamport_truncated_key.json +1454 -0
  93. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/missing_required_field.json +157 -0
  94. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/non_binary64_integer.json +174 -0
  95. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/policy_decision_wrong_type.json +146 -0
  96. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/retry_cannot_launder_a_failure.json +197 -0
  97. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/self_asserted_cannot_satisfy.json +173 -0
  98. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/self_assertion_cannot_poison_authoritative_pass.json +194 -0
  99. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/status_contradicts_contents.json +173 -0
  100. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/summary_contradicts_contents.json +170 -0
  101. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/tamper_statement.json +172 -0
  102. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/tamper_statement_resealed.json +172 -0
  103. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/task_id_under_unrelated_field.json +172 -0
  104. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/unbound_task.json +165 -0
  105. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/unknown_action_intent_field.json +173 -0
  106. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/unknown_algorithm.json +172 -0
  107. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/unknown_schema_version.json +170 -0
  108. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/unknown_top_level_field.json +175 -0
  109. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/unverified_forged_body_no_key.json +170 -0
  110. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/unverified_hmac_no_key.json +170 -0
  111. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/valid_hmac.json +172 -0
  112. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/valid_hmac_jcs_edges.json +186 -0
  113. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/valid_hmac_unicode.json +172 -0
  114. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/valid_lamport.json +1458 -0
  115. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/wrong_project.json +173 -0
  116. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/verifiers/__init__.py +0 -0
  117. causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/verifiers/verify_proof.py +1109 -0
  118. causal_continuity_engine-0.1.0.dist-info/METADATA +720 -0
  119. causal_continuity_engine-0.1.0.dist-info/RECORD +124 -0
  120. causal_continuity_engine-0.1.0.dist-info/WHEEL +5 -0
  121. causal_continuity_engine-0.1.0.dist-info/entry_points.txt +2 -0
  122. causal_continuity_engine-0.1.0.dist-info/licenses/LICENSE.txt +202 -0
  123. causal_continuity_engine-0.1.0.dist-info/licenses/NOTICE +6 -0
  124. causal_continuity_engine-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,251 @@
1
+ from __future__ import annotations
2
+
3
+ import copy
4
+ import itertools
5
+
6
+ import pytest
7
+
8
+ from causal_continuity_engine.capsule import CapsuleError
9
+ from causal_continuity_engine.engine import Engine
10
+ from causal_continuity_engine.github import continuity_conclusion
11
+
12
+ TENANT = "ten_round15_trust"
13
+ PROJECT = "prj_round15_trust"
14
+
15
+
16
+ @pytest.fixture
17
+ def engine():
18
+ instance = Engine(tenant_id=TENANT)
19
+ instance.create_project(
20
+ "round15 trust", project_id=PROJECT,
21
+ config={"require_proof_for": []})
22
+ yield instance
23
+ instance.close()
24
+
25
+
26
+ @pytest.fixture
27
+ def capsule(engine):
28
+ engine.graph.put_node(
29
+ entity_type="artifact", tenant_id=TENANT, project_id=PROJECT,
30
+ status="recorded",
31
+ data={"kind": "environment", "python": "3.13", "os": "test"})
32
+ result = engine.capsules.export(
33
+ tenant_id=TENANT, project_id=PROJECT, session_id=None,
34
+ source_model="source-model", source_runtime="source-runtime",
35
+ target_adapter="target-adapter", signer=engine.signer)
36
+ assert engine.capsules.challenge(result)["passed"] is True
37
+ return result
38
+
39
+
40
+ def _database_snapshot(engine: Engine) -> tuple[str, ...]:
41
+ return tuple(engine.store._conn.iterdump())
42
+
43
+
44
+ def _set_path(value: dict, path: tuple[str, ...], replacement) -> None:
45
+ target = value
46
+ for key in path[:-1]:
47
+ target = target[key]
48
+ target[path[-1]] = replacement
49
+
50
+
51
+ @pytest.mark.parametrize("malformed", [None, "", False, []])
52
+ def test_capsule_challenge_rejects_non_object_input(engine, malformed):
53
+ before = _database_snapshot(engine)
54
+ with pytest.raises(CapsuleError, match="challenge input"):
55
+ engine.capsules.challenge(malformed)
56
+ assert _database_snapshot(engine) == before
57
+
58
+
59
+ @pytest.mark.parametrize(
60
+ ("path", "malformed"),
61
+ [
62
+ (("resume_packet",), {}),
63
+ (("observable_state",), {}),
64
+ (("resume_packet", "environment"), "python-3.13"),
65
+ (("resume_packet", "trust", "gaps"), {}),
66
+ (("observable_state", "active_assumptions"), {}),
67
+ (("observable_state", "open_invalidations"), {}),
68
+ (("observable_state", "open_invalidations"), [""]),
69
+ (("observable_state", "active_assumptions"), [{
70
+ "node_id": "asm_round15",
71
+ "statement": "dependency is stable",
72
+ "status": "active",
73
+ "criticality": "high",
74
+ "confidence": float("nan"),
75
+ }]),
76
+ ],
77
+ )
78
+ def test_capsule_challenge_rejects_malformed_control_surfaces_before_state(
79
+ engine, capsule, path, malformed):
80
+ candidate = copy.deepcopy(capsule)
81
+ _set_path(candidate, path, malformed)
82
+ before = _database_snapshot(engine)
83
+ with pytest.raises(CapsuleError):
84
+ engine.capsules.challenge(candidate)
85
+ assert _database_snapshot(engine) == before
86
+
87
+
88
+ @pytest.mark.parametrize(
89
+ ("field", "malformed"),
90
+ list(itertools.product(
91
+ ("source_model", "source_runtime", "target_adapter"),
92
+ (None, "", " ", 0, False, "\x1b[31m", "\u202eoverride", "x" * 257),
93
+ )),
94
+ )
95
+ def test_capsule_export_rejects_unsafe_text_before_sign_or_write(
96
+ engine, field, malformed):
97
+ class CountingSigner:
98
+ def __init__(self, signer):
99
+ self.signer = signer
100
+ self.calls = 0
101
+
102
+ def sign(self, body):
103
+ self.calls += 1
104
+ return self.signer.sign(body)
105
+
106
+ signer = CountingSigner(engine.signer)
107
+ values = {
108
+ "source_model": "source-model",
109
+ "source_runtime": "source-runtime",
110
+ "target_adapter": "target-adapter",
111
+ }
112
+ values[field] = malformed
113
+ before = _database_snapshot(engine)
114
+ with pytest.raises(CapsuleError):
115
+ engine.capsules.export(
116
+ tenant_id=TENANT, project_id=PROJECT, session_id=None,
117
+ signer=signer, **values)
118
+ assert signer.calls == 0
119
+ assert _database_snapshot(engine) == before
120
+
121
+
122
+ @pytest.mark.parametrize(
123
+ ("field", "malformed"),
124
+ list(itertools.product(
125
+ ("target_model", "target_runtime", "actor"),
126
+ (None, "", " ", 0, False, "\x1b[31m", "\u202eoverride", "x" * 257),
127
+ )),
128
+ )
129
+ def test_capsule_import_rejects_unsafe_text_before_verify_or_write(
130
+ engine, capsule, field, malformed):
131
+ class CountingVerifier:
132
+ algorithm = engine.signer.algorithm
133
+
134
+ def __init__(self):
135
+ self.calls = 0
136
+
137
+ def verify(self, _body):
138
+ self.calls += 1
139
+ return True
140
+
141
+ signer = CountingVerifier()
142
+ values = {
143
+ "target_model": "target-model",
144
+ "target_runtime": "target-runtime",
145
+ "actor": "reviewer",
146
+ }
147
+ values[field] = malformed
148
+ before = _database_snapshot(engine)
149
+ with pytest.raises(CapsuleError):
150
+ engine.capsules.import_capsule(capsule, signer=signer, **values)
151
+ assert signer.calls == 0
152
+ assert _database_snapshot(engine) == before
153
+
154
+
155
+ def _pending_invalidation(engine: Engine) -> tuple[dict, dict]:
156
+ target = engine.graph.put_node(
157
+ entity_type="assumption", tenant_id=TENANT, project_id=PROJECT,
158
+ status="active", criticality="high",
159
+ data={"statement": "dependency is stable"})
160
+ pending = engine.invalidation.fire(
161
+ tenant_id=TENANT, project_id=PROJECT, target_node_id=target.id,
162
+ trigger_type="dependency_drift", trigger_confidence=0.4)
163
+ assert pending["status"] == "pending_confirmation"
164
+ return target, pending
165
+
166
+
167
+ @pytest.mark.parametrize(
168
+ "malformed", [None, 0, 1, "true", "false", [], {}])
169
+ def test_invalidation_confirmation_requires_an_exact_boolean(engine, malformed):
170
+ _target, pending = _pending_invalidation(engine)
171
+ before = _database_snapshot(engine)
172
+ with pytest.raises(ValueError, match="accept must be a boolean"):
173
+ engine.invalidation.confirm(
174
+ pending["node_id"], actor="reviewer", accept=malformed)
175
+ assert _database_snapshot(engine) == before
176
+
177
+
178
+ @pytest.mark.parametrize(
179
+ "malformed",
180
+ [None, "", " ", 0, False, "\x1b[31m", "\u202eoverride", "x" * 257],
181
+ )
182
+ def test_invalidation_confirmation_requires_a_safe_actor(engine, malformed):
183
+ _target, pending = _pending_invalidation(engine)
184
+ before = _database_snapshot(engine)
185
+ with pytest.raises(ValueError, match="confirmation actor"):
186
+ engine.invalidation.confirm(
187
+ pending["node_id"], actor=malformed, accept=True)
188
+ assert _database_snapshot(engine) == before
189
+
190
+
191
+ @pytest.mark.parametrize(
192
+ ("accept", "invalidation_status", "target_status"),
193
+ [(False, "rejected", "active"), (True, "open", "invalidated")],
194
+ )
195
+ def test_invalidation_confirmation_preserves_true_and_false_semantics(
196
+ engine, accept, invalidation_status, target_status):
197
+ target, pending = _pending_invalidation(engine)
198
+ result = engine.invalidation.confirm(
199
+ pending["node_id"], actor="reviewer", accept=accept)
200
+ assert result["status"] == invalidation_status
201
+ assert result["data"]["accepted"] is accept
202
+ assert engine.graph.get(target.id)["status"] == target_status
203
+ audit = engine.store.audit_entries("invalidation.confirm")[-1]
204
+ assert audit["actor"] == "reviewer"
205
+ assert f"accepted={accept}" in audit["detail"]
206
+
207
+
208
+ def _expected_conclusion(values: dict[str, bool]) -> str:
209
+ if values["trust_unavailable"]:
210
+ return "cancelled"
211
+ if (values["critical_invalidation"] or values["authority_conflict"]
212
+ or values["approval_needed"]):
213
+ return "action_required"
214
+ if not values["proof_ok"]:
215
+ return "failure"
216
+ if values["packet_current"]:
217
+ return "success"
218
+ return "neutral"
219
+
220
+
221
+ def test_continuity_conclusion_complete_boolean_truth_table():
222
+ fields = (
223
+ "critical_invalidation", "proof_ok", "packet_current",
224
+ "authority_conflict", "approval_needed", "trust_unavailable")
225
+ for combination in itertools.product((False, True), repeat=len(fields)):
226
+ values = dict(zip(fields, combination))
227
+ assert continuity_conclusion(**values) == _expected_conclusion(values)
228
+
229
+
230
+ @pytest.mark.parametrize(
231
+ ("field", "malformed"),
232
+ list(itertools.product(
233
+ (
234
+ "critical_invalidation", "proof_ok", "packet_current",
235
+ "authority_conflict", "approval_needed", "trust_unavailable",
236
+ ),
237
+ (None, 0, 1, "true", "false", [], {}),
238
+ )),
239
+ )
240
+ def test_continuity_conclusion_rejects_non_boolean_inputs(field, malformed):
241
+ values = {
242
+ "critical_invalidation": False,
243
+ "proof_ok": True,
244
+ "packet_current": True,
245
+ "authority_conflict": False,
246
+ "approval_needed": False,
247
+ "trust_unavailable": False,
248
+ }
249
+ values[field] = malformed
250
+ with pytest.raises(ValueError, match=field):
251
+ continuity_conclusion(**values)