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.
Files changed (96) hide show
  1. core_runtime/__init__.py +10 -0
  2. core_runtime/__version__.py +17 -0
  3. core_runtime/cli/__init__.py +7 -0
  4. core_runtime/cli/__main__.py +22 -0
  5. core_runtime/cli/bump_version.py +176 -0
  6. core_runtime/cli/contract_preflight.py +69 -0
  7. core_runtime/cli/create_domain.py +66 -0
  8. core_runtime/cli/doctor.py +44 -0
  9. core_runtime/cli/inventory.py +85 -0
  10. core_runtime/cli/lint.py +8 -0
  11. core_runtime/cli/main.py +579 -0
  12. core_runtime/cli/release_check.py +65 -0
  13. core_runtime/cli/repair_artifact_paths.py +48 -0
  14. core_runtime/cli/sync_template.py +67 -0
  15. core_runtime/cli/validate.py +73 -0
  16. core_runtime/core/__init__.py +34 -0
  17. core_runtime/core/audit_event.py +760 -0
  18. core_runtime/core/audit_trail_index.py +100 -0
  19. core_runtime/core/canonicalization.py +55 -0
  20. core_runtime/core/contract_evaluator.py +945 -0
  21. core_runtime/core/contract_executability.py +307 -0
  22. core_runtime/core/contract_loader.py +57 -0
  23. core_runtime/core/contract_probes.py +630 -0
  24. core_runtime/core/contract_program.py +131 -0
  25. core_runtime/core/contract_program_registry.py +104 -0
  26. core_runtime/core/contract_program_v2.py +126 -0
  27. core_runtime/core/dsk_v3.py +142 -0
  28. core_runtime/core/explainability.py +2115 -0
  29. core_runtime/core/numeric_normalization.py +120 -0
  30. core_runtime/core/rule_anchor.py +1388 -0
  31. core_runtime/core/schema_fingerprint.py +69 -0
  32. core_runtime/core/sensor_evidence.py +548 -0
  33. core_runtime/data/contracts/CoreAnchor.sol +109 -0
  34. core_runtime/data/contracts/CoreRuleAnchor.abi.json +111 -0
  35. core_runtime/data/contracts/CoreRuleAnchor.bin +1 -0
  36. core_runtime/data/contracts/CoreRuleAnchor.build.json +20 -0
  37. core_runtime/data/contracts/CoreRuleAnchor.runtime.bin +1 -0
  38. core_runtime/data/contracts/CoreRuleAnchor.sol +74 -0
  39. core_runtime/data/package_data_manifest.v1.json +173 -0
  40. core_runtime/data/schemas/core/causal_trace.v1.json +141 -0
  41. core_runtime/data/schemas/core/context_gate.v1.json +38 -0
  42. core_runtime/data/schemas/core/context_threshold.v1.json +46 -0
  43. core_runtime/data/schemas/core/contract_program.v1.json +186 -0
  44. core_runtime/data/schemas/core/contract_program.v2.json +187 -0
  45. core_runtime/data/schemas/core/control_decision.v1.json +114 -0
  46. core_runtime/data/schemas/core/dsk.v3.json +105 -0
  47. core_runtime/data/schemas/core/effect_result.v1.json +39 -0
  48. core_runtime/data/schemas/core/entropy_signal.v1.json +108 -0
  49. core_runtime/data/schemas/core/execution_receipt.v1.json +93 -0
  50. core_runtime/data/schemas/core/frozen_release_manifest.v1.json +87 -0
  51. core_runtime/data/schemas/core/frozen_release_manifest.v2.json +72 -0
  52. core_runtime/data/schemas/core/frozen_release_manifest.v3.json +38 -0
  53. core_runtime/data/schemas/core/frozen_release_manifest.v4.json +72 -0
  54. core_runtime/data/schemas/core/frozen_release_manifest.v5.json +38 -0
  55. core_runtime/data/schemas/core/frozen_release_manifest.v6.json +116 -0
  56. core_runtime/data/schemas/core/frozen_release_manifest.v7.json +37 -0
  57. core_runtime/data/schemas/core/frozen_release_manifest.v8.json +114 -0
  58. core_runtime/data/schemas/core/frozen_rule_set.v1.json +282 -0
  59. core_runtime/data/schemas/core/memory_artifact.v1.json +120 -0
  60. core_runtime/data/schemas/core/memory_generation_result.v1.json +37 -0
  61. core_runtime/data/schemas/core/operational_learning_event.v1.json +63 -0
  62. core_runtime/data/schemas/core/pattern_candidate.v1.json +114 -0
  63. core_runtime/data/schemas/core/physical_safety_assurance_case.v1.json +676 -0
  64. core_runtime/data/schemas/core/policy_lifecycle.v1.json +99 -0
  65. core_runtime/data/schemas/core/retention_manifest.v1.json +53 -0
  66. core_runtime/data/schemas/core/reversibility_policy.v1.json +107 -0
  67. core_runtime/data/schemas/core/rule_anchor_batch.v1.json +93 -0
  68. core_runtime/data/schemas/core/rule_anchor_chain_evidence.v1.json +56 -0
  69. core_runtime/data/schemas/core/rule_approval.v1.json +49 -0
  70. core_runtime/data/schemas/core/rule_approval_request.v1.json +42 -0
  71. core_runtime/data/schemas/core/state_transition.v1.json +115 -0
  72. core_runtime/data/schemas/core/task_closeout.v1.json +47 -0
  73. core_runtime/data/schemas/core/template_promotion_candidate.v1.json +83 -0
  74. core_runtime/data/schemas/core/unsigned_rule_anchor_deployment.v1.json +106 -0
  75. core_runtime/data/schemas/core/unsigned_rule_anchor_transaction.v1.json +116 -0
  76. core_runtime/tooling/__init__.py +48 -0
  77. core_runtime/tooling/bump_version.py +900 -0
  78. core_runtime/tooling/contract_preflight.py +293 -0
  79. core_runtime/tooling/create_domain.py +254 -0
  80. core_runtime/tooling/diagnostics.py +129 -0
  81. core_runtime/tooling/doctor.py +482 -0
  82. core_runtime/tooling/file_inventory.py +182 -0
  83. core_runtime/tooling/json_checks.py +100 -0
  84. core_runtime/tooling/release_check.py +1017 -0
  85. core_runtime/tooling/repair_artifact_paths.py +458 -0
  86. core_runtime/tooling/report_writer.py +176 -0
  87. core_runtime/tooling/repository_inventory.py +399 -0
  88. core_runtime/tooling/safety_checks.py +172 -0
  89. core_runtime/tooling/sync_template.py +303 -0
  90. core_runtime/tooling/validation.py +507 -0
  91. core_runtime/tooling/version_inventory.py +256 -0
  92. core_runtime_engine-11.5.1.dist-info/METADATA +35 -0
  93. core_runtime_engine-11.5.1.dist-info/RECORD +96 -0
  94. core_runtime_engine-11.5.1.dist-info/WHEEL +5 -0
  95. core_runtime_engine-11.5.1.dist-info/entry_points.txt +2 -0
  96. core_runtime_engine-11.5.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,293 @@
