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,399 @@
|
|
|
1
|
+
"""Deterministic repository inventory helpers for read-only CLI navigation."""
|
|
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
|
+
from core_runtime.tooling.file_inventory import FileInventory
|
|
12
|
+
from core_runtime.tooling.version_inventory import VersionInventory
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class InventoryItem:
|
|
17
|
+
"""A single repository inventory item."""
|
|
18
|
+
|
|
19
|
+
kind: str
|
|
20
|
+
name: str
|
|
21
|
+
path: str
|
|
22
|
+
title: str | None = None
|
|
23
|
+
summary: str | None = None
|
|
24
|
+
status: str = "present"
|
|
25
|
+
details: dict[str, Any] = field(default_factory=dict)
|
|
26
|
+
|
|
27
|
+
def to_dict(self) -> dict[str, Any]:
|
|
28
|
+
result = {
|
|
29
|
+
"kind": self.kind,
|
|
30
|
+
"name": self.name,
|
|
31
|
+
"path": self.path,
|
|
32
|
+
"status": self.status,
|
|
33
|
+
}
|
|
34
|
+
if self.title is not None:
|
|
35
|
+
result["title"] = self.title
|
|
36
|
+
if self.summary is not None:
|
|
37
|
+
result["summary"] = self.summary
|
|
38
|
+
if self.details:
|
|
39
|
+
result["details"] = self.details
|
|
40
|
+
return result
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class InventoryReport:
|
|
45
|
+
"""Normalized report for `core-runtime list` and `core-runtime info`."""
|
|
46
|
+
|
|
47
|
+
tool: str
|
|
48
|
+
command: str
|
|
49
|
+
status: str
|
|
50
|
+
mutation_performed: bool = False
|
|
51
|
+
summary: dict[str, Any] = field(default_factory=dict)
|
|
52
|
+
selection: dict[str, Any] | None = None
|
|
53
|
+
items: list[InventoryItem] = field(default_factory=list)
|
|
54
|
+
diagnostics: DiagnosticCollection = field(default_factory=DiagnosticCollection)
|
|
55
|
+
|
|
56
|
+
def to_dict(self) -> dict[str, Any]:
|
|
57
|
+
counts = self.diagnostics.count_by_severity()
|
|
58
|
+
summary = dict(self.summary)
|
|
59
|
+
summary.setdefault("info", counts["info"])
|
|
60
|
+
summary.setdefault("warning", counts["warning"])
|
|
61
|
+
summary.setdefault("error", counts["error"])
|
|
62
|
+
summary.setdefault("blocked", counts["blocked"])
|
|
63
|
+
return {
|
|
64
|
+
"tool": self.tool,
|
|
65
|
+
"command": self.command,
|
|
66
|
+
"status": self.status,
|
|
67
|
+
"mutation_performed": self.mutation_performed,
|
|
68
|
+
"summary": summary,
|
|
69
|
+
"selection": self.selection,
|
|
70
|
+
"items": [item.to_dict() for item in self.items],
|
|
71
|
+
"diagnostics": [d.to_dict() for d in self.diagnostics.diagnostics],
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
def to_markdown(self) -> str:
|
|
75
|
+
counts = self.diagnostics.count_by_severity()
|
|
76
|
+
summary = dict(self.summary)
|
|
77
|
+
summary.setdefault("info", counts["info"])
|
|
78
|
+
summary.setdefault("warning", counts["warning"])
|
|
79
|
+
summary.setdefault("error", counts["error"])
|
|
80
|
+
summary.setdefault("blocked", counts["blocked"])
|
|
81
|
+
lines: list[str] = []
|
|
82
|
+
lines.append("# CORE repository inventory")
|
|
83
|
+
lines.append("")
|
|
84
|
+
lines.append("## Summary")
|
|
85
|
+
lines.append("")
|
|
86
|
+
lines.append("| Metric | Value |")
|
|
87
|
+
lines.append("|--------|-------|")
|
|
88
|
+
lines.append("| Tool | {0} |".format(self.tool))
|
|
89
|
+
lines.append("| Command | {0} |".format(self.command))
|
|
90
|
+
lines.append("| Status | {0} |".format(self.status.upper()))
|
|
91
|
+
lines.append("| Mutation Performed | No |")
|
|
92
|
+
lines.append("| Items | {0} |".format(summary.get("item_count", len(self.items))))
|
|
93
|
+
lines.append("| Info | {0} |".format(counts["info"]))
|
|
94
|
+
lines.append("| Warning | {0} |".format(counts["warning"]))
|
|
95
|
+
lines.append("| Error | {0} |".format(counts["error"]))
|
|
96
|
+
lines.append("| Blocked | {0} |".format(counts["blocked"]))
|
|
97
|
+
lines.append("")
|
|
98
|
+
|
|
99
|
+
if self.selection is not None:
|
|
100
|
+
lines.append("## Selection")
|
|
101
|
+
lines.append("")
|
|
102
|
+
for key, value in self.selection.items():
|
|
103
|
+
lines.append("- **{0}**: {1}".format(key, value))
|
|
104
|
+
lines.append("")
|
|
105
|
+
|
|
106
|
+
if self.items:
|
|
107
|
+
lines.append("## Items")
|
|
108
|
+
lines.append("")
|
|
109
|
+
lines.append("| Kind | Name | Path | Status |")
|
|
110
|
+
lines.append("|------|------|------|--------|")
|
|
111
|
+
for item in self.items:
|
|
112
|
+
lines.append("| {0} | {1} | {2} | {3} |".format(item.kind, item.name, item.path, item.status))
|
|
113
|
+
lines.append("")
|
|
114
|
+
|
|
115
|
+
if self.diagnostics.diagnostics:
|
|
116
|
+
lines.append("## Diagnostics")
|
|
117
|
+
lines.append("")
|
|
118
|
+
lines.append("| Severity | Code | Path | Message |")
|
|
119
|
+
lines.append("|----------|------|------|---------|")
|
|
120
|
+
for diagnostic in self.diagnostics.diagnostics:
|
|
121
|
+
path = diagnostic.path or "-"
|
|
122
|
+
message = diagnostic.message.replace("|", "\\|")
|
|
123
|
+
lines.append(
|
|
124
|
+
"| {0} | {1} | {2} | {3} |".format(
|
|
125
|
+
diagnostic.severity.value.upper(),
|
|
126
|
+
diagnostic.code,
|
|
127
|
+
path,
|
|
128
|
+
message,
|
|
129
|
+
)
|
|
130
|
+
)
|
|
131
|
+
lines.append("")
|
|
132
|
+
|
|
133
|
+
return "\n".join(lines)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class RepositoryInventory:
|
|
137
|
+
"""Build read-only inventories for public CORE repo navigation."""
|
|
138
|
+
|
|
139
|
+
def __init__(self, repo_root: Path):
|
|
140
|
+
self.repo_root = repo_root
|
|
141
|
+
self.version_inventory = VersionInventory(repo_root)
|
|
142
|
+
self.file_inventory = FileInventory(repo_root)
|
|
143
|
+
|
|
144
|
+
def list_items(self, kind: str) -> list[InventoryItem]:
|
|
145
|
+
kind = kind.lower()
|
|
146
|
+
if kind == "schemas":
|
|
147
|
+
return self._list_schemas()
|
|
148
|
+
if kind == "contracts":
|
|
149
|
+
return self._list_contracts()
|
|
150
|
+
if kind == "domains":
|
|
151
|
+
return self._list_domains()
|
|
152
|
+
if kind == "adapters":
|
|
153
|
+
return self._list_adapters()
|
|
154
|
+
return []
|
|
155
|
+
|
|
156
|
+
def find_item(self, kind: str, name: str) -> InventoryItem | None:
|
|
157
|
+
target = name.strip().lower()
|
|
158
|
+
for item in self.list_items(kind):
|
|
159
|
+
candidates = {
|
|
160
|
+
item.name.lower(),
|
|
161
|
+
Path(item.path).name.lower(),
|
|
162
|
+
Path(item.path).stem.lower(),
|
|
163
|
+
}
|
|
164
|
+
if target in candidates:
|
|
165
|
+
return item
|
|
166
|
+
return None
|
|
167
|
+
|
|
168
|
+
def build_list_report(self, kind: str, diagnostics: DiagnosticCollection | None = None) -> InventoryReport:
|
|
169
|
+
diagnostics = diagnostics or DiagnosticCollection()
|
|
170
|
+
items = self.list_items(kind)
|
|
171
|
+
if not items:
|
|
172
|
+
diagnostics.add_blocked(
|
|
173
|
+
code="core.inventory.kind_unknown",
|
|
174
|
+
message="Unknown inventory kind: {0}".format(kind),
|
|
175
|
+
path=kind,
|
|
176
|
+
expected="schemas|contracts|domains|adapters",
|
|
177
|
+
actual=kind,
|
|
178
|
+
)
|
|
179
|
+
status = "blocked"
|
|
180
|
+
else:
|
|
181
|
+
status = "pass"
|
|
182
|
+
return InventoryReport(
|
|
183
|
+
tool="core-runtime list",
|
|
184
|
+
command="list {0}".format(kind),
|
|
185
|
+
status=status,
|
|
186
|
+
summary={"kind": kind, "item_count": len(items)},
|
|
187
|
+
items=items,
|
|
188
|
+
diagnostics=diagnostics,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
def build_info_report(
|
|
192
|
+
self,
|
|
193
|
+
kind: str | None = None,
|
|
194
|
+
name: str | None = None,
|
|
195
|
+
diagnostics: DiagnosticCollection | None = None,
|
|
196
|
+
) -> InventoryReport:
|
|
197
|
+
diagnostics = diagnostics or DiagnosticCollection()
|
|
198
|
+
selection = {"kind": kind or "repository", "name": name or "all"}
|
|
199
|
+
|
|
200
|
+
if kind is None:
|
|
201
|
+
version_sources = self.version_inventory.discover()
|
|
202
|
+
summary = {
|
|
203
|
+
"repo_root": str(self.repo_root),
|
|
204
|
+
"canonical_version": self.version_inventory.get_canonical_version(),
|
|
205
|
+
"version_sources": len(version_sources),
|
|
206
|
+
"required_files_seen": sum(1 for exists in self.file_inventory.check_all(DiagnosticCollection()).values() if exists),
|
|
207
|
+
"schema_count": len(self.list_items("schemas")),
|
|
208
|
+
"contract_count": len(self.list_items("contracts")),
|
|
209
|
+
"domain_count": len(self.list_items("domains")),
|
|
210
|
+
"adapter_count": len(self.list_items("adapters")),
|
|
211
|
+
"item_count": 0,
|
|
212
|
+
}
|
|
213
|
+
return InventoryReport(
|
|
214
|
+
tool="core-runtime info",
|
|
215
|
+
command="info",
|
|
216
|
+
status="pass",
|
|
217
|
+
summary=summary,
|
|
218
|
+
selection=selection,
|
|
219
|
+
diagnostics=diagnostics,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
items = self.list_items(kind)
|
|
223
|
+
if not items:
|
|
224
|
+
diagnostics.add_blocked(
|
|
225
|
+
code="core.inventory.kind_unknown",
|
|
226
|
+
message="Unknown inventory kind: {0}".format(kind),
|
|
227
|
+
path=kind,
|
|
228
|
+
expected="schemas|contracts|domains|adapters",
|
|
229
|
+
actual=kind,
|
|
230
|
+
)
|
|
231
|
+
return InventoryReport(
|
|
232
|
+
tool="core-runtime info",
|
|
233
|
+
command="info {0}".format(kind),
|
|
234
|
+
status="blocked",
|
|
235
|
+
summary={"kind": kind, "item_count": 0},
|
|
236
|
+
selection=selection,
|
|
237
|
+
diagnostics=diagnostics,
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
if name is None:
|
|
241
|
+
return InventoryReport(
|
|
242
|
+
tool="core-runtime info",
|
|
243
|
+
command="info {0}".format(kind),
|
|
244
|
+
status="pass",
|
|
245
|
+
summary={"kind": kind, "item_count": len(items)},
|
|
246
|
+
selection=selection,
|
|
247
|
+
items=items,
|
|
248
|
+
diagnostics=diagnostics,
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
item = self.find_item(kind, name)
|
|
252
|
+
if item is None:
|
|
253
|
+
diagnostics.add_error(
|
|
254
|
+
code="core.inventory.item_not_found",
|
|
255
|
+
message="No {0} named '{1}' found".format(kind[:-1] if kind.endswith("s") else kind, name),
|
|
256
|
+
path=name,
|
|
257
|
+
expected=kind,
|
|
258
|
+
actual="not found",
|
|
259
|
+
)
|
|
260
|
+
return InventoryReport(
|
|
261
|
+
tool="core-runtime info",
|
|
262
|
+
command="info {0} {1}".format(kind, name),
|
|
263
|
+
status="error",
|
|
264
|
+
summary={"kind": kind, "item_count": 0},
|
|
265
|
+
selection=selection,
|
|
266
|
+
diagnostics=diagnostics,
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
return InventoryReport(
|
|
270
|
+
tool="core-runtime info",
|
|
271
|
+
command="info {0} {1}".format(kind, name),
|
|
272
|
+
status="pass",
|
|
273
|
+
summary={"kind": kind, "item_count": 1},
|
|
274
|
+
selection=selection,
|
|
275
|
+
items=[item],
|
|
276
|
+
diagnostics=diagnostics,
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
def _list_schemas(self) -> list[InventoryItem]:
|
|
280
|
+
schema_dir = self.repo_root / "schemas"
|
|
281
|
+
items: list[InventoryItem] = []
|
|
282
|
+
if not schema_dir.is_dir():
|
|
283
|
+
return items
|
|
284
|
+
|
|
285
|
+
for path in sorted(schema_dir.rglob("*.json")):
|
|
286
|
+
if not path.is_file():
|
|
287
|
+
continue
|
|
288
|
+
try:
|
|
289
|
+
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
290
|
+
except (OSError, json.JSONDecodeError):
|
|
291
|
+
payload = {}
|
|
292
|
+
title = payload.get("title")
|
|
293
|
+
schema_id = payload.get("$id")
|
|
294
|
+
schema_version = None
|
|
295
|
+
properties = payload.get("properties")
|
|
296
|
+
if isinstance(properties, dict):
|
|
297
|
+
version_prop = properties.get("schema_version")
|
|
298
|
+
if isinstance(version_prop, dict):
|
|
299
|
+
schema_version = version_prop.get("const")
|
|
300
|
+
items.append(
|
|
301
|
+
InventoryItem(
|
|
302
|
+
kind="schema",
|
|
303
|
+
name=str(title or path.stem),
|
|
304
|
+
path=str(path.relative_to(self.repo_root)),
|
|
305
|
+
title=str(title) if isinstance(title, str) else None,
|
|
306
|
+
summary=str(schema_version or "schema"),
|
|
307
|
+
details={
|
|
308
|
+
"schema_id": schema_id,
|
|
309
|
+
"schema_version": schema_version,
|
|
310
|
+
},
|
|
311
|
+
)
|
|
312
|
+
)
|
|
313
|
+
return items
|
|
314
|
+
|
|
315
|
+
def _list_contracts(self) -> list[InventoryItem]:
|
|
316
|
+
items: list[InventoryItem] = []
|
|
317
|
+
for rel_dir in ("contracts", "docs/contracts"):
|
|
318
|
+
directory = self.repo_root / rel_dir
|
|
319
|
+
if not directory.is_dir():
|
|
320
|
+
continue
|
|
321
|
+
for path in sorted(directory.rglob("*")):
|
|
322
|
+
if not path.is_file():
|
|
323
|
+
continue
|
|
324
|
+
if path.name.startswith("."):
|
|
325
|
+
continue
|
|
326
|
+
items.append(
|
|
327
|
+
InventoryItem(
|
|
328
|
+
kind="contract",
|
|
329
|
+
name=path.stem,
|
|
330
|
+
path=str(path.relative_to(self.repo_root)),
|
|
331
|
+
summary=path.suffix.lstrip(".") or "file",
|
|
332
|
+
details={"source_dir": rel_dir},
|
|
333
|
+
)
|
|
334
|
+
)
|
|
335
|
+
return items
|
|
336
|
+
|
|
337
|
+
def _list_domains(self) -> list[InventoryItem]:
|
|
338
|
+
domains_dir = self.repo_root / "core_runtime" / "domains"
|
|
339
|
+
items: list[InventoryItem] = []
|
|
340
|
+
if not domains_dir.is_dir():
|
|
341
|
+
return items
|
|
342
|
+
|
|
343
|
+
for path in sorted(domains_dir.iterdir()):
|
|
344
|
+
if not path.is_dir() or path.name.startswith("__"):
|
|
345
|
+
continue
|
|
346
|
+
module_files = sorted(
|
|
347
|
+
child.name for child in path.iterdir() if child.is_file() and child.suffix == ".py"
|
|
348
|
+
)
|
|
349
|
+
items.append(
|
|
350
|
+
InventoryItem(
|
|
351
|
+
kind="domain",
|
|
352
|
+
name=path.name,
|
|
353
|
+
path=str(path.relative_to(self.repo_root)),
|
|
354
|
+
summary="{0} python module(s)".format(len(module_files)),
|
|
355
|
+
details={"modules": module_files},
|
|
356
|
+
)
|
|
357
|
+
)
|
|
358
|
+
return items
|
|
359
|
+
|
|
360
|
+
def _list_adapters(self) -> list[InventoryItem]:
|
|
361
|
+
items: list[InventoryItem] = []
|
|
362
|
+
|
|
363
|
+
adapter_examples = self.repo_root / "examples" / "adapters"
|
|
364
|
+
if adapter_examples.is_dir():
|
|
365
|
+
for path in sorted(adapter_examples.iterdir()):
|
|
366
|
+
if not path.is_dir():
|
|
367
|
+
continue
|
|
368
|
+
fixture_count = len(list(path.rglob("manifest.json")))
|
|
369
|
+
items.append(
|
|
370
|
+
InventoryItem(
|
|
371
|
+
kind="adapter",
|
|
372
|
+
name=path.name,
|
|
373
|
+
path=str(path.relative_to(self.repo_root)),
|
|
374
|
+
summary="{0} fixture manifest(s)".format(fixture_count),
|
|
375
|
+
details={
|
|
376
|
+
"source": "examples/adapters",
|
|
377
|
+
"has_readme": (path / "README.md").is_file(),
|
|
378
|
+
},
|
|
379
|
+
)
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
domains_dir = self.repo_root / "core_runtime" / "domains"
|
|
383
|
+
if domains_dir.is_dir():
|
|
384
|
+
for domain_dir in sorted(domains_dir.iterdir()):
|
|
385
|
+
if not domain_dir.is_dir() or domain_dir.name.startswith("__"):
|
|
386
|
+
continue
|
|
387
|
+
adapter_file = domain_dir / "adapters.py"
|
|
388
|
+
if adapter_file.is_file():
|
|
389
|
+
items.append(
|
|
390
|
+
InventoryItem(
|
|
391
|
+
kind="adapter",
|
|
392
|
+
name="{0}.adapters".format(domain_dir.name),
|
|
393
|
+
path=str(adapter_file.relative_to(self.repo_root)),
|
|
394
|
+
summary="domain adapter module",
|
|
395
|
+
details={"source": "core_runtime/domains"},
|
|
396
|
+
)
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
return items
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""Safety checks - conservative scans for private path leakage and executable proposals."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import re
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from core_runtime.tooling.diagnostics import DiagnosticCollection
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# Known private paths/labs that should not leak into public examples
|
|
13
|
+
PRIVATE_PATH_MARKERS = [
|
|
14
|
+
"private_product_name",
|
|
15
|
+
"private_customer_name",
|
|
16
|
+
"/home/real-user/",
|
|
17
|
+
"/home/user/private",
|
|
18
|
+
"private/",
|
|
19
|
+
".env",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
# Patterns that indicate rejected fixtures (should be skipped)
|
|
23
|
+
REJECTED_PATTERNS = [
|
|
24
|
+
"rejected",
|
|
25
|
+
"REJECTED",
|
|
26
|
+
"malicious",
|
|
27
|
+
"invalid",
|
|
28
|
+
"not_accepted",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
# Known private adapter IDs
|
|
32
|
+
PRIVATE_ADAPTERS = [
|
|
33
|
+
"bridge_private_product",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
TEMPLATE_LEFTOVER_PATTERNS = (
|
|
37
|
+
re.compile(r"\bTODO\b"),
|
|
38
|
+
re.compile(r"\bFIXME\b"),
|
|
39
|
+
re.compile(r"\bXXX\b"),
|
|
40
|
+
re.compile(r"\bHACK\b"),
|
|
41
|
+
re.compile(r"\bREPLACE_ME\b"),
|
|
42
|
+
re.compile(r"\bTEMPLATE_VALUE\b"),
|
|
43
|
+
re.compile(r"\{\{\s*[^}]+\s*\}\}"),
|
|
44
|
+
re.compile(r"<[A-Z0-9][A-Z0-9_\-]*>"),
|
|
45
|
+
re.compile(r"template placeholder", re.IGNORECASE),
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class SafetyChecks:
|
|
50
|
+
"""Conservative safety scans for public examples."""
|
|
51
|
+
|
|
52
|
+
def __init__(self, repo_root: Path):
|
|
53
|
+
self.repo_root = repo_root
|
|
54
|
+
|
|
55
|
+
def check_private_path_leakage(self, diagnostics: DiagnosticCollection) -> None:
|
|
56
|
+
"""Scan examples for private path leakage."""
|
|
57
|
+
examples_dir = self.repo_root / "examples"
|
|
58
|
+
if not examples_dir.is_dir():
|
|
59
|
+
return
|
|
60
|
+
|
|
61
|
+
checked = 0
|
|
62
|
+
for json_file in examples_dir.rglob("*.json"):
|
|
63
|
+
rel = json_file.relative_to(self.repo_root)
|
|
64
|
+
|
|
65
|
+
# Skip rejected fixtures
|
|
66
|
+
if self._is_rejected_fixture(json_file, rel):
|
|
67
|
+
continue
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
text = json_file.read_text(encoding="utf-8")
|
|
71
|
+
except OSError:
|
|
72
|
+
continue
|
|
73
|
+
|
|
74
|
+
for marker in PRIVATE_PATH_MARKERS:
|
|
75
|
+
if marker in text:
|
|
76
|
+
diagnostics.add_warning(
|
|
77
|
+
code="core.safety.private_path_leak",
|
|
78
|
+
message="Possible private path leakage: '{0}' found in {1}".format(marker, rel),
|
|
79
|
+
path=str(rel),
|
|
80
|
+
details="Marker: {0}".format(marker),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
checked += 1
|
|
84
|
+
if checked > 200: # Limit to avoid very long runs
|
|
85
|
+
break
|
|
86
|
+
|
|
87
|
+
def _is_rejected_fixture(self, json_file: Path, rel_path: Path) -> bool:
|
|
88
|
+
"""Check if a fixture is explicitly rejected."""
|
|
89
|
+
# Check path patterns
|
|
90
|
+
rel_str = str(rel_path)
|
|
91
|
+
for pattern in REJECTED_PATTERNS:
|
|
92
|
+
if pattern.lower() in rel_str.lower():
|
|
93
|
+
return True
|
|
94
|
+
|
|
95
|
+
# Check content for REJECTED markers
|
|
96
|
+
try:
|
|
97
|
+
text = json_file.read_text(encoding="utf-8")
|
|
98
|
+
if "REJECTED" in text or "rejected" in text.lower():
|
|
99
|
+
return True
|
|
100
|
+
# Check for adapter_id with private prefix
|
|
101
|
+
data = json.loads(text)
|
|
102
|
+
adapter_id = data.get("adapter_id", "")
|
|
103
|
+
for private in PRIVATE_ADAPTERS:
|
|
104
|
+
if private in adapter_id:
|
|
105
|
+
return True
|
|
106
|
+
except (json.JSONDecodeError, OSError):
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
return False
|
|
110
|
+
|
|
111
|
+
def check_proposal_execution_safety(self, diagnostics: DiagnosticCollection) -> None:
|
|
112
|
+
"""Check that command-like proposals are not treated as executable."""
|
|
113
|
+
examples_dir = self.repo_root / "examples"
|
|
114
|
+
if not examples_dir.is_dir():
|
|
115
|
+
return
|
|
116
|
+
|
|
117
|
+
# Check agent plans and sessions for executable intent
|
|
118
|
+
for subdir in ["agent_plans", "agent_sessions", "agent_traces"]:
|
|
119
|
+
subdir_path = examples_dir / subdir
|
|
120
|
+
if not subdir_path.is_dir():
|
|
121
|
+
continue
|
|
122
|
+
|
|
123
|
+
for json_file in subdir_path.rglob("*.json"):
|
|
124
|
+
try:
|
|
125
|
+
data = json.loads(json_file.read_text(encoding="utf-8"))
|
|
126
|
+
except (json.JSONDecodeError, OSError):
|
|
127
|
+
continue
|
|
128
|
+
|
|
129
|
+
# Look for intent fields that suggest execution
|
|
130
|
+
intent = data.get("intent", "")
|
|
131
|
+
if isinstance(intent, str):
|
|
132
|
+
executable_keywords = ["execute", "run", "deploy", "install", "delete", "remove", "modify", "write", "ssh", "curl", "wget"]
|
|
133
|
+
for keyword in executable_keywords:
|
|
134
|
+
if keyword in intent.lower():
|
|
135
|
+
rel = json_file.relative_to(self.repo_root)
|
|
136
|
+
diagnostics.add_warning(
|
|
137
|
+
code="core.safety.proposal_executable_intent",
|
|
138
|
+
message="Agent fixture may contain executable intent: {0}".format(keyword),
|
|
139
|
+
path=str(rel),
|
|
140
|
+
details="Intent field: {0}".format(intent[:200]),
|
|
141
|
+
)
|
|
142
|
+
break
|
|
143
|
+
|
|
144
|
+
def check_todo_templates(self, diagnostics: DiagnosticCollection) -> None:
|
|
145
|
+
"""Check for explicit TODO/FIXME/template-placeholder leftovers in key files."""
|
|
146
|
+
# Only check a few key files, not entire codebase
|
|
147
|
+
key_files = [
|
|
148
|
+
"scripts/verify_release.py",
|
|
149
|
+
"scripts/bump_version.py",
|
|
150
|
+
"scripts/check_version_consistency.py",
|
|
151
|
+
"docs/VERSIONING_POLICY.md",
|
|
152
|
+
"docs/QUALITY_GATE.md",
|
|
153
|
+
]
|
|
154
|
+
|
|
155
|
+
for rel_path in key_files:
|
|
156
|
+
full_path = self.repo_root / rel_path
|
|
157
|
+
if not full_path.exists():
|
|
158
|
+
continue
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
text = full_path.read_text(encoding="utf-8")
|
|
162
|
+
except OSError:
|
|
163
|
+
continue
|
|
164
|
+
|
|
165
|
+
for pattern in TEMPLATE_LEFTOVER_PATTERNS:
|
|
166
|
+
if pattern.search(text):
|
|
167
|
+
diagnostics.add_warning(
|
|
168
|
+
code="core.safety.template_leftover",
|
|
169
|
+
message="Template marker '{0}' found in {1}".format(pattern.pattern, rel_path),
|
|
170
|
+
path=rel_path,
|
|
171
|
+
details="Pattern: {0}".format(pattern.pattern),
|
|
172
|
+
)
|