core-runtime-engine 11.5.1__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.
- core_runtime/__init__.py +10 -0
- core_runtime/__version__.py +17 -0
- core_runtime/cli/__init__.py +7 -0
- core_runtime/cli/__main__.py +22 -0
- core_runtime/cli/bump_version.py +176 -0
- core_runtime/cli/contract_preflight.py +69 -0
- core_runtime/cli/create_domain.py +66 -0
- core_runtime/cli/doctor.py +44 -0
- core_runtime/cli/inventory.py +85 -0
- core_runtime/cli/lint.py +8 -0
- core_runtime/cli/main.py +579 -0
- core_runtime/cli/release_check.py +65 -0
- core_runtime/cli/repair_artifact_paths.py +48 -0
- core_runtime/cli/sync_template.py +67 -0
- core_runtime/cli/validate.py +73 -0
- core_runtime/core/__init__.py +34 -0
- core_runtime/core/audit_event.py +760 -0
- core_runtime/core/audit_trail_index.py +100 -0
- core_runtime/core/canonicalization.py +55 -0
- core_runtime/core/contract_evaluator.py +945 -0
- core_runtime/core/contract_executability.py +307 -0
- core_runtime/core/contract_loader.py +57 -0
- core_runtime/core/contract_probes.py +630 -0
- core_runtime/core/contract_program.py +131 -0
- core_runtime/core/contract_program_registry.py +104 -0
- core_runtime/core/contract_program_v2.py +126 -0
- core_runtime/core/dsk_v3.py +142 -0
- core_runtime/core/explainability.py +2115 -0
- core_runtime/core/numeric_normalization.py +120 -0
- core_runtime/core/rule_anchor.py +1388 -0
- core_runtime/core/schema_fingerprint.py +69 -0
- core_runtime/core/sensor_evidence.py +548 -0
- core_runtime/data/contracts/CoreAnchor.sol +109 -0
- core_runtime/data/contracts/CoreRuleAnchor.abi.json +111 -0
- core_runtime/data/contracts/CoreRuleAnchor.bin +1 -0
- core_runtime/data/contracts/CoreRuleAnchor.build.json +20 -0
- core_runtime/data/contracts/CoreRuleAnchor.runtime.bin +1 -0
- core_runtime/data/contracts/CoreRuleAnchor.sol +74 -0
- core_runtime/data/package_data_manifest.v1.json +173 -0
- core_runtime/data/schemas/core/causal_trace.v1.json +141 -0
- core_runtime/data/schemas/core/context_gate.v1.json +38 -0
- core_runtime/data/schemas/core/context_threshold.v1.json +46 -0
- core_runtime/data/schemas/core/contract_program.v1.json +186 -0
- core_runtime/data/schemas/core/contract_program.v2.json +187 -0
- core_runtime/data/schemas/core/control_decision.v1.json +114 -0
- core_runtime/data/schemas/core/dsk.v3.json +105 -0
- core_runtime/data/schemas/core/effect_result.v1.json +39 -0
- core_runtime/data/schemas/core/entropy_signal.v1.json +108 -0
- core_runtime/data/schemas/core/execution_receipt.v1.json +93 -0
- core_runtime/data/schemas/core/frozen_release_manifest.v1.json +87 -0
- core_runtime/data/schemas/core/frozen_release_manifest.v2.json +72 -0
- core_runtime/data/schemas/core/frozen_release_manifest.v3.json +38 -0
- core_runtime/data/schemas/core/frozen_release_manifest.v4.json +72 -0
- core_runtime/data/schemas/core/frozen_release_manifest.v5.json +38 -0
- core_runtime/data/schemas/core/frozen_release_manifest.v6.json +116 -0
- core_runtime/data/schemas/core/frozen_release_manifest.v7.json +37 -0
- core_runtime/data/schemas/core/frozen_release_manifest.v8.json +114 -0
- core_runtime/data/schemas/core/frozen_rule_set.v1.json +282 -0
- core_runtime/data/schemas/core/memory_artifact.v1.json +120 -0
- core_runtime/data/schemas/core/memory_generation_result.v1.json +37 -0
- core_runtime/data/schemas/core/operational_learning_event.v1.json +63 -0
- core_runtime/data/schemas/core/pattern_candidate.v1.json +114 -0
- core_runtime/data/schemas/core/physical_safety_assurance_case.v1.json +676 -0
- core_runtime/data/schemas/core/policy_lifecycle.v1.json +99 -0
- core_runtime/data/schemas/core/retention_manifest.v1.json +53 -0
- core_runtime/data/schemas/core/reversibility_policy.v1.json +107 -0
- core_runtime/data/schemas/core/rule_anchor_batch.v1.json +93 -0
- core_runtime/data/schemas/core/rule_anchor_chain_evidence.v1.json +56 -0
- core_runtime/data/schemas/core/rule_approval.v1.json +49 -0
- core_runtime/data/schemas/core/rule_approval_request.v1.json +42 -0
- core_runtime/data/schemas/core/state_transition.v1.json +115 -0
- core_runtime/data/schemas/core/task_closeout.v1.json +47 -0
- core_runtime/data/schemas/core/template_promotion_candidate.v1.json +83 -0
- core_runtime/data/schemas/core/unsigned_rule_anchor_deployment.v1.json +106 -0
- core_runtime/data/schemas/core/unsigned_rule_anchor_transaction.v1.json +116 -0
- core_runtime/tooling/__init__.py +48 -0
- core_runtime/tooling/bump_version.py +900 -0
- core_runtime/tooling/contract_preflight.py +293 -0
- core_runtime/tooling/create_domain.py +254 -0
- core_runtime/tooling/diagnostics.py +129 -0
- core_runtime/tooling/doctor.py +482 -0
- core_runtime/tooling/file_inventory.py +182 -0
- core_runtime/tooling/json_checks.py +100 -0
- core_runtime/tooling/release_check.py +1017 -0
- core_runtime/tooling/repair_artifact_paths.py +458 -0
- core_runtime/tooling/report_writer.py +176 -0
- core_runtime/tooling/repository_inventory.py +399 -0
- core_runtime/tooling/safety_checks.py +172 -0
- core_runtime/tooling/sync_template.py +303 -0
- core_runtime/tooling/validation.py +507 -0
- core_runtime/tooling/version_inventory.py +256 -0
- core_runtime_engine-11.5.1.dist-info/METADATA +35 -0
- core_runtime_engine-11.5.1.dist-info/RECORD +96 -0
- core_runtime_engine-11.5.1.dist-info/WHEEL +5 -0
- core_runtime_engine-11.5.1.dist-info/entry_points.txt +2 -0
- core_runtime_engine-11.5.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
"""Read-only environment diagnostics for CORE tooling."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import platform
|
|
7
|
+
import shutil
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from core_runtime.tooling.diagnostics import DiagnosticCollection
|
|
15
|
+
from core_runtime.tooling.version_inventory import VersionInventory
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class DoctorItem:
|
|
20
|
+
"""A single environment readiness check."""
|
|
21
|
+
|
|
22
|
+
kind: str
|
|
23
|
+
name: str
|
|
24
|
+
path: str
|
|
25
|
+
status: str
|
|
26
|
+
details: dict[str, Any] = field(default_factory=dict)
|
|
27
|
+
|
|
28
|
+
def to_dict(self) -> dict[str, Any]:
|
|
29
|
+
result = {
|
|
30
|
+
"kind": self.kind,
|
|
31
|
+
"name": self.name,
|
|
32
|
+
"path": self.path,
|
|
33
|
+
"status": self.status,
|
|
34
|
+
}
|
|
35
|
+
if self.details:
|
|
36
|
+
result["details"] = self.details
|
|
37
|
+
return result
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class DoctorReport:
|
|
42
|
+
"""Normalized report for `core-runtime doctor`."""
|
|
43
|
+
|
|
44
|
+
tool: str
|
|
45
|
+
command: str
|
|
46
|
+
status: str
|
|
47
|
+
mutation_performed: bool = False
|
|
48
|
+
summary: dict[str, Any] = field(default_factory=dict)
|
|
49
|
+
items: list[DoctorItem] = field(default_factory=list)
|
|
50
|
+
diagnostics: DiagnosticCollection = field(default_factory=DiagnosticCollection)
|
|
51
|
+
|
|
52
|
+
def to_dict(self) -> dict[str, Any]:
|
|
53
|
+
counts = self.diagnostics.count_by_severity()
|
|
54
|
+
summary = dict(self.summary)
|
|
55
|
+
summary.setdefault("info", counts["info"])
|
|
56
|
+
summary.setdefault("warning", counts["warning"])
|
|
57
|
+
summary.setdefault("error", counts["error"])
|
|
58
|
+
summary.setdefault("blocked", counts["blocked"])
|
|
59
|
+
return {
|
|
60
|
+
"tool": self.tool,
|
|
61
|
+
"command": self.command,
|
|
62
|
+
"status": self.status,
|
|
63
|
+
"mutation_performed": self.mutation_performed,
|
|
64
|
+
"summary": summary,
|
|
65
|
+
"items": [item.to_dict() for item in self.items],
|
|
66
|
+
"diagnostics": [d.to_dict() for d in self.diagnostics.diagnostics],
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
def to_markdown(self) -> str:
|
|
70
|
+
counts = self.diagnostics.count_by_severity()
|
|
71
|
+
summary = dict(self.summary)
|
|
72
|
+
summary.setdefault("info", counts["info"])
|
|
73
|
+
summary.setdefault("warning", counts["warning"])
|
|
74
|
+
summary.setdefault("error", counts["error"])
|
|
75
|
+
summary.setdefault("blocked", counts["blocked"])
|
|
76
|
+
lines: list[str] = []
|
|
77
|
+
lines.append("# CORE doctor report")
|
|
78
|
+
lines.append("")
|
|
79
|
+
lines.append("## Summary")
|
|
80
|
+
lines.append("")
|
|
81
|
+
lines.append("| Metric | Value |")
|
|
82
|
+
lines.append("|--------|-------|")
|
|
83
|
+
lines.append("| Tool | {0} |".format(self.tool))
|
|
84
|
+
lines.append("| Command | {0} |".format(self.command))
|
|
85
|
+
lines.append("| Status | {0} |".format(self.status.upper()))
|
|
86
|
+
lines.append("| Mutation Performed | No |")
|
|
87
|
+
lines.append("| Checks | {0} |".format(summary.get("item_count", len(self.items))))
|
|
88
|
+
lines.append("| Info | {0} |".format(counts["info"]))
|
|
89
|
+
lines.append("| Warning | {0} |".format(counts["warning"]))
|
|
90
|
+
lines.append("| Error | {0} |".format(counts["error"]))
|
|
91
|
+
lines.append("| Blocked | {0} |".format(counts["blocked"]))
|
|
92
|
+
lines.append("")
|
|
93
|
+
|
|
94
|
+
if self.items:
|
|
95
|
+
lines.append("## Checks")
|
|
96
|
+
lines.append("")
|
|
97
|
+
lines.append("| Kind | Name | Path | Status |")
|
|
98
|
+
lines.append("|------|------|------|--------|")
|
|
99
|
+
for item in self.items:
|
|
100
|
+
lines.append("| {0} | {1} | {2} | {3} |".format(item.kind, item.name, item.path, item.status))
|
|
101
|
+
lines.append("")
|
|
102
|
+
|
|
103
|
+
if self.diagnostics.diagnostics:
|
|
104
|
+
lines.append("## Diagnostics")
|
|
105
|
+
lines.append("")
|
|
106
|
+
lines.append("| Severity | Code | Path | Message |")
|
|
107
|
+
lines.append("|----------|------|------|---------|")
|
|
108
|
+
for diagnostic in self.diagnostics.diagnostics:
|
|
109
|
+
path = diagnostic.path or "-"
|
|
110
|
+
message = diagnostic.message.replace("|", "\\|")
|
|
111
|
+
lines.append(
|
|
112
|
+
"| {0} | {1} | {2} | {3} |".format(
|
|
113
|
+
diagnostic.severity.value.upper(),
|
|
114
|
+
diagnostic.code,
|
|
115
|
+
path,
|
|
116
|
+
message,
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
lines.append("")
|
|
120
|
+
|
|
121
|
+
return "\n".join(lines)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class RepositoryDoctor:
|
|
125
|
+
"""Build deterministic environment diagnostics for local CORE tooling."""
|
|
126
|
+
|
|
127
|
+
def __init__(self, repo_root: Path):
|
|
128
|
+
self.repo_root = repo_root
|
|
129
|
+
self.version_inventory = VersionInventory(repo_root)
|
|
130
|
+
|
|
131
|
+
def build_report(self, diagnostics: DiagnosticCollection | None = None) -> DoctorReport:
|
|
132
|
+
diagnostics = diagnostics or DiagnosticCollection()
|
|
133
|
+
items: list[DoctorItem] = []
|
|
134
|
+
|
|
135
|
+
python_version = self._check_python_version(diagnostics, items)
|
|
136
|
+
git_info = self._check_git(diagnostics, items)
|
|
137
|
+
self._check_tools(diagnostics, items)
|
|
138
|
+
self._check_verify_release_help(diagnostics, items)
|
|
139
|
+
self._check_release_report_permissions(diagnostics, items)
|
|
140
|
+
self._check_version_bearing_files(git_info, diagnostics, items)
|
|
141
|
+
|
|
142
|
+
passed = sum(1 for item in items if item.status == "passed")
|
|
143
|
+
warnings = sum(1 for item in items if item.status == "warning")
|
|
144
|
+
skipped = sum(1 for item in items if item.status == "skipped")
|
|
145
|
+
status = "pass"
|
|
146
|
+
if diagnostics.has_blocked():
|
|
147
|
+
status = "blocked"
|
|
148
|
+
elif diagnostics.has_errors():
|
|
149
|
+
status = "error"
|
|
150
|
+
elif warnings:
|
|
151
|
+
status = "warning"
|
|
152
|
+
|
|
153
|
+
summary = {
|
|
154
|
+
"item_count": len(items),
|
|
155
|
+
"passed": passed,
|
|
156
|
+
"warnings": warnings,
|
|
157
|
+
"skipped": skipped,
|
|
158
|
+
"python_version": python_version,
|
|
159
|
+
"repo_root": str(self.repo_root),
|
|
160
|
+
}
|
|
161
|
+
if git_info is not None:
|
|
162
|
+
summary["git_branch"] = git_info["branch"]
|
|
163
|
+
summary["git_version"] = git_info["version"]
|
|
164
|
+
|
|
165
|
+
return DoctorReport(
|
|
166
|
+
tool="core-runtime doctor",
|
|
167
|
+
command="doctor",
|
|
168
|
+
status=status,
|
|
169
|
+
summary=summary,
|
|
170
|
+
items=items,
|
|
171
|
+
diagnostics=diagnostics,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
def _check_python_version(self, diagnostics: DiagnosticCollection, items: list[DoctorItem]) -> str:
|
|
175
|
+
current = platform.python_version()
|
|
176
|
+
required = "3.11"
|
|
177
|
+
status = "passed"
|
|
178
|
+
if sys.version_info < (3, 11):
|
|
179
|
+
diagnostics.add_warning(
|
|
180
|
+
code="core.doctor.python_version_too_low",
|
|
181
|
+
message="Python version is below the supported baseline",
|
|
182
|
+
path="python",
|
|
183
|
+
expected=">= 3.11",
|
|
184
|
+
actual=current,
|
|
185
|
+
)
|
|
186
|
+
status = "warning"
|
|
187
|
+
items.append(
|
|
188
|
+
DoctorItem(
|
|
189
|
+
kind="environment",
|
|
190
|
+
name="python",
|
|
191
|
+
path="python",
|
|
192
|
+
status=status,
|
|
193
|
+
details={"version": current, "required": required},
|
|
194
|
+
)
|
|
195
|
+
)
|
|
196
|
+
return current
|
|
197
|
+
|
|
198
|
+
def _check_tools(self, diagnostics: DiagnosticCollection, items: list[DoctorItem]) -> None:
|
|
199
|
+
for tool in ("pytest", "ruff", "mypy"):
|
|
200
|
+
version = self._tool_version(tool)
|
|
201
|
+
if version is None:
|
|
202
|
+
diagnostics.add_warning(
|
|
203
|
+
code=f"core.doctor.{tool}_missing",
|
|
204
|
+
message=f"Required development tool not found: {tool}",
|
|
205
|
+
path=tool,
|
|
206
|
+
expected="installed",
|
|
207
|
+
actual="missing",
|
|
208
|
+
)
|
|
209
|
+
items.append(
|
|
210
|
+
DoctorItem(
|
|
211
|
+
kind="tool",
|
|
212
|
+
name=tool,
|
|
213
|
+
path=tool,
|
|
214
|
+
status="warning",
|
|
215
|
+
details={"version": None, "available": False},
|
|
216
|
+
)
|
|
217
|
+
)
|
|
218
|
+
continue
|
|
219
|
+
items.append(
|
|
220
|
+
DoctorItem(
|
|
221
|
+
kind="tool",
|
|
222
|
+
name=tool,
|
|
223
|
+
path=tool,
|
|
224
|
+
status="passed",
|
|
225
|
+
details={"version": version, "available": True},
|
|
226
|
+
)
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
def _check_git(self, diagnostics: DiagnosticCollection, items: list[DoctorItem]) -> dict[str, str] | None:
|
|
230
|
+
version = self._tool_version("git")
|
|
231
|
+
if version is None:
|
|
232
|
+
diagnostics.add_warning(
|
|
233
|
+
code="core.doctor.git_missing",
|
|
234
|
+
message="Git is not available in PATH",
|
|
235
|
+
path="git",
|
|
236
|
+
expected="installed",
|
|
237
|
+
actual="missing",
|
|
238
|
+
)
|
|
239
|
+
items.append(
|
|
240
|
+
DoctorItem(
|
|
241
|
+
kind="tool",
|
|
242
|
+
name="git",
|
|
243
|
+
path="git",
|
|
244
|
+
status="warning",
|
|
245
|
+
details={"version": None, "available": False},
|
|
246
|
+
)
|
|
247
|
+
)
|
|
248
|
+
return None
|
|
249
|
+
|
|
250
|
+
branch = self._git_branch()
|
|
251
|
+
if branch is None:
|
|
252
|
+
diagnostics.add_warning(
|
|
253
|
+
code="core.doctor.git_branch_unavailable",
|
|
254
|
+
message="Git branch could not be determined",
|
|
255
|
+
path=str(self.repo_root),
|
|
256
|
+
expected="branch name",
|
|
257
|
+
actual="unknown",
|
|
258
|
+
)
|
|
259
|
+
status = "warning"
|
|
260
|
+
branch = "unknown"
|
|
261
|
+
elif branch == "HEAD":
|
|
262
|
+
diagnostics.add_warning(
|
|
263
|
+
code="core.doctor.git_detached_head",
|
|
264
|
+
message="Git repository is in detached HEAD state",
|
|
265
|
+
path=str(self.repo_root),
|
|
266
|
+
expected="named branch",
|
|
267
|
+
actual="HEAD",
|
|
268
|
+
)
|
|
269
|
+
status = "warning"
|
|
270
|
+
else:
|
|
271
|
+
status = "passed"
|
|
272
|
+
|
|
273
|
+
items.append(
|
|
274
|
+
DoctorItem(
|
|
275
|
+
kind="tool",
|
|
276
|
+
name="git",
|
|
277
|
+
path="git",
|
|
278
|
+
status=status,
|
|
279
|
+
details={"version": version, "branch": branch, "available": True},
|
|
280
|
+
)
|
|
281
|
+
)
|
|
282
|
+
return {"version": version, "branch": branch}
|
|
283
|
+
|
|
284
|
+
def _check_verify_release_help(self, diagnostics: DiagnosticCollection, items: list[DoctorItem]) -> None:
|
|
285
|
+
script = self.repo_root / "scripts" / "verify_release.py"
|
|
286
|
+
if not script.is_file():
|
|
287
|
+
diagnostics.add_warning(
|
|
288
|
+
code="core.doctor.verify_release_missing",
|
|
289
|
+
message="verify_release.py script is missing",
|
|
290
|
+
path="scripts/verify_release.py",
|
|
291
|
+
expected="exists",
|
|
292
|
+
actual="missing",
|
|
293
|
+
)
|
|
294
|
+
items.append(
|
|
295
|
+
DoctorItem(
|
|
296
|
+
kind="script",
|
|
297
|
+
name="verify_release.py",
|
|
298
|
+
path="scripts/verify_release.py",
|
|
299
|
+
status="warning",
|
|
300
|
+
details={"available": False},
|
|
301
|
+
)
|
|
302
|
+
)
|
|
303
|
+
return
|
|
304
|
+
|
|
305
|
+
result = subprocess.run(
|
|
306
|
+
[sys.executable, str(script), "--help"],
|
|
307
|
+
cwd=self.repo_root,
|
|
308
|
+
capture_output=True,
|
|
309
|
+
text=True,
|
|
310
|
+
check=False,
|
|
311
|
+
)
|
|
312
|
+
if result.returncode != 0:
|
|
313
|
+
diagnostics.add_warning(
|
|
314
|
+
code="core.doctor.verify_release_help_failed",
|
|
315
|
+
message="verify_release.py --help did not exit cleanly",
|
|
316
|
+
path="scripts/verify_release.py",
|
|
317
|
+
expected="exit 0",
|
|
318
|
+
actual=str(result.returncode),
|
|
319
|
+
)
|
|
320
|
+
status = "warning"
|
|
321
|
+
else:
|
|
322
|
+
status = "passed"
|
|
323
|
+
items.append(
|
|
324
|
+
DoctorItem(
|
|
325
|
+
kind="script",
|
|
326
|
+
name="verify_release.py",
|
|
327
|
+
path="scripts/verify_release.py",
|
|
328
|
+
status=status,
|
|
329
|
+
details={"available": True, "returncode": result.returncode},
|
|
330
|
+
)
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
def _check_release_report_permissions(self, diagnostics: DiagnosticCollection, items: list[DoctorItem]) -> None:
|
|
334
|
+
releases_dir = self.repo_root / "docs" / "releases"
|
|
335
|
+
if not releases_dir.is_dir():
|
|
336
|
+
diagnostics.add_warning(
|
|
337
|
+
code="core.doctor.release_dir_missing",
|
|
338
|
+
message="Release notes directory is missing",
|
|
339
|
+
path="docs/releases",
|
|
340
|
+
expected="directory",
|
|
341
|
+
actual="missing",
|
|
342
|
+
)
|
|
343
|
+
items.append(
|
|
344
|
+
DoctorItem(
|
|
345
|
+
kind="path",
|
|
346
|
+
name="release-notes",
|
|
347
|
+
path="docs/releases",
|
|
348
|
+
status="warning",
|
|
349
|
+
details={"exists": False, "writable": False},
|
|
350
|
+
)
|
|
351
|
+
)
|
|
352
|
+
return
|
|
353
|
+
|
|
354
|
+
writable = os.access(releases_dir, os.W_OK)
|
|
355
|
+
if not writable:
|
|
356
|
+
diagnostics.add_warning(
|
|
357
|
+
code="core.doctor.release_dir_not_writable",
|
|
358
|
+
message="Release notes directory is not writable",
|
|
359
|
+
path="docs/releases",
|
|
360
|
+
expected="writable",
|
|
361
|
+
actual="read-only",
|
|
362
|
+
)
|
|
363
|
+
status = "warning"
|
|
364
|
+
else:
|
|
365
|
+
status = "passed"
|
|
366
|
+
|
|
367
|
+
items.append(
|
|
368
|
+
DoctorItem(
|
|
369
|
+
kind="path",
|
|
370
|
+
name="release-notes",
|
|
371
|
+
path="docs/releases",
|
|
372
|
+
status=status,
|
|
373
|
+
details={"exists": True, "writable": writable},
|
|
374
|
+
)
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
def _check_version_bearing_files(
|
|
378
|
+
self,
|
|
379
|
+
git_info: dict[str, str] | None,
|
|
380
|
+
diagnostics: DiagnosticCollection,
|
|
381
|
+
items: list[DoctorItem],
|
|
382
|
+
) -> None:
|
|
383
|
+
sources = self.version_inventory.discover()
|
|
384
|
+
version_paths = [str(source.path.relative_to(self.repo_root)) for source in sources]
|
|
385
|
+
missing = [source.name for source in sources if not source.path.exists()]
|
|
386
|
+
if missing:
|
|
387
|
+
diagnostics.add_warning(
|
|
388
|
+
code="core.doctor.version_source_missing",
|
|
389
|
+
message="One or more version-bearing files are missing",
|
|
390
|
+
path=missing[0],
|
|
391
|
+
expected="exists",
|
|
392
|
+
actual=", ".join(missing),
|
|
393
|
+
)
|
|
394
|
+
items.append(
|
|
395
|
+
DoctorItem(
|
|
396
|
+
kind="repo",
|
|
397
|
+
name="version-bearing-files",
|
|
398
|
+
path="version-bearing-files",
|
|
399
|
+
status="warning",
|
|
400
|
+
details={"missing": missing, "dirty": []},
|
|
401
|
+
)
|
|
402
|
+
)
|
|
403
|
+
return
|
|
404
|
+
|
|
405
|
+
if git_info is None:
|
|
406
|
+
items.append(
|
|
407
|
+
DoctorItem(
|
|
408
|
+
kind="repo",
|
|
409
|
+
name="version-bearing-files",
|
|
410
|
+
path="version-bearing-files",
|
|
411
|
+
status="skipped",
|
|
412
|
+
details={"dirty": [], "reason": "git unavailable"},
|
|
413
|
+
)
|
|
414
|
+
)
|
|
415
|
+
diagnostics.add_warning(
|
|
416
|
+
code="core.doctor.version_dirty_unknown",
|
|
417
|
+
message="Cannot inspect version-bearing files without git",
|
|
418
|
+
path="version-bearing-files",
|
|
419
|
+
expected="git available",
|
|
420
|
+
actual="git missing",
|
|
421
|
+
)
|
|
422
|
+
return
|
|
423
|
+
|
|
424
|
+
result = subprocess.run(
|
|
425
|
+
["git", "-C", str(self.repo_root), "status", "--porcelain", "--", *version_paths],
|
|
426
|
+
capture_output=True,
|
|
427
|
+
text=True,
|
|
428
|
+
check=False,
|
|
429
|
+
)
|
|
430
|
+
dirty: list[str] = []
|
|
431
|
+
for line in result.stdout.splitlines():
|
|
432
|
+
if len(line) < 4:
|
|
433
|
+
continue
|
|
434
|
+
dirty.append(line[3:].strip())
|
|
435
|
+
|
|
436
|
+
if dirty:
|
|
437
|
+
diagnostics.add_warning(
|
|
438
|
+
code="core.doctor.version_dirty",
|
|
439
|
+
message="Version-bearing files have uncommitted changes",
|
|
440
|
+
path=dirty[0],
|
|
441
|
+
expected="clean",
|
|
442
|
+
actual=", ".join(dirty),
|
|
443
|
+
)
|
|
444
|
+
status = "warning"
|
|
445
|
+
else:
|
|
446
|
+
status = "passed"
|
|
447
|
+
|
|
448
|
+
items.append(
|
|
449
|
+
DoctorItem(
|
|
450
|
+
kind="repo",
|
|
451
|
+
name="version-bearing-files",
|
|
452
|
+
path="version-bearing-files",
|
|
453
|
+
status=status,
|
|
454
|
+
details={"dirty": dirty, "checked": version_paths},
|
|
455
|
+
)
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
def _tool_version(self, tool: str) -> str | None:
|
|
459
|
+
resolved = shutil.which(tool)
|
|
460
|
+
if not resolved:
|
|
461
|
+
return None
|
|
462
|
+
result = subprocess.run(
|
|
463
|
+
[resolved, "--version"],
|
|
464
|
+
cwd=self.repo_root,
|
|
465
|
+
capture_output=True,
|
|
466
|
+
text=True,
|
|
467
|
+
check=False,
|
|
468
|
+
)
|
|
469
|
+
if result.returncode != 0:
|
|
470
|
+
return None
|
|
471
|
+
return result.stdout.strip() or result.stderr.strip() or "unknown"
|
|
472
|
+
|
|
473
|
+
def _git_branch(self) -> str | None:
|
|
474
|
+
result = subprocess.run(
|
|
475
|
+
["git", "-C", str(self.repo_root), "rev-parse", "--abbrev-ref", "HEAD"],
|
|
476
|
+
capture_output=True,
|
|
477
|
+
text=True,
|
|
478
|
+
check=False,
|
|
479
|
+
)
|
|
480
|
+
if result.returncode != 0:
|
|
481
|
+
return None
|
|
482
|
+
return result.stdout.strip() or None
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""File inventory - check presence of required files."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from core_runtime.tooling.diagnostics import DiagnosticCollection
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# Required files for CORE tooling
|
|
11
|
+
REQUIRED_FILES = [
|
|
12
|
+
"README.md",
|
|
13
|
+
"pyproject.toml",
|
|
14
|
+
"CHANGELOG.md",
|
|
15
|
+
"core_runtime/__version__.py",
|
|
16
|
+
"core_runtime/__init__.py",
|
|
17
|
+
"docs/VERSIONING_POLICY.md",
|
|
18
|
+
"docs/CORE_RELEASE_README.md",
|
|
19
|
+
"docs/REPRODUCIBILITY.md",
|
|
20
|
+
"docs/QUALITY_GATE.md",
|
|
21
|
+
"docs/releases/README.md",
|
|
22
|
+
"scripts/verify_release.py",
|
|
23
|
+
"scripts/check_version_consistency.py",
|
|
24
|
+
"scripts/bump_version.py",
|
|
25
|
+
"scripts/generate_requirements_lock.py",
|
|
26
|
+
"contracts/CoreRuleAnchor.abi.json",
|
|
27
|
+
"contracts/CoreRuleAnchor.bin",
|
|
28
|
+
"contracts/CoreRuleAnchor.build.json",
|
|
29
|
+
"contracts/CoreRuleAnchor.runtime.bin",
|
|
30
|
+
"contracts/CoreRuleAnchor.sol",
|
|
31
|
+
"core_runtime/core/contract_evaluator.py",
|
|
32
|
+
"core_runtime/core/contract_executability.py",
|
|
33
|
+
"core_runtime/core/rule_anchor.py",
|
|
34
|
+
"schemas/core/frozen_release_manifest.v1.json",
|
|
35
|
+
"schemas/core/frozen_release_manifest.v2.json",
|
|
36
|
+
"schemas/core/frozen_release_manifest.v3.json",
|
|
37
|
+
"schemas/core/dsk.v3.json",
|
|
38
|
+
"schemas/core/frozen_release_manifest.v7.json",
|
|
39
|
+
"schemas/core/frozen_release_manifest.v8.json",
|
|
40
|
+
"schemas/core/dsk.v3.json",
|
|
41
|
+
"schemas/core/frozen_release_manifest.v7.json",
|
|
42
|
+
"schemas/core/frozen_rule_set.v1.json",
|
|
43
|
+
"schemas/core/physical_safety_assurance_case.v1.json",
|
|
44
|
+
"schemas/core/rule_anchor_chain_evidence.v1.json",
|
|
45
|
+
"schemas/core/rule_approval.v1.json",
|
|
46
|
+
"schemas/core/rule_approval_request.v1.json",
|
|
47
|
+
"schemas/core/rule_anchor_batch.v1.json",
|
|
48
|
+
"schemas/core/unsigned_rule_anchor_deployment.v1.json",
|
|
49
|
+
"schemas/core/unsigned_rule_anchor_transaction.v1.json",
|
|
50
|
+
"scripts/audit_contract_executability.py",
|
|
51
|
+
"scripts/build_frozen_release_manifest.py",
|
|
52
|
+
"scripts/build_frozen_release_manifest_v11_2.py",
|
|
53
|
+
"scripts/build_frozen_release_manifest_v11_2_frozen.py",
|
|
54
|
+
"scripts/build_frozen_release_manifest_v11_5.py",
|
|
55
|
+
"scripts/build_frozen_release_manifest_v11_5.py",
|
|
56
|
+
"scripts/validate_frozen_rule_set.py",
|
|
57
|
+
"scripts/validate_frozen_release_manifest.py",
|
|
58
|
+
"scripts/validate_frozen_release_manifest_v11_2.py",
|
|
59
|
+
"scripts/validate_frozen_release_manifest_v11_2_frozen.py",
|
|
60
|
+
"scripts/validate_dsk_v3.py",
|
|
61
|
+
"scripts/validate_frozen_release_manifest_v11_5.py",
|
|
62
|
+
"scripts/validate_frozen_release_manifest_v11_5_1.py",
|
|
63
|
+
"scripts/build_frozen_release_manifest_v11_5_1.py",
|
|
64
|
+
"scripts/validate_dsk_v3.py",
|
|
65
|
+
"scripts/validate_frozen_release_manifest_v11_5.py",
|
|
66
|
+
"scripts/validate_rule_approval.py",
|
|
67
|
+
"scripts/validate_rule_anchor_batch.py",
|
|
68
|
+
"scripts/validate_unsigned_rule_anchor_deployment.py",
|
|
69
|
+
"scripts/validate_unsigned_rule_anchor_transaction.py",
|
|
70
|
+
"scripts/build_rule_anchor_batch.py",
|
|
71
|
+
"scripts/compile_core_rule_anchor.py",
|
|
72
|
+
"scripts/create_private_rule_commitment.py",
|
|
73
|
+
"scripts/create_rule_approval_request.py",
|
|
74
|
+
"scripts/evaluate_core_contract.py",
|
|
75
|
+
"scripts/finalize_rule_approval.py",
|
|
76
|
+
"scripts/prepare_core_rule_anchor_deployment.py",
|
|
77
|
+
"scripts/prepare_rule_anchor_transaction.py",
|
|
78
|
+
"scripts/verify_merkle_proof.py",
|
|
79
|
+
"scripts/verify_rule_anchor_onchain.py",
|
|
80
|
+
"docs/EXECUTABLE_CONTRACTS_AND_PHYSICAL_SAFETY.md",
|
|
81
|
+
"docs/FROZEN_RULE_ANCHORING.md",
|
|
82
|
+
"docs/PROJECTION_AND_RESPONSIBLE_AGENCY.md",
|
|
83
|
+
"examples/frozen_release_manifest/accepted_v11_1_0.json",
|
|
84
|
+
"examples/frozen_release_manifest/accepted_v11_2_0_candidate.json",
|
|
85
|
+
"examples/frozen_release_manifest/accepted_v11_2_0.json",
|
|
86
|
+
"examples/frozen_release_manifest/accepted_v11_5_0_candidate.json",
|
|
87
|
+
"examples/frozen_release_manifest/accepted_v11_5_1_candidate.json",
|
|
88
|
+
"examples/frozen_release_manifest/accepted_v11_5_0_candidate.json",
|
|
89
|
+
"tests/test_frozen_release_manifest.py",
|
|
90
|
+
"tests/test_frozen_release_manifest_v11_5_1.py",
|
|
91
|
+
"tests/test_frozen_release_manifest_v11_2.py",
|
|
92
|
+
"tests/test_frozen_release_manifest_v11_2_frozen.py",
|
|
93
|
+
"requirements.lock",
|
|
94
|
+
"requirements-dev.txt",
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
# Required directories
|
|
98
|
+
REQUIRED_DIRS = [
|
|
99
|
+
"schemas",
|
|
100
|
+
"examples",
|
|
101
|
+
"tests",
|
|
102
|
+
".github/workflows",
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
# Optional files (created by this implementation)
|
|
106
|
+
OPTIONAL_FILES = [
|
|
107
|
+
"docs/CORE_TOOLING.md",
|
|
108
|
+
]
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class FileInventory:
|
|
112
|
+
"""Check presence of required files and directories."""
|
|
113
|
+
|
|
114
|
+
def __init__(self, repo_root: Path):
|
|
115
|
+
self.repo_root = repo_root
|
|
116
|
+
|
|
117
|
+
def check_file(self, rel_path: str) -> bool:
|
|
118
|
+
"""Check if a file exists."""
|
|
119
|
+
return (self.repo_root / rel_path).is_file()
|
|
120
|
+
|
|
121
|
+
def check_dir(self, rel_path: str) -> bool:
|
|
122
|
+
"""Check if a directory exists."""
|
|
123
|
+
return (self.repo_root / rel_path).is_dir()
|
|
124
|
+
|
|
125
|
+
def check_all(self, diagnostics: DiagnosticCollection) -> dict[str, bool]:
|
|
126
|
+
"""Check all required files and directories."""
|
|
127
|
+
results = {}
|
|
128
|
+
|
|
129
|
+
# Check required files
|
|
130
|
+
for rel_path in REQUIRED_FILES:
|
|
131
|
+
exists = self.check_file(rel_path)
|
|
132
|
+
results[rel_path] = exists
|
|
133
|
+
if not exists:
|
|
134
|
+
diagnostics.add_error(
|
|
135
|
+
code="core.file.missing",
|
|
136
|
+
message="Required file missing: {0}".format(rel_path),
|
|
137
|
+
path=rel_path,
|
|
138
|
+
expected="exists",
|
|
139
|
+
actual="missing",
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
# Check required directories
|
|
143
|
+
for rel_path in REQUIRED_DIRS:
|
|
144
|
+
exists = self.check_dir(rel_path)
|
|
145
|
+
results[rel_path] = exists
|
|
146
|
+
if not exists:
|
|
147
|
+
diagnostics.add_error(
|
|
148
|
+
code="core.dir.missing",
|
|
149
|
+
message="Required directory missing: {0}".format(rel_path),
|
|
150
|
+
path=rel_path,
|
|
151
|
+
expected="exists",
|
|
152
|
+
actual="missing",
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
# Check optional files (just report, don't error)
|
|
156
|
+
for rel_path in OPTIONAL_FILES:
|
|
157
|
+
exists = self.check_file(rel_path)
|
|
158
|
+
results[rel_path] = exists
|
|
159
|
+
if not exists:
|
|
160
|
+
diagnostics.add_info(
|
|
161
|
+
code="core.file.optional_missing",
|
|
162
|
+
message="Optional file not yet present: {0}".format(rel_path),
|
|
163
|
+
path=rel_path,
|
|
164
|
+
expected="optional",
|
|
165
|
+
actual="missing",
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
return results
|
|
169
|
+
|
|
170
|
+
def check_release_note_exists(self, version: str, diagnostics: DiagnosticCollection) -> bool:
|
|
171
|
+
"""Check if release note for given version exists."""
|
|
172
|
+
rel_path = "docs/releases/v{0}.md".format(version)
|
|
173
|
+
exists = self.check_file(rel_path)
|
|
174
|
+
if not exists:
|
|
175
|
+
diagnostics.add_error(
|
|
176
|
+
code="core.file.release_note_missing",
|
|
177
|
+
message="Release note for v{0} not found".format(version),
|
|
178
|
+
path=rel_path,
|
|
179
|
+
expected="exists",
|
|
180
|
+
actual="missing",
|
|
181
|
+
)
|
|
182
|
+
return exists
|