1
+ """Advisory-only contract preflight helpers for CORE tooling."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from dataclasses import dataclass, field
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ from core_runtime.tooling.diagnostics import DiagnosticCollection
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class ContractItem:
15
+ """A single contract review item."""
16
+
17
+ kind: str
18
+ name: str
19
+ path: str
20
+ status: str
21
+ details: dict[str, Any] = field(default_factory=dict)
22
+
23
+ def to_dict(self) -> dict[str, Any]:
24
+ result = {
25
+ "kind": self.kind,
26
+ "name": self.name,
27
+ "path": self.path,
28
+ "status": self.status,
29
+ }
30
+ if self.details:
31
+ result["details"] = self.details
32
+ return result
33
+
34
+
35
+ @dataclass
36
+ class ContractPreflightReport:
37
+ """Normalized report for `core-runtime contract-preflight`."""
38
+
39
+ tool: str
40
+ command: str
41
+ status: str
42
+ mutation_performed: bool = False
43
+ summary: dict[str, Any] = field(default_factory=dict)
44
+ selection: dict[str, Any] | None = None
45
+ items: list[ContractItem] = field(default_factory=list)
46
+ diagnostics: DiagnosticCollection = field(default_factory=DiagnosticCollection)
47
+
48
+ def to_dict(self) -> dict[str, Any]:
49
+ counts = self.diagnostics.count_by_severity()
50
+ summary = dict(self.summary)
51
+ summary.setdefault("info", counts["info"])
52
+ summary.setdefault("warning", counts["warning"])
53
+ summary.setdefault("error", counts["error"])
54
+ summary.setdefault("blocked", counts["blocked"])
55
+ return {
56
+ "tool": self.tool,
57
+ "command": self.command,
58
+ "status": self.status,
59
+ "mutation_performed": self.mutation_performed,
60
+ "summary": summary,
61
+ "selection": self.selection,
62
+ "items": [item.to_dict() for item in self.items],
63
+ "diagnostics": [d.to_dict() for d in self.diagnostics.diagnostics],
64
+ }
65
+
66
+ def to_markdown(self) -> str:
67
+ counts = self.diagnostics.count_by_severity()
68
+ summary = dict(self.summary)
69
+ summary.setdefault("info", counts["info"])
70
+ summary.setdefault("warning", counts["warning"])
71
+ summary.setdefault("error", counts["error"])
72
+ summary.setdefault("blocked", counts["blocked"])
73
+ lines: list[str] = []
74
+ lines.append("# CORE contract preflight")
75
+ lines.append("")
76
+ lines.append("## Summary")
77
+ lines.append("")
78
+ lines.append("| Metric | Value |")
79
+ lines.append("|--------|-------|")
80
+ lines.append("| Tool | {0} |".format(self.tool))
81
+ lines.append("| Command | {0} |".format(self.command))
82
+ lines.append("| Status | {0} |".format(self.status.upper()))
83
+ lines.append("| Mutation Performed | No |")
84
+ lines.append("| Items | {0} |".format(summary.get("item_count", len(self.items))))
85
+ lines.append("| Info | {0} |".format(counts["info"]))
86
+ lines.append("| Warning | {0} |".format(counts["warning"]))
87
+ lines.append("| Error | {0} |".format(counts["error"]))
88
+ lines.append("| Blocked | {0} |".format(counts["blocked"]))
89
+ lines.append("")
90
+
91
+ if self.selection is not None:
92
+ lines.append("## Selection")
93
+ lines.append("")
94
+ for key, value in self.selection.items():
95
+ lines.append("- **{0}**: {1}".format(key, value))
96
+ lines.append("")
97
+
98
+ if self.items:
99
+ lines.append("## Items")
100
+ lines.append("")
101
+ lines.append("| Kind | Name | Path | Status |")
102
+ lines.append("|------|------|------|--------|")
103
+ for item in self.items:
104
+ lines.append("| {0} | {1} | {2} | {3} |".format(item.kind, item.name, item.path, item.status))
105
+ lines.append("")
106
+
107
+ if self.diagnostics.diagnostics:
108
+ lines.append("## Diagnostics")
109
+ lines.append("")
110
+ lines.append("| Severity | Code | Path | Message |")
111
+ lines.append("|----------|------|------|---------|")
112
+ for diagnostic in self.diagnostics.diagnostics:
113
+ path = diagnostic.path or "-"
114
+ message = diagnostic.message.replace("|", "\\|")
115
+ lines.append(
116
+ "| {0} | {1} | {2} | {3} |".format(
117
+ diagnostic.severity.value.upper(),
118
+ diagnostic.code,
119
+ path,
120
+ message,
121
+ )
122
+ )
123
+ lines.append("")
124
+
125
+ return "\n".join(lines)
126
+
127
+
128
+ class RepositoryContractPreflight:
129
+ """Build advisory-only contract review reports from public CORE schemas."""
130
+
131
+ def __init__(self, repo_root: Path):
132
+ self.repo_root = repo_root
133
+
134
+ def build_candidate_report(self, name: str, diagnostics: DiagnosticCollection | None = None) -> ContractPreflightReport:
135
+ diagnostics = diagnostics or DiagnosticCollection()
136
+ contract = self._resolve_contract(name)
137
+ selection = {"mode": "candidate", "candidate": name}
138
+
139
+ if contract is None:
140
+ diagnostics.add_blocked(
141
+ code="core.contract_preflight.contract_unknown",
142
+ message="Unknown contract candidate: {0}".format(name),
143
+ path=name,
144
+ expected="known CORE contract name",
145
+ actual=name,
146
+ )
147
+ return ContractPreflightReport(
148
+ tool="core-runtime contract-preflight",
149
+ command="contract-preflight --candidate {0}".format(name),
150
+ status="blocked",
151
+ summary={"mode": "candidate", "item_count": 0},
152
+ selection=selection,
153
+ diagnostics=diagnostics,
154
+ )
155
+
156
+ item = self._contract_item(contract)
157
+ return ContractPreflightReport(
158
+ tool="core-runtime contract-preflight",
159
+ command="contract-preflight --candidate {0}".format(name),
160
+ status="pass",
161
+ summary={
162
+ "mode": "candidate",
163
+ "item_count": 1,
164
+ "required_field_count": len(item.details.get("required_fields", [])),
165
+ "property_count": len(item.details.get("property_names", [])),
166
+ },
167
+ selection=selection,
168
+ items=[item],
169
+ diagnostics=diagnostics,
170
+ )
171
+
172
+ def build_compare_report(
173
+ self,
174
+ left: str,
175
+ right: str,
176
+ diagnostics: DiagnosticCollection | None = None,
177
+ ) -> ContractPreflightReport:
178
+ diagnostics = diagnostics or DiagnosticCollection()
179
+ left_contract = self._resolve_contract(left)
180
+ right_contract = self._resolve_contract(right)
181
+ selection = {"mode": "compare", "left": left, "right": right}
182
+
183
+ missing: list[str] = []
184
+ if left_contract is None:
185
+ missing.append(left)
186
+ if right_contract is None:
187
+ missing.append(right)
188
+ if missing:
189
+ diagnostics.add_blocked(
190
+ code="core.contract_preflight.contract_unknown",
191
+ message="Unknown contract candidate(s): {0}".format(", ".join(missing)),
192
+ path=", ".join(missing),
193
+ expected="known CORE contract name",
194
+ actual=", ".join(missing),
195
+ )
196
+ return ContractPreflightReport(
197
+ tool="core-runtime contract-preflight",
198
+ command="contract-preflight --compare {0} {1}".format(left, right),
199
+ status="blocked",
200
+ summary={"mode": "compare", "item_count": 0},
201
+ selection=selection,
202
+ diagnostics=diagnostics,
203
+ )
204
+
205
+ left_item = self._contract_item(left_contract)
206
+ right_item = self._contract_item(right_contract)
207
+ left_required = set(left_item.details.get("required_fields", []))
208
+ right_required = set(right_item.details.get("required_fields", []))
209
+ shared_required = sorted(left_required & right_required)
210
+ left_only = sorted(left_required - right_required)
211
+ right_only = sorted(right_required - left_required)
212
+ status = "pass"
213
+ if left_item.details.get("schema_version") != right_item.details.get("schema_version"):
214
+ status = "warning"
215
+
216
+ return ContractPreflightReport(
217
+ tool="core-runtime contract-preflight",
218
+ command="contract-preflight --compare {0} {1}".format(left, right),
219
+ status=status,
220
+ summary={
221
+ "mode": "compare",
222
+ "item_count": 2,
223
+ "shared_required_fields": len(shared_required),
224
+ "left_only_required_fields": len(left_only),
225
+ "right_only_required_fields": len(right_only),
226
+ "same_schema_version": left_item.details.get("schema_version") == right_item.details.get("schema_version"),
227
+ },
228
+ selection=selection,
229
+ items=[left_item, right_item],
230
+ diagnostics=diagnostics,
231
+ )
232
+
233
+ def _resolve_contract(self, name: str) -> dict[str, Any] | None:
234
+ normalized = name.strip().lower()
235
+ for path in sorted((self.repo_root / "schemas" / "core").glob("*.json")):
236
+ contract = self._load_contract(path)
237
+ if contract is None:
238
+ continue
239
+ candidates = {
240
+ contract.get("title", "").lower(),
241
+ contract.get("schema_version", "").lower(),
242
+ path.stem.lower(),
243
+ }
244
+ if normalized in candidates:
245
+ return contract
246
+ return None
247
+
248
+ def _load_contract(self, path: Path) -> dict[str, Any] | None:
249
+ try:
250
+ payload = json.loads(path.read_text(encoding="utf-8"))
251
+ except (OSError, json.JSONDecodeError):
252
+ return None
253
+ if not isinstance(payload, dict):
254
+ return None
255
+ required = payload.get("required")
256
+ properties = payload.get("properties")
257
+ if not isinstance(properties, dict):
258
+ properties = {}
259
+ return {
260
+ "path": path,
261
+ "title": payload.get("title"),
262
+ "schema_version": self._schema_version(payload),
263
+ "required": required if isinstance(required, list) else [],
264
+ "properties": properties,
265
+ "additionalProperties": payload.get("additionalProperties"),
266
+ }
267
+
268
+ def _schema_version(self, payload: dict[str, Any]) -> str | None:
269
+ properties = payload.get("properties")
270
+ if isinstance(properties, dict):
271
+ schema_version = properties.get("schema_version")
272
+ if isinstance(schema_version, dict):
273
+ const = schema_version.get("const")
274
+ if isinstance(const, str) and const:
275
+ return const
276
+ return None
277
+
278
+ def _contract_item(self, contract: dict[str, Any]) -> ContractItem:
279
+ properties = contract.get("properties", {})
280
+ property_names = sorted(properties.keys()) if isinstance(properties, dict) else []
281
+ required_fields = sorted(str(value) for value in contract.get("required", []))
282
+ return ContractItem(
283
+ kind="contract",
284
+ name=str(contract.get("title") or Path(contract["path"]).stem),
285
+ path=str(Path(contract["path"]).relative_to(self.repo_root)),
286
+ status="passed",
287
+ details={
288
+ "schema_version": contract.get("schema_version"),
289
+ "required_fields": required_fields,
290
+ "property_names": property_names,
291
+ "additional_properties": contract.get("additionalProperties"),
292
+ },
293
+ )
@@ -0,0 +1,254 @@
1
+ """Dry-run-first domain scaffolding preflight for CORE tooling."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from dataclasses import dataclass, field
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ from core_runtime.tooling.diagnostics import DiagnosticCollection
11
+
12
+
13
+ DOMAIN_NAME_PATTERN = re.compile(r"^[a-z][a-z0-9_]*$")
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class CreateDomainItem:
18
+ """A planned file or artifact in a domain scaffold."""
19
+
20
+ kind: str
21
+ name: str
22
+ path: str
23
+ status: str
24
+ details: dict[str, Any] = field(default_factory=dict)
25
+
26
+ def to_dict(self) -> dict[str, Any]:
27
+ result = {
28
+ "kind": self.kind,
29
+ "name": self.name,
30
+ "path": self.path,
31
+ "status": self.status,
32
+ }
33
+ if self.details:
34
+ result["details"] = self.details
35
+ return result
36
+
37
+
38
+ @dataclass
39
+ class CreateDomainReport:
40
+ """Normalized report for `core-runtime create-domain`."""
41
+
42
+ tool: str
43
+ command: str
44
+ status: str
45
+ mutation_performed: bool = False
46
+ summary: dict[str, Any] = field(default_factory=dict)
47
+ selection: dict[str, Any] | None = None
48
+ items: list[CreateDomainItem] = field(default_factory=list)
49
+ diagnostics: DiagnosticCollection = field(default_factory=DiagnosticCollection)
50
+
51
+ def to_dict(self) -> dict[str, Any]:
52
+ counts = self.diagnostics.count_by_severity()
53
+ summary = dict(self.summary)
54
+ summary.setdefault("info", counts["info"])
55
+ summary.setdefault("warning", counts["warning"])
56
+ summary.setdefault("error", counts["error"])
57
+ summary.setdefault("blocked", counts["blocked"])
58
+ return {
59
+ "tool": self.tool,
60
+ "command": self.command,
61
+ "status": self.status,
62
+ "mutation_performed": self.mutation_performed,
63
+ "summary": summary,
64
+ "selection": self.selection,
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 create-domain plan")
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("| Items | {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.selection is not None:
95
+ lines.append("## Selection")
96
+ lines.append("")
97
+ for key, value in self.selection.items():
98
+ lines.append("- **{0}**: {1}".format(key, value))
99
+ lines.append("")
100
+
101
+ if self.items:
102
+ lines.append("## Planned Artifacts")
103
+ lines.append("")
104
+ lines.append("| Kind | Name | Path | Status |")
105
+ lines.append("|------|------|------|--------|")
106
+ for item in self.items:
107
+ lines.append("| {0} | {1} | {2} | {3} |".format(item.kind, item.name, item.path, item.status))
108
+ lines.append("")
109
+
110
+ if self.diagnostics.diagnostics:
111
+ lines.append("## Diagnostics")
112
+ lines.append("")
113
+ lines.append("| Severity | Code | Path | Message |")
114
+ lines.append("|----------|------|------|---------|")
115
+ for diagnostic in self.diagnostics.diagnostics:
116
+ path = diagnostic.path or "-"
117
+ message = diagnostic.message.replace("|", "\\|")
118
+ lines.append(
119
+ "| {0} | {1} | {2} | {3} |".format(
120
+ diagnostic.severity.value.upper(),
121
+ diagnostic.code,
122
+ path,
123
+ message,
124
+ )
125
+ )
126
+ lines.append("")
127
+
128
+ return "\n".join(lines)
129
+
130
+
131
+ class DomainScaffolder:
132
+ """Plan a deterministic domain scaffold without mutating the repo."""
133
+
134
+ def __init__(self, repo_root: Path):
135
+ self.repo_root = repo_root
136
+
137
+ def build_plan(self, name: str, template: str = "generic", diagnostics: DiagnosticCollection | None = None) -> CreateDomainReport:
138
+ diagnostics = diagnostics or DiagnosticCollection()
139
+ normalized_name = name.strip()
140
+ normalized_template = template.strip()
141
+ selection = {"domain": normalized_name, "template": normalized_template, "mode": "dry-run"}
142
+
143
+ if not DOMAIN_NAME_PATTERN.fullmatch(normalized_name):
144
+ diagnostics.add_blocked(
145
+ code="core.create_domain.invalid_name",
146
+ message="Domain name must match ^[a-z][a-z0-9_]*$",
147
+ path=normalized_name,
148
+ expected="lowercase identifier",
149
+ actual=normalized_name,
150
+ )
151
+ return CreateDomainReport(
152
+ tool="core-runtime create-domain",
153
+ command="create-domain {0} --dry-run".format(normalized_name),
154
+ status="blocked",
155
+ summary={"mode": "dry-run", "item_count": 0},
156
+ selection=selection,
157
+ diagnostics=diagnostics,
158
+ )
159
+
160
+ planned_items, collisions, risks = self._plan_items(normalized_name, normalized_template)
161
+ if collisions:
162
+ diagnostics.add_blocked(
163
+ code="core.create_domain.collision",
164
+ message="Domain scaffold collides with existing files or directories",
165
+ path=collisions[0],
166
+ expected="unused paths",
167
+ actual=", ".join(collisions),
168
+ )
169
+ status = "blocked"
170
+ else:
171
+ status = "pass"
172
+
173
+ if risks:
174
+ diagnostics.add_warning(
175
+ code="core.create_domain.review_required",
176
+ message="Domain scaffold has reviewable design risks",
177
+ path=normalized_name,
178
+ expected="bounded dry-run review",
179
+ actual=", ".join(risks),
180
+ )
181
+ if status == "pass":
182
+ status = "warning"
183
+
184
+ return CreateDomainReport(
185
+ tool="core-runtime create-domain",
186
+ command="create-domain {0} --dry-run".format(normalized_name),
187
+ status=status,
188
+ summary={
189
+ "mode": "dry-run",
190
+ "item_count": len(planned_items),
191
+ "collision_count": len(collisions),
192
+ "risk_count": len(risks),
193
+ "template": normalized_template,
194
+ },
195
+ selection=selection,
196
+ items=planned_items,
197
+ diagnostics=diagnostics,
198
+ )
199
+
200
+ def _plan_items(self, name: str, template: str) -> tuple[list[CreateDomainItem], list[str], list[str]]:
201
+ collisions: list[str] = []
202
+ risks: list[str] = []
203
+ items: list[CreateDomainItem] = []
204
+
205
+ domain_dir = self.repo_root / "core_runtime" / "domains" / name
206
+ test_path = self.repo_root / "tests" / f"test_domain_{name}.py"
207
+ example_dir = self.repo_root / "examples" / "domains" / name
208
+ docs_path = self.repo_root / "docs" / "domains" / f"{name}.md"
209
+
210
+ planned_paths = [
211
+ domain_dir,
212
+ domain_dir / "__init__.py",
213
+ domain_dir / "task.py",
214
+ domain_dir / "oracle.py",
215
+ domain_dir / "surrogate.py",
216
+ domain_dir / "projection.py",
217
+ domain_dir / "evaluator.py",
218
+ domain_dir / "confidence.py",
219
+ domain_dir / "manifest.json",
220
+ test_path,
221
+ example_dir / "valid_task.json",
222
+ example_dir / "invalid_task.json",
223
+ docs_path,
224
+ ]
225
+
226
+ for path in planned_paths:
227
+ if path.exists():
228
+ collisions.append(str(path.relative_to(self.repo_root)))
229
+ items.append(
230
+ CreateDomainItem(
231
+ kind="planned_dir" if path == domain_dir else "planned_file",
232
+ name=path.name,
233
+ path=str(path.relative_to(self.repo_root)),
234
+ status="planned",
235
+ details={
236
+ "template": template,
237
+ "exists": path.exists(),
238
+ },
239
+ )
240
+ )
241
+
242
+ risks.extend(
243
+ [
244
+ "no_runtime_behavior",
245
+ "advisory_only_manifest",
246
+ "fixtures_must_stay_synthetic",
247
+ "tests_require_followup",
248
+ "docs_need_manual_review",
249
+ ]
250
+ )
251
+ if template != "generic":
252
+ risks.append(f"template:{template}")
253
+
254
+ return items, collisions, risks
@@ -0,0 +1,129 @@
1
+ """Diagnostic data structures and severity handling for CORE tooling."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from enum import Enum
7
+ from typing import Any, Optional
8
+
9
+
10
+ class Severity(str, Enum):
11
+ """Diagnostic severity levels."""
12
+
13
+ INFO = "info"
14
+ WARNING = "warning"
15
+ ERROR = "error"
16
+ BLOCKED = "blocked"
17
+
18
+ def exit_code_weight(self) -> int:
19
+ """Return the weight for exit code computation."""
20
+ return {
21
+ Severity.INFO: 0,
22
+ Severity.WARNING: 0,
23
+ Severity.ERROR: 1,
24
+ Severity.BLOCKED: 2,
25
+ }[self]
26
+
27
+
28
+ class ExitCode(Enum):
29
+ """Exit codes for lint command."""
30
+
31
+ OK = 0
32
+ ERROR = 1
33
+ BLOCKED = 2
34
+ INTERNAL_ERROR = 3
35
+
36
+
37
+ @dataclass(frozen=True, order=False)
38
+ class Diagnostic:
39
+ """A single diagnostic result from a check."""
40
+
41
+ code: str
42
+ severity: Severity
43
+ message: str
44
+ mutation_allowed: bool
45
+ path: Optional[str] = None
46
+ expected: Optional[str] = None
47
+ actual: Optional[str] = None
48
+ details: Optional[str] = None
49
+
50
+ def to_dict(self) -> dict[str, Any]:
51
+ """Convert to dictionary for JSON serialization."""
52
+ result = {
53
+ "code": self.code,
54
+ "severity": self.severity.value,
55
+ "message": self.message,
56
+ "mutation_allowed": self.mutation_allowed,
57
+ }
58
+ if self.path is not None:
59
+ result["path"] = self.path
60
+ if self.expected is not None:
61
+ result["expected"] = self.expected
62
+ if self.actual is not None:
63
+ result["actual"] = self.actual
64
+ if self.details is not None:
65
+ result["details"] = self.details
66
+ return result
67
+
68
+
69
+ @dataclass
70
+ class DiagnosticCollection:
71
+ """Collection of diagnostics with summary statistics."""
72
+
73
+ diagnostics: list[Diagnostic] = field(default_factory=list)
74
+
75
+ def add(self, diagnostic: Diagnostic) -> None:
76
+ """Add a diagnostic."""
77
+ self.diagnostics.append(diagnostic)
78
+
79
+ def add_error(self, code: str, message: str, path: Optional[str] = None, **kwargs: Any) -> None:
80
+ """Add an error severity diagnostic."""
81
+ self.add(Diagnostic(code=code, severity=Severity.ERROR, message=message, path=path, mutation_allowed=False, **kwargs))
82
+
83
+ def add_warning(self, code: str, message: str, path: Optional[str] = None, **kwargs: Any) -> None:
84
+ """Add a warning severity diagnostic."""
85
+ self.add(Diagnostic(code=code, severity=Severity.WARNING, message=message, path=path, mutation_allowed=False, **kwargs))
86
+
87
+ def add_info(self, code: str, message: str, path: Optional[str] = None, **kwargs: Any) -> None:
88
+ """Add an info severity diagnostic."""
89
+ self.add(Diagnostic(code=code, severity=Severity.INFO, message=message, path=path, mutation_allowed=False, **kwargs))
90
+
91
+ def add_blocked(self, code: str, message: str, path: Optional[str] = None, **kwargs: Any) -> None:
92
+ """Add a blocked severity diagnostic."""
93
+ self.add(Diagnostic(code=code, severity=Severity.BLOCKED, message=message, path=path, mutation_allowed=False, **kwargs))
94
+
95
+ def count_by_severity(self) -> dict[str, int]:
96
+ """Count diagnostics by severity."""
97
+ counts = {s.value: 0 for s in Severity}
98
+ for d in self.diagnostics:
99
+ counts[d.severity.value] += 1
100
+ return counts
101
+
102
+ def has_errors(self) -> bool:
103
+ """Check if any error or blocked diagnostics exist."""
104
+ return any(d.severity in (Severity.ERROR, Severity.BLOCKED) for d in self.diagnostics)
105
+
106
+ def has_blocked(self) -> bool:
107
+ """Check if any blocked diagnostics exist."""
108
+ return any(d.severity == Severity.BLOCKED for d in self.diagnostics)
109
+
110
+ def compute_exit_code(self) -> ExitCode:
111
+ """Compute exit code based on diagnostics."""
112
+ if not self.diagnostics:
113
+ return ExitCode.OK
114
+
115
+ has_errors = any(d.severity == Severity.ERROR for d in self.diagnostics)
116
+ has_blocked = any(d.severity == Severity.BLOCKED for d in self.diagnostics)
117
+
118
+ if has_blocked:
119
+ return ExitCode.BLOCKED
120
+ if has_errors:
121
+ return ExitCode.ERROR
122
+ return ExitCode.OK
123
+
124
+ def to_dict(self) -> dict[str, Any]:
125
+ """Convert to dictionary for JSON serialization."""
126
+ return {
127
+ "diagnostics": [d.to_dict() for d in self.diagnostics],
128
+ "summary": self.count_by_severity(),
129
+ }