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.
- causal_continuity_engine/__init__.py +21 -0
- causal_continuity_engine/api.py +1264 -0
- causal_continuity_engine/capabilities.py +548 -0
- causal_continuity_engine/capsule.py +996 -0
- causal_continuity_engine/cli.py +1338 -0
- causal_continuity_engine/core.py +468 -0
- causal_continuity_engine/engine.py +4354 -0
- causal_continuity_engine/evidence.py +971 -0
- causal_continuity_engine/extraction.py +263 -0
- causal_continuity_engine/github.py +657 -0
- causal_continuity_engine/graph.py +809 -0
- causal_continuity_engine/invalidation.py +1040 -0
- causal_continuity_engine/lamport.py +306 -0
- causal_continuity_engine/learning.py +466 -0
- causal_continuity_engine/memory.py +610 -0
- causal_continuity_engine/ontology.py +108 -0
- causal_continuity_engine/partial.py +309 -0
- causal_continuity_engine/policy.py +940 -0
- causal_continuity_engine/proof.py +920 -0
- causal_continuity_engine/py.typed +1 -0
- causal_continuity_engine/redaction.py +94 -0
- causal_continuity_engine/resume.py +653 -0
- causal_continuity_engine/store.py +1174 -0
- causal_continuity_engine/verifiers.py +1006 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/SPEC.md +515 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/benchmarks/__init__.py +0 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/benchmarks/continuitybench/__init__.py +0 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/benchmarks/continuitybench/run.py +146 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/benchmarks/continuitybench/scenarios.py +544 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/schemas/cce.anchor.v1.json +62 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/schemas/cce.capsule.v1.json +112 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/schemas/cce.continuity-receipt.v1.json +315 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/schemas/cce.event.v1.json +73 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/schemas/cce.proof-predicate.v1.json +35 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/schemas/cce.proof.v1.json +242 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/schemas/cce.recovery.v1.json +109 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/schemas/cce.resume.v1.json +332 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/__init__.py +1 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/schema_validation.py +61 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_api_contract.py +1051 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_cli.py +664 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_conformance.py +758 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_distribution_environment.py +26 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_engine_e2e.py +286 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_github_redaction.py +193 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_instrument_validation.py +535 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_invalidation_resume.py +205 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_learning_partial_api.py +380 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_memory_extraction.py +151 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions.py +406 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round10_release.py +842 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round11_deep_integrity.py +831 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round12_release_blockers.py +206 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round13_jcs.py +212 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round14_state_integrity.py +1056 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round15_capsule_invalidation_github.py +251 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round15_public_boundaries.py +561 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round15_storage_boundaries.py +707 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round16_cli_api_core_capabilities.py +285 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round2.py +388 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round3.py +490 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round4.py +557 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round5.py +583 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round6.py +559 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round7.py +579 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round8_boundaries.py +558 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round8_engine.py +843 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round8_portability.py +2250 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round8_state.py +705 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round9_integrity.py +885 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_regressions_round9_scope_freshness.py +179 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_release_boundary_validation.py +676 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_release_controls.py +840 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_sqlite_lifecycle.py +52 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_store_graph.py +172 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/tests/test_trust.py +398 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/action_intent_wrong_type.json +170 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/actor_wrong_type.json +170 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/created_at_invalid_calendar_date.json +172 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/created_at_noncanonical_offset.json +172 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/empty_required_authoritative_failure.json +114 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/empty_required_authoritative_pass.json +107 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/empty_required_no_results.json +99 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/empty_required_self_asserted_pass.json +105 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/generate.py +498 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/incomplete_failed_check.json +168 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/index.json +240 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/input_item_wrong_type.json +163 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/invalid_result_value.json +170 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/lamport_fingerprint_spoof.json +1458 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/lamport_no_registry.json +1454 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/lamport_truncated_key.json +1454 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/missing_required_field.json +157 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/non_binary64_integer.json +174 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/policy_decision_wrong_type.json +146 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/retry_cannot_launder_a_failure.json +197 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/self_asserted_cannot_satisfy.json +173 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/self_assertion_cannot_poison_authoritative_pass.json +194 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/status_contradicts_contents.json +173 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/summary_contradicts_contents.json +170 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/tamper_statement.json +172 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/tamper_statement_resealed.json +172 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/task_id_under_unrelated_field.json +172 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/unbound_task.json +165 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/unknown_action_intent_field.json +173 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/unknown_algorithm.json +172 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/unknown_schema_version.json +170 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/unknown_top_level_field.json +175 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/unverified_forged_body_no_key.json +170 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/unverified_hmac_no_key.json +170 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/valid_hmac.json +172 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/valid_hmac_jcs_edges.json +186 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/valid_hmac_unicode.json +172 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/valid_lamport.json +1458 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/vectors/wrong_project.json +173 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/verifiers/__init__.py +0 -0
- causal_continuity_engine-0.1.0.data/data/share/causal-continuity-engine/audit/verifiers/verify_proof.py +1109 -0
- causal_continuity_engine-0.1.0.dist-info/METADATA +720 -0
- causal_continuity_engine-0.1.0.dist-info/RECORD +124 -0
- causal_continuity_engine-0.1.0.dist-info/WHEEL +5 -0
- causal_continuity_engine-0.1.0.dist-info/entry_points.txt +2 -0
- causal_continuity_engine-0.1.0.dist-info/licenses/LICENSE.txt +202 -0
- causal_continuity_engine-0.1.0.dist-info/licenses/NOTICE +6 -0
- 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)
|