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,548 @@
|
|
|
1
|
+
"""Mechanically checked capability claims (ADR-029).
|
|
2
|
+
|
|
3
|
+
`docs/REQUIREMENTS_COVERAGE.md` used to be hand-written prose. Nothing broke
|
|
4
|
+
if a cited test was renamed, and nothing objected when a row said
|
|
5
|
+
"implemented" for a gate a claimant could walk through — which is exactly
|
|
6
|
+
what happened to EV-004 before ADR-024.
|
|
7
|
+
|
|
8
|
+
Here every claim names the symbols that must import and the files and tests
|
|
9
|
+
that must exist, plus an `honest_limit` recording what the row does NOT mean.
|
|
10
|
+
`verify()` resolves all of it. A stale claim fails the build instead of
|
|
11
|
+
quietly reading well, and the markdown table is generated from these
|
|
12
|
+
declarations rather than maintained beside them.
|
|
13
|
+
|
|
14
|
+
This checks that claimed code EXISTS and its tests are present. It cannot
|
|
15
|
+
check that the code is correct — that is what the suite and ContinuityBench
|
|
16
|
+
are for. A capability audit is an anti-drift device, not an oracle.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import importlib
|
|
22
|
+
import importlib.util
|
|
23
|
+
import os
|
|
24
|
+
import stat
|
|
25
|
+
from dataclasses import dataclass, field
|
|
26
|
+
from importlib.metadata import PackageNotFoundError, distribution
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
|
|
29
|
+
STATUSES = ("implemented", "partial", "contract-only", "out-of-scope")
|
|
30
|
+
|
|
31
|
+
ROOT = Path(__file__).resolve().parent.parent
|
|
32
|
+
|
|
33
|
+
_AUDIT_DATA_PARTS = ("share", "causal-continuity-engine", "audit")
|
|
34
|
+
_OWNED_AUDIT_MODULES = {
|
|
35
|
+
"verifiers.verify_proof": "verifiers/verify_proof.py",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass(frozen=True)
|
|
40
|
+
class Capability:
|
|
41
|
+
requirement: str
|
|
42
|
+
layer: str
|
|
43
|
+
summary: str
|
|
44
|
+
status: str
|
|
45
|
+
honest_limit: str
|
|
46
|
+
symbols: tuple[str, ...] = ()
|
|
47
|
+
files: tuple[str, ...] = ()
|
|
48
|
+
tests: tuple[str, ...] = ()
|
|
49
|
+
|
|
50
|
+
def __post_init__(self):
|
|
51
|
+
if self.status not in STATUSES:
|
|
52
|
+
raise ValueError(f"{self.requirement}: bad status {self.status!r}")
|
|
53
|
+
if self.status == "implemented" and not (self.symbols and self.tests):
|
|
54
|
+
raise ValueError(
|
|
55
|
+
f"{self.requirement}: 'implemented' requires both importable "
|
|
56
|
+
f"symbols and backing tests — otherwise it is prose")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _resolve(symbol: str):
|
|
60
|
+
"""'causal_continuity_engine.graph:Graph.provenance' -> the attribute, or raise."""
|
|
61
|
+
if not isinstance(symbol, str) or symbol.count(":") != 1:
|
|
62
|
+
raise ValueError("symbol must use exact module:attribute syntax")
|
|
63
|
+
module_name, attr_path = symbol.split(":", 1)
|
|
64
|
+
module_parts = module_name.split(".")
|
|
65
|
+
attr_parts = attr_path.split(".")
|
|
66
|
+
if (not module_name or not attr_path
|
|
67
|
+
or any(not part.isidentifier() for part in module_parts)
|
|
68
|
+
or any(not part.isidentifier() for part in attr_parts)):
|
|
69
|
+
raise ValueError(
|
|
70
|
+
"symbol must use exact module:attribute syntax with a dotted "
|
|
71
|
+
"attribute path")
|
|
72
|
+
audit_relative = _OWNED_AUDIT_MODULES.get(module_name)
|
|
73
|
+
if audit_relative is None:
|
|
74
|
+
obj = importlib.import_module(module_name)
|
|
75
|
+
else:
|
|
76
|
+
location = _locate_evidence(audit_relative)
|
|
77
|
+
if location is None:
|
|
78
|
+
raise ImportError(
|
|
79
|
+
f"owned audit module {module_name!r} is not available")
|
|
80
|
+
private_name = f"{__package__}._audit_{location.stem}"
|
|
81
|
+
spec = importlib.util.spec_from_file_location(private_name, location)
|
|
82
|
+
if spec is None or spec.loader is None:
|
|
83
|
+
raise ImportError(
|
|
84
|
+
f"owned audit module {module_name!r} cannot be loaded")
|
|
85
|
+
obj = importlib.util.module_from_spec(spec)
|
|
86
|
+
spec.loader.exec_module(obj)
|
|
87
|
+
for part in attr_parts:
|
|
88
|
+
obj = getattr(obj, part)
|
|
89
|
+
return obj
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _is_reparse(info: os.stat_result) -> bool:
|
|
93
|
+
return bool(
|
|
94
|
+
stat.S_ISLNK(info.st_mode)
|
|
95
|
+
or getattr(info, "st_file_attributes", 0)
|
|
96
|
+
& getattr(stat, "FILE_ATTRIBUTE_REPARSE_POINT", 0x400)
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _physical_checkout_file(root: Path, parts: tuple[str, ...]) -> bool:
|
|
101
|
+
"""Require every evidence component to be physical and the leaf regular."""
|
|
102
|
+
cursor = root
|
|
103
|
+
try:
|
|
104
|
+
root_info = os.lstat(cursor)
|
|
105
|
+
if _is_reparse(root_info) or not stat.S_ISDIR(root_info.st_mode):
|
|
106
|
+
return False
|
|
107
|
+
for index, part in enumerate(parts):
|
|
108
|
+
cursor = cursor / part
|
|
109
|
+
info = os.lstat(cursor)
|
|
110
|
+
if _is_reparse(info):
|
|
111
|
+
return False
|
|
112
|
+
if index < len(parts) - 1 and not stat.S_ISDIR(info.st_mode):
|
|
113
|
+
return False
|
|
114
|
+
return bool(parts) and stat.S_ISREG(info.st_mode)
|
|
115
|
+
except (OSError, ValueError):
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _safe_evidence_parts(relative: str) -> tuple[str, ...] | None:
|
|
120
|
+
if not isinstance(relative, str) or not relative or "\x00" in relative:
|
|
121
|
+
return None
|
|
122
|
+
normalized = relative.replace("\\", "/")
|
|
123
|
+
parts = tuple(normalized.split("/"))
|
|
124
|
+
path = Path(relative)
|
|
125
|
+
if (path.is_absolute() or path.drive or path.anchor
|
|
126
|
+
or any(part in {"", ".", ".."} for part in parts)):
|
|
127
|
+
return None
|
|
128
|
+
return parts
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _locate_evidence(relative: str) -> Path | None:
|
|
132
|
+
"""Locate physical audit evidence owned by this source or distribution."""
|
|
133
|
+
parts = _safe_evidence_parts(relative)
|
|
134
|
+
if parts is None:
|
|
135
|
+
return None
|
|
136
|
+
try:
|
|
137
|
+
root = ROOT.resolve(strict=True)
|
|
138
|
+
candidate = ROOT.joinpath(*parts)
|
|
139
|
+
resolved = candidate.resolve(strict=True)
|
|
140
|
+
except (OSError, ValueError):
|
|
141
|
+
resolved = None
|
|
142
|
+
root = None
|
|
143
|
+
if (resolved is not None and root is not None
|
|
144
|
+
and (resolved == root or root in resolved.parents)
|
|
145
|
+
and _physical_checkout_file(ROOT, parts)):
|
|
146
|
+
return resolved
|
|
147
|
+
# A checkout is authoritative for its own audit. Falling through to an
|
|
148
|
+
# older globally installed causal-continuity-engine would let a deleted
|
|
149
|
+
# test or specification survive the gate using unrelated bytes.
|
|
150
|
+
if (ROOT / "pyproject.toml").is_file():
|
|
151
|
+
return None
|
|
152
|
+
try:
|
|
153
|
+
installed = distribution("causal-continuity-engine")
|
|
154
|
+
except PackageNotFoundError:
|
|
155
|
+
return None
|
|
156
|
+
owned_suffix = _AUDIT_DATA_PARTS + parts
|
|
157
|
+
for entry in installed.files or ():
|
|
158
|
+
entry_parts = tuple(str(entry).replace("\\", "/").split("/"))
|
|
159
|
+
if entry_parts[-len(owned_suffix):] != owned_suffix:
|
|
160
|
+
continue
|
|
161
|
+
try:
|
|
162
|
+
located = Path(os.path.abspath(installed.locate_file(entry)))
|
|
163
|
+
if tuple(located.parts[-len(owned_suffix):]) != owned_suffix:
|
|
164
|
+
continue
|
|
165
|
+
owned_parent = located.parents[len(owned_suffix) - 1]
|
|
166
|
+
resolved = located.resolve(strict=True)
|
|
167
|
+
except (IndexError, OSError, TypeError, ValueError):
|
|
168
|
+
continue
|
|
169
|
+
if _physical_checkout_file(owned_parent, owned_suffix):
|
|
170
|
+
return resolved
|
|
171
|
+
return None
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _evidence_exists(relative: str) -> bool:
|
|
175
|
+
"""Find audit evidence in a checkout or wherever the wheel installed it."""
|
|
176
|
+
return _locate_evidence(relative) is not None
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
CAPABILITIES: tuple[Capability, ...] = (
|
|
180
|
+
Capability(
|
|
181
|
+
requirement="CCG-001", layer="Core",
|
|
182
|
+
summary="Persist source events append-only with tamper evidence",
|
|
183
|
+
status="implemented",
|
|
184
|
+
honest_limit="Append-only is enforced by SQLite triggers and hash chains, "
|
|
185
|
+
"not immutable media. A database operator can disable local "
|
|
186
|
+
"controls or truncate a tail; an externally published anchor "
|
|
187
|
+
"is required to expose that. Idempotency is scoped to "
|
|
188
|
+
"(tenant_id, project_id, idempotency_key); a source that "
|
|
189
|
+
"reuses a key in that scope for genuinely different content "
|
|
190
|
+
"is flagged, not merged.",
|
|
191
|
+
symbols=(
|
|
192
|
+
"causal_continuity_engine.store:Store.append_event",
|
|
193
|
+
"causal_continuity_engine.store:PayloadMismatchError",
|
|
194
|
+
),
|
|
195
|
+
files=("causal_continuity_engine/store.py",), tests=("tests/test_store_graph.py",)),
|
|
196
|
+
Capability(
|
|
197
|
+
requirement="CCG-004", layer="Core", summary="Bi-temporal validity",
|
|
198
|
+
status="implemented",
|
|
199
|
+
honest_limit="Valid time is carried and queried; it is not inferred. A source "
|
|
200
|
+
"that misreports when a fact became true is believed.",
|
|
201
|
+
symbols=(
|
|
202
|
+
"causal_continuity_engine.graph:Graph.as_of",
|
|
203
|
+
"causal_continuity_engine.graph:Graph.history",
|
|
204
|
+
),
|
|
205
|
+
files=("causal_continuity_engine/graph.py",), tests=("tests/test_store_graph.py",
|
|
206
|
+
"tests/test_regressions.py")),
|
|
207
|
+
Capability(
|
|
208
|
+
requirement="CCG-006", layer="Core", summary="Rebuild projections from the event log",
|
|
209
|
+
status="implemented",
|
|
210
|
+
honest_limit="Covers EVENT-DERIVED state. Runtime records (proofs, imported "
|
|
211
|
+
"sessions, manual checkpoints) are provenanced by signature and "
|
|
212
|
+
"audit log, not by replay — see the ADR-011 note. "
|
|
213
|
+
"The reverse comparator covers only rows with entirely event-only, "
|
|
214
|
+
"fully retained and replayable provenance; runtime and hybrid rows "
|
|
215
|
+
"remain outside that absence claim (ADR-091). "
|
|
216
|
+
"REBUILDABILITY IS BOUNDED BY THE RETENTION WINDOW: once "
|
|
217
|
+
"SEC-006 clears a payload the projection cannot be rebuilt "
|
|
218
|
+
"from the log, and `cce-engine rebuild` reports UNDECIDABLE (exit "
|
|
219
|
+
"3) rather than MATCHES or DIVERGES (ADR-063).",
|
|
220
|
+
symbols=("causal_continuity_engine.engine:Engine.rebuild_projection",
|
|
221
|
+
"causal_continuity_engine.engine:Engine.projection_fingerprint",
|
|
222
|
+
"causal_continuity_engine.engine:Engine.replay_completeness",
|
|
223
|
+
"causal_continuity_engine.engine:Engine.replay_agrees_where_replayable"),
|
|
224
|
+
files=("causal_continuity_engine/engine.py",), tests=("tests/test_engine_e2e.py",
|
|
225
|
+
"tests/test_regressions_round7.py",
|
|
226
|
+
"tests/test_regressions_round14_state_integrity.py")),
|
|
227
|
+
Capability(
|
|
228
|
+
requirement="CCG-008", layer="Core", summary="Bounded graph traversal",
|
|
229
|
+
status="implemented",
|
|
230
|
+
honest_limit="Budgets count distinct nodes (ADR-023). Bounds are per query; "
|
|
231
|
+
"there is no global cost budget across a request.",
|
|
232
|
+
symbols=(
|
|
233
|
+
"causal_continuity_engine.graph:Graph.dependents",
|
|
234
|
+
"causal_continuity_engine.graph:TraversalBudgetExceeded",
|
|
235
|
+
),
|
|
236
|
+
files=("causal_continuity_engine/graph.py",),
|
|
237
|
+
tests=("tests/test_regressions_round3.py",)),
|
|
238
|
+
Capability(
|
|
239
|
+
requirement="TM-005", layer="Core", summary="Distil to L3 only with provenance",
|
|
240
|
+
status="implemented",
|
|
241
|
+
honest_limit="Provenance must RESOLVE and not be self-referential (ADR-022). "
|
|
242
|
+
"That a source exists is checked; that it supports the claim is not.",
|
|
243
|
+
symbols=(
|
|
244
|
+
"causal_continuity_engine.memory:Memory.promote",
|
|
245
|
+
"causal_continuity_engine.memory:Memory._has_real_provenance",
|
|
246
|
+
),
|
|
247
|
+
files=("causal_continuity_engine/memory.py",),
|
|
248
|
+
tests=("tests/test_regressions_round3.py",)),
|
|
249
|
+
Capability(
|
|
250
|
+
requirement="AD-006", layer="Core", summary="Untrusted text cannot become control state",
|
|
251
|
+
status="implemented",
|
|
252
|
+
honest_limit="Injection screening is pattern-based and will miss novel phrasing. "
|
|
253
|
+
"The structural defences (authority typing, quarantine barred from "
|
|
254
|
+
"every tier and vacating any tier it already held) do not depend "
|
|
255
|
+
"on the patterns catching anything. The packet strip withholds "
|
|
256
|
+
"live nodes whose text matches quarantined content, which an "
|
|
257
|
+
"outsider can trigger by quoting them; those suppressions are "
|
|
258
|
+
"named and audited rather than prevented (ADR-061, ADR-062).",
|
|
259
|
+
symbols=("causal_continuity_engine.github:text_authority",
|
|
260
|
+
"causal_continuity_engine.extraction:DeterministicExtractor.extract",
|
|
261
|
+
"causal_continuity_engine.resume:ResumeComposer._strip_quarantined",
|
|
262
|
+
"causal_continuity_engine.memory:Memory.demote_from_any_tier"),
|
|
263
|
+
files=("causal_continuity_engine/extraction.py",
|
|
264
|
+
"causal_continuity_engine/github.py",
|
|
265
|
+
"causal_continuity_engine/resume.py"),
|
|
266
|
+
tests=("tests/test_regressions.py", "tests/test_regressions_round3.py",
|
|
267
|
+
"tests/test_regressions_round7.py")),
|
|
268
|
+
Capability(
|
|
269
|
+
requirement="CI-002", layer="Core", summary="Bounded blast-radius propagation",
|
|
270
|
+
status="implemented",
|
|
271
|
+
honest_limit="Edge strengths are configured constants, not learned or calibrated "
|
|
272
|
+
"against outcomes.",
|
|
273
|
+
symbols=("causal_continuity_engine.invalidation:InvalidationEngine.fire",),
|
|
274
|
+
files=("causal_continuity_engine/invalidation.py",),
|
|
275
|
+
tests=("tests/test_invalidation_resume.py",)),
|
|
276
|
+
Capability(
|
|
277
|
+
requirement="CI-005", layer="Core", summary="Human gate for low-confidence broad impact",
|
|
278
|
+
status="implemented",
|
|
279
|
+
honest_limit="A gated invalidation applies NO state change, so rejecting it "
|
|
280
|
+
"strands nothing (ADR-030). Completion treats unresolved open or "
|
|
281
|
+
"pending invalidations as control state for touched nodes, and "
|
|
282
|
+
"critical unresolved invalidations as project-wide (ADR-089). "
|
|
283
|
+
"The affected set still depends on typed graph coverage and "
|
|
284
|
+
"classification.",
|
|
285
|
+
symbols=("causal_continuity_engine.invalidation:InvalidationEngine.confirm",),
|
|
286
|
+
files=("causal_continuity_engine/invalidation.py",),
|
|
287
|
+
tests=("tests/test_regressions_round3.py",
|
|
288
|
+
"tests/test_regressions_round14_state_integrity.py")),
|
|
289
|
+
Capability(
|
|
290
|
+
requirement="MIG-002", layer="Core", summary="Token budget never drops L0",
|
|
291
|
+
status="implemented",
|
|
292
|
+
honest_limit="Token counts are estimated at 4 chars/token to stay model-neutral; "
|
|
293
|
+
"a specific tokenizer will differ. Capsule currency commits the "
|
|
294
|
+
"complete semantic control basis before trimming, so budget "
|
|
295
|
+
"omissions do not create drift; this does not prove model-semantic "
|
|
296
|
+
"equivalence (ADR-094).",
|
|
297
|
+
symbols=("causal_continuity_engine.resume:ResumeComposer.compose",),
|
|
298
|
+
files=("causal_continuity_engine/resume.py",),
|
|
299
|
+
tests=("tests/test_invalidation_resume.py",
|
|
300
|
+
"tests/test_regressions_round14_state_integrity.py")),
|
|
301
|
+
Capability(
|
|
302
|
+
requirement="PA-005", layer="Trust", summary="Absence of success is never success",
|
|
303
|
+
status="implemented",
|
|
304
|
+
honest_limit="Worst-result-wins across duplicate reports (ADR-015).",
|
|
305
|
+
symbols=("causal_continuity_engine.proof:ProofEnvelope.finalize",
|
|
306
|
+
"causal_continuity_engine.proof:_worst"),
|
|
307
|
+
files=("causal_continuity_engine/proof.py",), tests=("tests/test_trust.py",
|
|
308
|
+
"tests/test_regressions_round2.py")),
|
|
309
|
+
Capability(
|
|
310
|
+
requirement="PA-004", layer="Trust", summary="Proof bound to its subject",
|
|
311
|
+
status="implemented",
|
|
312
|
+
honest_limit="Completion requires a scoped, subject-bound, single-use proof "
|
|
313
|
+
"(ADR-018). Binding is checked against SIGNED fields only.",
|
|
314
|
+
symbols=("causal_continuity_engine.engine:Engine.complete_task",
|
|
315
|
+
"causal_continuity_engine.engine:_proof_covers"),
|
|
316
|
+
files=("causal_continuity_engine/engine.py",),
|
|
317
|
+
tests=("tests/test_regressions_round2.py",)),
|
|
318
|
+
Capability(
|
|
319
|
+
requirement="EV-004", layer="Trust", summary="Requirement-specific verifier policy",
|
|
320
|
+
status="implemented",
|
|
321
|
+
honest_limit="Policy-mandated verifiers are non-substitutable ONLY when pinned "
|
|
322
|
+
"with a command (ADR-024). A bare-name entry is satisfiable by a "
|
|
323
|
+
"command the claimant chooses; the grade caps at D and the proof "
|
|
324
|
+
"records it under evidence_context.unpinned_required.",
|
|
325
|
+
symbols=("causal_continuity_engine.policy:PolicyEngine.required_verifier_defs",
|
|
326
|
+
"causal_continuity_engine.verifiers:VerifierSpec.from_policy"),
|
|
327
|
+
files=("causal_continuity_engine/policy.py", "causal_continuity_engine/verifiers.py"),
|
|
328
|
+
tests=("tests/test_regressions_round4.py",)),
|
|
329
|
+
Capability(
|
|
330
|
+
requirement="EV-005", layer="Trust",
|
|
331
|
+
summary="Reject evidence that no longer describes current deliverables",
|
|
332
|
+
status="implemented",
|
|
333
|
+
honest_limit="Covers declared artifacts and signed continuity inputs. Every "
|
|
334
|
+
"declared artifact route and nested descendant must remain physical "
|
|
335
|
+
"under the work tree; symlink/reparse routing fails closed. These "
|
|
336
|
+
"standard-library checks are not kernel isolation and cannot "
|
|
337
|
+
"eliminate privileged concurrent filesystem mutation (ADR-096). "
|
|
338
|
+
"An undeclared artifact has no staleness surface.",
|
|
339
|
+
symbols=("causal_continuity_engine.engine:Engine.proof_currency",
|
|
340
|
+
"causal_continuity_engine.engine:Engine._artifact_digests",
|
|
341
|
+
"causal_continuity_engine.engine:Engine.complete_task"),
|
|
342
|
+
files=("causal_continuity_engine/engine.py",),
|
|
343
|
+
tests=("tests/test_regressions_round6.py",
|
|
344
|
+
"tests/test_regressions_round14_state_integrity.py")),
|
|
345
|
+
Capability(
|
|
346
|
+
requirement="EV-006", layer="Trust", summary="Verifier sandboxing and limits",
|
|
347
|
+
status="partial",
|
|
348
|
+
honest_limit="Timeouts, output caps, a scrubbed environment with a named threat "
|
|
349
|
+
"per entry, and a guard on commands that delegate to another "
|
|
350
|
+
"program. NOT kernel isolation, and NOT a defence against in-process "
|
|
351
|
+
"forgery: a test must import the code under test, so the subject can "
|
|
352
|
+
"rewrite the runner's report. Use value-oracle checks and mutation "
|
|
353
|
+
"probes for that (ADR-025), and containers for SEC-008.",
|
|
354
|
+
symbols=("causal_continuity_engine.verifiers:VerifierRunner._build_env",
|
|
355
|
+
"causal_continuity_engine.verifiers:check_command_safety"),
|
|
356
|
+
files=("causal_continuity_engine/verifiers.py",),
|
|
357
|
+
tests=("tests/test_regressions_round4.py",)),
|
|
358
|
+
Capability(
|
|
359
|
+
requirement="EV-007", layer="Trust", summary="Expose unverified surface area",
|
|
360
|
+
status="implemented",
|
|
361
|
+
honest_limit="The mutation probe is a mechanical LOWER BOUND: it proves a check "
|
|
362
|
+
"binds to a deliverable's existence and content, never that it "
|
|
363
|
+
"checks the right property (ADR-027). Per-file line coverage is "
|
|
364
|
+
"still not computed. Only a FAILED check counts as a detection; "
|
|
365
|
+
"a check that crashed establishes nothing and leaves binding "
|
|
366
|
+
"undetermined rather than proven (ADR-066).",
|
|
367
|
+
symbols=("causal_continuity_engine.evidence:run_mutation_probe",
|
|
368
|
+
"causal_continuity_engine.evidence:grade_evidence",
|
|
369
|
+
"causal_continuity_engine.evidence:MutationReport.bound",
|
|
370
|
+
"causal_continuity_engine.engine:Engine.probe_evidence"),
|
|
371
|
+
files=("causal_continuity_engine/evidence.py",), tests=("tests/test_regressions_round4.py",
|
|
372
|
+
"tests/test_regressions_round7.py")),
|
|
373
|
+
Capability(
|
|
374
|
+
requirement="AUT-005", layer="Trust", summary="Automatic autonomy downgrade",
|
|
375
|
+
status="implemented",
|
|
376
|
+
honest_limit="Fires on failed proof, failed migration challenge and critical "
|
|
377
|
+
"invalidation. Clearing is an explicit human act.",
|
|
378
|
+
symbols=("causal_continuity_engine.policy:PolicyEngine.downgrade",),
|
|
379
|
+
files=("causal_continuity_engine/policy.py",), tests=("tests/test_regressions.py",)),
|
|
380
|
+
Capability(
|
|
381
|
+
requirement="SEC-003", layer="Platform", summary="Capture modes",
|
|
382
|
+
status="implemented",
|
|
383
|
+
honest_limit="Redaction is applied before persistence and extraction reads the "
|
|
384
|
+
"persisted form (ADR-016), so nothing dropped reaches the graph. "
|
|
385
|
+
"Secret patterns are a denylist and will miss novel formats.",
|
|
386
|
+
symbols=("causal_continuity_engine.redaction:apply_capture_mode",
|
|
387
|
+
"causal_continuity_engine.redaction:redact_text"),
|
|
388
|
+
files=("causal_continuity_engine/redaction.py",), tests=("tests/test_github_redaction.py",
|
|
389
|
+
"tests/test_regressions_round3.py")),
|
|
390
|
+
Capability(
|
|
391
|
+
requirement="SEC-007", layer="Platform", summary="Append-only audit log",
|
|
392
|
+
status="implemented",
|
|
393
|
+
honest_limit="Triggers refuse mutation, a hash chain detects rewrites even if the "
|
|
394
|
+
"triggers are dropped, and a closed typed anchor can detect tail "
|
|
395
|
+
"truncation and optionally bind tenant/project scope. Malformed "
|
|
396
|
+
"anchors fail cleanly, but an unbound anchor proves no scope and ANY "
|
|
397
|
+
"anchor matters externally ONLY if published somewhere the operator "
|
|
398
|
+
"does not control. CCE ships no publication channel (ADR-028, "
|
|
399
|
+
"ADR-095).",
|
|
400
|
+
symbols=("causal_continuity_engine.store:Store.verify_chain",
|
|
401
|
+
"causal_continuity_engine.store:Store.export_anchor",
|
|
402
|
+
"causal_continuity_engine.store:Store.verify_against_anchor"),
|
|
403
|
+
files=("causal_continuity_engine/store.py",), tests=("tests/test_regressions_round4.py",
|
|
404
|
+
"tests/test_regressions_round14_state_integrity.py")),
|
|
405
|
+
Capability(
|
|
406
|
+
requirement="SEC-002", layer="Platform", summary="Tenant isolation",
|
|
407
|
+
status="partial",
|
|
408
|
+
honest_limit="Every row is tenant-scoped and queries are project-scoped, but "
|
|
409
|
+
"enforcement is application-level. Database row-level security "
|
|
410
|
+
"needs the PostgreSQL deployment (ADR-011).",
|
|
411
|
+
symbols=("causal_continuity_engine.store:Store.append_event",),
|
|
412
|
+
files=("causal_continuity_engine/store.py",), tests=("tests/test_store_graph.py",)),
|
|
413
|
+
Capability(
|
|
414
|
+
requirement="SEC-008", layer="Platform", summary="Deny-by-default tool sandboxing",
|
|
415
|
+
status="partial",
|
|
416
|
+
honest_limit="The runner records the network policy it was ASKED for; it does not "
|
|
417
|
+
"enforce it. Kernel-level isolation is a deployment concern.",
|
|
418
|
+
symbols=("causal_continuity_engine.verifiers:VerifierRunner.run",),
|
|
419
|
+
files=("causal_continuity_engine/verifiers.py",), tests=("tests/test_trust.py",)),
|
|
420
|
+
Capability(
|
|
421
|
+
requirement="PA-003", layer="Trust",
|
|
422
|
+
summary="Third-party-verifiable proof envelopes",
|
|
423
|
+
status="implemented",
|
|
424
|
+
honest_limit="SPEC.md is normative and verifiers/verify_proof.py "
|
|
425
|
+
"implements it importing nothing from "
|
|
426
|
+
"causal_continuity_engine; every committed "
|
|
427
|
+
"vector pins both implementations. This shows the spec is "
|
|
428
|
+
"unambiguous enough to reimplement — it does NOT constitute "
|
|
429
|
+
"implementation independence, because both have one author. "
|
|
430
|
+
"A stranger still needs the key fingerprint out of band, and "
|
|
431
|
+
"CCE ships no channel for that (ADR-031, ADR-057). "
|
|
432
|
+
"hmac-sha256 IS NOT THIRD-PARTY VERIFIABLE AT ALL: without "
|
|
433
|
+
"the secret the verifier returns UNVERIFIED, never VALID, "
|
|
434
|
+
"because an untouched envelope and a resealed forgery are "
|
|
435
|
+
"indistinguishable to a keyless party. Only lamport-sha256/1 "
|
|
436
|
+
"plus an out-of-band fingerprint reaches VALID for a "
|
|
437
|
+
"stranger (ADR-057).",
|
|
438
|
+
symbols=("verifiers.verify_proof:verify",
|
|
439
|
+
"verifiers.verify_proof:derive_fingerprint"),
|
|
440
|
+
files=("SPEC.md", "verifiers/verify_proof.py", "vectors/generate.py"),
|
|
441
|
+
tests=("tests/test_conformance.py", "tests/test_regressions_round7.py")),
|
|
442
|
+
Capability(
|
|
443
|
+
requirement="PLT-005", layer="Platform", summary="Web dashboard",
|
|
444
|
+
status="out-of-scope",
|
|
445
|
+
honest_limit="Not built. The HTTP API serves the same data."),
|
|
446
|
+
Capability(
|
|
447
|
+
requirement="NFR-001", layer="Platform", summary="Webhook SLOs",
|
|
448
|
+
status="out-of-scope",
|
|
449
|
+
honest_limit="Latency targets need deployed infrastructure and representative "
|
|
450
|
+
"traffic; local execution cannot evidence a deployment SLO."),
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
@dataclass
|
|
455
|
+
class CapabilityResult:
|
|
456
|
+
capability: Capability
|
|
457
|
+
ok: bool
|
|
458
|
+
problems: list[str] = field(default_factory=list)
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def verify(capabilities=CAPABILITIES) -> list[CapabilityResult]:
|
|
462
|
+
"""Resolve every declared symbol, file and test. Claims must be real."""
|
|
463
|
+
results = []
|
|
464
|
+
for cap in capabilities:
|
|
465
|
+
problems: list[str] = []
|
|
466
|
+
for symbol in cap.symbols:
|
|
467
|
+
try:
|
|
468
|
+
_resolve(symbol)
|
|
469
|
+
except (ImportError, AttributeError, ValueError) as exc:
|
|
470
|
+
problems.append(f"symbol {symbol!r} does not resolve: {exc}")
|
|
471
|
+
for rel in cap.files + cap.tests:
|
|
472
|
+
if not _evidence_exists(rel):
|
|
473
|
+
problems.append(f"missing file {rel!r}")
|
|
474
|
+
if cap.status in ("implemented", "partial") and not cap.honest_limit:
|
|
475
|
+
problems.append("no honest_limit recorded")
|
|
476
|
+
results.append(CapabilityResult(cap, not problems, problems))
|
|
477
|
+
return results
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
def render_markdown(capabilities=CAPABILITIES) -> str:
|
|
481
|
+
"""Generate the coverage table FROM the declarations."""
|
|
482
|
+
lines = [
|
|
483
|
+
"# Requirement coverage — generated, do not edit by hand",
|
|
484
|
+
"",
|
|
485
|
+
(
|
|
486
|
+
"Generated by `python -m causal_continuity_engine.capabilities --write`. Every row is "
|
|
487
|
+
"checked"
|
|
488
|
+
),
|
|
489
|
+
"by `TestCapabilitiesAudit` in `tests/test_regressions_round4.py`: the",
|
|
490
|
+
"symbols must import and the files and tests must exist, so a claim",
|
|
491
|
+
"cannot outlive the code behind it.",
|
|
492
|
+
"",
|
|
493
|
+
"The **honest limit** column is the point of this table. A status of",
|
|
494
|
+
"`implemented` means the mechanism exists and is tested — never that",
|
|
495
|
+
"the requirement is solved in full.",
|
|
496
|
+
"",
|
|
497
|
+
"| Requirement | Layer | Capability | Status | Honest limit |",
|
|
498
|
+
"|---|---|---|---|---|",
|
|
499
|
+
]
|
|
500
|
+
for cap in capabilities:
|
|
501
|
+
limit = cap.honest_limit.replace("\n", " ").replace("|", "\\|")
|
|
502
|
+
lines.append(
|
|
503
|
+
f"| {cap.requirement} | {cap.layer} | {cap.summary} | "
|
|
504
|
+
f"`{cap.status}` | {limit} |")
|
|
505
|
+
lines += [
|
|
506
|
+
"",
|
|
507
|
+
"## What this table is not",
|
|
508
|
+
"",
|
|
509
|
+
"It records that claimed code exists and has tests. It cannot record",
|
|
510
|
+
"that the code is correct; the suite and ContinuityBench address that,",
|
|
511
|
+
"and ContinuityBench is self-scored on deterministic fixtures.",
|
|
512
|
+
"",
|
|
513
|
+
"The public meaning of every requirement ID is defined in",
|
|
514
|
+
"[REQUIREMENTS.md](REQUIREMENTS.md). Narrative implementation status and evidence,",
|
|
515
|
+
"including IDs with no capability declaration here, remain in",
|
|
516
|
+
"[REQUIREMENTS_COVERAGE.md](REQUIREMENTS_COVERAGE.md).",
|
|
517
|
+
]
|
|
518
|
+
return "\n".join(lines) + "\n"
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
def main(argv: list[str] | None = None) -> int:
|
|
522
|
+
import sys as _sys
|
|
523
|
+
argv = _sys.argv[1:] if argv is None else argv
|
|
524
|
+
if "--write" in argv:
|
|
525
|
+
target = ROOT / "docs" / "CAPABILITIES.md"
|
|
526
|
+
# Generated documentation is a build input. Platform-default text
|
|
527
|
+
# encoding made the Windows gate emit CP1252 and CRLF while Linux
|
|
528
|
+
# committed UTF-8 and LF, so a clean checkout failed its own audit.
|
|
529
|
+
target.write_text(render_markdown(), encoding="utf-8", newline="\n")
|
|
530
|
+
print(f"wrote {target.relative_to(ROOT)}")
|
|
531
|
+
results = verify()
|
|
532
|
+
failed = [r for r in results if not r.ok]
|
|
533
|
+
for r in failed:
|
|
534
|
+
print(f"FAIL {r.capability.requirement}: " + "; ".join(r.problems))
|
|
535
|
+
counts: dict[str, int] = {}
|
|
536
|
+
for r in results:
|
|
537
|
+
counts[r.capability.status] = counts.get(r.capability.status, 0) + 1
|
|
538
|
+
print(f"{len(results)} capability claims checked: "
|
|
539
|
+
+ ", ".join(f"{n} {s}" for s, n in sorted(counts.items())))
|
|
540
|
+
if failed:
|
|
541
|
+
print(f"{len(failed)} claim(s) do not resolve")
|
|
542
|
+
return 1
|
|
543
|
+
print("every claim resolves to real symbols, files and tests")
|
|
544
|
+
return 0
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
if __name__ == "__main__":
|
|
548
|
+
raise SystemExit(main())
|