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,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
+ )