@trac3er/oh-my-god 2.0.8 → 2.0.9

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 (118) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.gemini/settings.json +11 -0
  4. package/.kimi/mcp.json +11 -0
  5. package/CHANGELOG.md +10 -0
  6. package/OMG-setup.sh +1 -1
  7. package/OMG_COMPAT_CONTRACT.md +10 -4
  8. package/README.md +1 -0
  9. package/build/lib/commands/OMG:forge.md +92 -0
  10. package/build/lib/commands/OMG:mode.md +13 -13
  11. package/build/lib/commands/OMG:session-branch.md +17 -1
  12. package/build/lib/commands/OMG:session-fork.md +5 -1
  13. package/build/lib/commands/OMG:session-merge.md +5 -1
  14. package/build/lib/control_plane/server.py +4 -0
  15. package/build/lib/control_plane/service.py +55 -0
  16. package/build/lib/hooks/setup_wizard.py +21 -1
  17. package/build/lib/hooks/shadow_manager.py +25 -2
  18. package/build/lib/hooks/state_migration.py +3 -0
  19. package/build/lib/plugins/dephealth/cve_scanner.py +91 -0
  20. package/build/lib/plugins/dephealth/vuln_analyzer.py +7 -0
  21. package/build/lib/registry/omg-capability.schema.json +83 -1
  22. package/build/lib/runtime/adoption.py +12 -4
  23. package/build/lib/runtime/artifact_parsers.py +161 -0
  24. package/build/lib/runtime/background_verification.py +48 -0
  25. package/build/lib/runtime/claim_judge.py +184 -7
  26. package/build/lib/runtime/contract_compiler.py +118 -9
  27. package/build/lib/runtime/evidence_query.py +203 -0
  28. package/build/lib/runtime/omg_mcp_server.py +19 -0
  29. package/build/lib/runtime/playwright_adapter.py +39 -0
  30. package/build/lib/runtime/proof_chain.py +136 -8
  31. package/build/lib/runtime/proof_gate.py +102 -0
  32. package/build/lib/runtime/providers/gemini_provider.py +7 -0
  33. package/build/lib/runtime/providers/kimi_provider.py +7 -0
  34. package/build/lib/runtime/repro_pack.py +292 -0
  35. package/build/lib/runtime/runtime_profile.py +87 -15
  36. package/build/lib/runtime/security_check.py +86 -3
  37. package/build/lib/runtime/test_intent_lock.py +47 -0
  38. package/build/lib/runtime/tracebank.py +33 -3
  39. package/build/lib/runtime/verification_loop.py +73 -0
  40. package/commands/OMG:forge.md +92 -0
  41. package/commands/OMG:mode.md +13 -13
  42. package/commands/OMG:session-branch.md +17 -1
  43. package/commands/OMG:session-fork.md +5 -1
  44. package/commands/OMG:session-merge.md +5 -1
  45. package/control_plane/server.py +4 -0
  46. package/control_plane/service.py +55 -0
  47. package/dist/enterprise/bundle/.gemini/settings.json +11 -0
  48. package/dist/enterprise/bundle/.kimi/mcp.json +11 -0
  49. package/dist/enterprise/bundle/OMG_COMPAT_CONTRACT.md +9 -3
  50. package/dist/enterprise/bundle/registry/omg-capability.schema.json +83 -1
  51. package/dist/enterprise/bundle/settings.json +1 -0
  52. package/dist/enterprise/manifest.json +17 -3
  53. package/dist/public/bundle/.agents/skills/omg/incident-replay/SKILL.md +1 -1
  54. package/dist/public/bundle/.agents/skills/omg/incident-replay/openai.yaml +1 -1
  55. package/dist/public/bundle/.agents/skills/omg/lsp-pack/SKILL.md +1 -1
  56. package/dist/public/bundle/.agents/skills/omg/lsp-pack/openai.yaml +1 -1
  57. package/dist/public/bundle/.agents/skills/omg/mcp-fabric/SKILL.md +1 -1
  58. package/dist/public/bundle/.agents/skills/omg/mcp-fabric/openai.yaml +1 -1
  59. package/dist/public/bundle/.agents/skills/omg/plan-council/SKILL.md +1 -1
  60. package/dist/public/bundle/.agents/skills/omg/plan-council/openai.yaml +1 -1
  61. package/dist/public/bundle/.agents/skills/omg/preflight/SKILL.md +1 -1
  62. package/dist/public/bundle/.agents/skills/omg/preflight/openai.yaml +1 -1
  63. package/dist/public/bundle/.agents/skills/omg/proof-gate/SKILL.md +1 -1
  64. package/dist/public/bundle/.agents/skills/omg/proof-gate/openai.yaml +1 -1
  65. package/dist/public/bundle/.agents/skills/omg/remote-supervisor/SKILL.md +1 -1
  66. package/dist/public/bundle/.agents/skills/omg/remote-supervisor/openai.yaml +1 -1
  67. package/dist/public/bundle/.agents/skills/omg/robotics/SKILL.md +1 -1
  68. package/dist/public/bundle/.agents/skills/omg/robotics/openai.yaml +1 -1
  69. package/dist/public/bundle/.agents/skills/omg/secure-worktree-pipeline/SKILL.md +1 -1
  70. package/dist/public/bundle/.agents/skills/omg/secure-worktree-pipeline/openai.yaml +1 -1
  71. package/dist/public/bundle/.agents/skills/omg/security-check/SKILL.md +1 -1
  72. package/dist/public/bundle/.agents/skills/omg/security-check/openai.yaml +1 -1
  73. package/dist/public/bundle/.agents/skills/omg/test-intent-lock/SKILL.md +1 -1
  74. package/dist/public/bundle/.agents/skills/omg/test-intent-lock/openai.yaml +1 -1
  75. package/dist/public/bundle/.agents/skills/omg/tracebank/SKILL.md +1 -1
  76. package/dist/public/bundle/.agents/skills/omg/tracebank/openai.yaml +1 -1
  77. package/dist/public/bundle/.agents/skills/omg/vision/SKILL.md +1 -1
  78. package/dist/public/bundle/.agents/skills/omg/vision/openai.yaml +1 -1
  79. package/dist/public/bundle/.gemini/settings.json +11 -0
  80. package/dist/public/bundle/.kimi/mcp.json +11 -0
  81. package/dist/public/bundle/OMG_COMPAT_CONTRACT.md +9 -3
  82. package/dist/public/bundle/registry/omg-capability.schema.json +83 -1
  83. package/dist/public/bundle/settings.json +2 -1
  84. package/dist/public/manifest.json +43 -29
  85. package/docs/proof.md +1 -0
  86. package/hooks/setup_wizard.py +21 -1
  87. package/hooks/shadow_manager.py +25 -2
  88. package/hooks/state_migration.py +3 -0
  89. package/hud/omg-hud.mjs +66 -3
  90. package/package.json +1 -1
  91. package/plugins/advanced/plugin.json +1 -1
  92. package/plugins/core/plugin.json +1 -1
  93. package/plugins/dephealth/cve_scanner.py +91 -0
  94. package/plugins/dephealth/vuln_analyzer.py +7 -0
  95. package/pyproject.toml +1 -1
  96. package/registry/omg-capability.schema.json +83 -1
  97. package/runtime/adoption.py +13 -5
  98. package/runtime/artifact_parsers.py +161 -0
  99. package/runtime/background_verification.py +48 -0
  100. package/runtime/claim_judge.py +184 -7
  101. package/runtime/contract_compiler.py +118 -9
  102. package/runtime/evidence_query.py +203 -0
  103. package/runtime/omg_mcp_server.py +19 -0
  104. package/runtime/playwright_adapter.py +39 -0
  105. package/runtime/proof_chain.py +136 -8
  106. package/runtime/proof_gate.py +102 -0
  107. package/runtime/providers/gemini_provider.py +7 -0
  108. package/runtime/providers/kimi_provider.py +7 -0
  109. package/runtime/repro_pack.py +292 -0
  110. package/runtime/runtime_profile.py +87 -15
  111. package/runtime/security_check.py +86 -3
  112. package/runtime/test_intent_lock.py +47 -0
  113. package/runtime/tracebank.py +33 -3
  114. package/runtime/verification_loop.py +73 -0
  115. package/scripts/omg.py +30 -3
  116. package/settings.json +4 -3
  117. package/tools/python_sandbox.py +9 -6
  118. package/tools/session_snapshot.py +146 -40
@@ -174,6 +174,25 @@ def omg_security_check(scope: str = ".", include_live_enrichment: bool = False,
174
174
  return payload
175
175
 
176
176
 
177
+ @mcp.tool()
178
+ def omg_claim_judge(claims: list[dict[str, Any]]) -> dict[str, Any]:
179
+ _status, payload = _service().claim_judge({"claims": claims})
180
+ return payload
181
+
182
+
183
+ @mcp.tool()
184
+ def omg_test_intent_lock(
185
+ action: str,
186
+ intent: dict[str, Any] | None = None,
187
+ lock_id: str | None = None,
188
+ results: dict[str, Any] | None = None,
189
+ ) -> dict[str, Any]:
190
+ _status, payload = _service().test_intent_lock(
191
+ {"action": action, "intent": intent, "lock_id": lock_id, "results": results}
192
+ )
193
+ return payload
194
+
195
+
177
196
  @mcp.tool()
178
197
  def omg_guide_assert(candidate: str, rules: dict[str, Any]) -> dict[str, Any]:
179
198
  _status, payload = _service().guide_assert({"candidate": candidate, "rules": rules})
@@ -0,0 +1,39 @@
1
+ """Optional adapter for summarizing Playwright artifacts into proof-chain-friendly payloads."""
2
+ from __future__ import annotations
3
+
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+
8
+ def summarize_playwright_artifacts(
9
+ trace_path: str | None = None,
10
+ junit_path: str | None = None,
11
+ screenshots: list[str] | None = None,
12
+ metadata: dict[str, Any] | None = None,
13
+ ) -> dict[str, Any]:
14
+ """Summarize browser artifacts into a proof-chain-friendly dict.
15
+
16
+ Returns a dict consumable by proof-gate / claim-judge:
17
+ status — "ok" or "error"
18
+ artifacts — {trace, junit, screenshots} (only provided paths)
19
+ metadata — provided metadata or {}
20
+ """
21
+ if not trace_path and not junit_path and not screenshots:
22
+ return {"status": "error", "reason": "no_artifacts_provided"}
23
+
24
+ artifacts: dict[str, Any] = {}
25
+
26
+ if trace_path:
27
+ artifacts["trace"] = str(Path(trace_path))
28
+
29
+ if junit_path:
30
+ artifacts["junit"] = str(Path(junit_path))
31
+
32
+ if screenshots:
33
+ artifacts["screenshots"] = [str(Path(s)) for s in screenshots]
34
+
35
+ return {
36
+ "status": "ok",
37
+ "artifacts": artifacts,
38
+ "metadata": metadata or {},
39
+ }
@@ -6,6 +6,9 @@ from pathlib import Path
6
6
  from typing import Any
7
7
 
8
8
 
9
+ _REQUIRED_ARTIFACT_FIELDS = ("kind", "path", "sha256", "parser", "summary", "trace_id")
10
+
11
+
9
12
  def _load_json(path: Path) -> dict[str, Any]:
10
13
  return json.loads(path.read_text(encoding="utf-8"))
11
14
 
@@ -26,6 +29,105 @@ def _read_jsonl(path: Path) -> list[dict[str, Any]]:
26
29
  return rows
27
30
 
28
31
 
32
+ def _hash_path(path: Path) -> str:
33
+ if not path.exists() or not path.is_file():
34
+ return ""
35
+ h = hashlib.sha256()
36
+ with path.open("rb") as handle:
37
+ while True:
38
+ chunk = handle.read(8192)
39
+ if not chunk:
40
+ break
41
+ h.update(chunk)
42
+ return h.hexdigest()
43
+
44
+
45
+ def _normalize_evidence_pack(payload: dict[str, Any]) -> dict[str, Any]:
46
+ if not isinstance(payload, dict):
47
+ raise ValueError("evidence_pack_invalid_payload")
48
+ schema_version = payload.get("schema_version")
49
+ if schema_version is None:
50
+ return payload
51
+ if schema_version != 2:
52
+ raise ValueError("evidence_pack_unsupported_schema_version")
53
+
54
+ artifacts = payload.get("artifacts", [])
55
+ if not isinstance(artifacts, list):
56
+ raise ValueError("evidence_pack_invalid_artifacts")
57
+
58
+ for index, artifact in enumerate(artifacts):
59
+ if not isinstance(artifact, dict):
60
+ raise ValueError(f"evidence_pack_artifact_invalid_type:{index}")
61
+ for field in _REQUIRED_ARTIFACT_FIELDS:
62
+ value = str(artifact.get(field, "")).strip()
63
+ if not value:
64
+ raise ValueError(f"evidence_pack_artifact_missing_{field}:{index}")
65
+ return payload
66
+
67
+
68
+ def _artifact_record(*, kind: str, path: str, parser: str, summary: str, trace_id: str, sha256: str = "") -> dict[str, str]:
69
+ return {
70
+ "kind": kind,
71
+ "path": path,
72
+ "sha256": sha256,
73
+ "parser": parser,
74
+ "summary": summary,
75
+ "trace_id": trace_id,
76
+ }
77
+
78
+
79
+ def _build_chain_artifacts(
80
+ *,
81
+ output_root: Path,
82
+ selected_path: str,
83
+ evidence_payload: dict[str, Any],
84
+ trace_payload: dict[str, Any],
85
+ eval_payload: dict[str, Any],
86
+ lineage: dict[str, Any] | Any,
87
+ trace_id: str,
88
+ ) -> list[dict[str, str]]:
89
+ artifacts: list[dict[str, str]] = []
90
+ raw_artifacts = evidence_payload.get("artifacts", [])
91
+ if isinstance(raw_artifacts, list):
92
+ for item in raw_artifacts:
93
+ if not isinstance(item, dict):
94
+ continue
95
+ path = str(item.get("path", "")).strip()
96
+ artifacts.append(
97
+ _artifact_record(
98
+ kind=str(item.get("kind", "")).strip() or "artifact",
99
+ path=path,
100
+ sha256=str(item.get("sha256", "")).strip(),
101
+ parser=str(item.get("parser", "")).strip() or "unknown",
102
+ summary=str(item.get("summary", "")).strip() or "evidence artifact",
103
+ trace_id=str(item.get("trace_id", "")).strip() or trace_id,
104
+ )
105
+ )
106
+
107
+ lineage_path = str((lineage or {}).get("path", "")).strip() if isinstance(lineage, dict) else ""
108
+ canonical_artifacts = [
109
+ ("trace", str(trace_payload.get("path", ".omg/tracebank/events.jsonl")).strip(), "jsonl", "tracebank event stream"),
110
+ ("eval", ".omg/evals/latest.json" if eval_payload else "", "json", "evaluation result"),
111
+ ("lineage", lineage_path, "json", "lineage manifest"),
112
+ ("evidence", selected_path, "json", "evidence pack"),
113
+ ]
114
+ for kind, path, parser, summary in canonical_artifacts:
115
+ if not path:
116
+ continue
117
+ file_path = output_root / path
118
+ artifacts.append(
119
+ _artifact_record(
120
+ kind=kind,
121
+ path=path,
122
+ sha256=_hash_path(file_path),
123
+ parser=parser,
124
+ summary=summary,
125
+ trace_id=trace_id,
126
+ )
127
+ )
128
+ return artifacts
129
+
130
+
29
131
  def _latest_evidence_pack(output_root: Path) -> tuple[str, dict[str, Any]]:
30
132
  evidence_dir = output_root / ".omg" / "evidence"
31
133
  if not evidence_dir.exists():
@@ -60,9 +162,10 @@ def assemble_proof_chain(project_dir: str, *, evidence_path: str | None = None)
60
162
 
61
163
  if evidence_path:
62
164
  selected_path = str(evidence_path)
63
- evidence_payload = _load_json(output_root / selected_path)
165
+ evidence_payload = _normalize_evidence_pack(_load_json(output_root / selected_path))
64
166
  else:
65
167
  selected_path, evidence_payload = _latest_evidence_pack(output_root)
168
+ evidence_payload = _normalize_evidence_pack(evidence_payload)
66
169
 
67
170
  trace_id = ""
68
171
  trace_ids = evidence_payload.get("trace_ids", [])
@@ -79,6 +182,7 @@ def assemble_proof_chain(project_dir: str, *, evidence_path: str | None = None)
79
182
 
80
183
  chain = {
81
184
  "schema": "ProofChain",
185
+ "schema_version": 2,
82
186
  "trace_id": trace_id,
83
187
  "eval_id": eval_id,
84
188
  "eval_trace_id": str(eval_payload.get("trace_id", "")),
@@ -91,16 +195,35 @@ def assemble_proof_chain(project_dir: str, *, evidence_path: str | None = None)
91
195
  "environment": evidence_payload.get("environment") or trace_payload.get("environment") or eval_payload.get("environment") or {"hostname": "unknown", "platform": "unknown"},
92
196
  "ci_job_url": evidence_payload.get("ci_job_url") or "",
93
197
  "external_inputs": evidence_payload.get("external_inputs", []),
94
- "artifacts": {
95
- "trace": trace_payload.get("path", ".omg/tracebank/events.jsonl"),
96
- "eval": ".omg/evals/latest.json" if eval_payload else "",
97
- "lineage": str((lineage or {}).get("path", "")) if isinstance(lineage, dict) else "",
98
- "evidence": selected_path,
99
- },
198
+ "artifacts": _build_chain_artifacts(
199
+ output_root=output_root,
200
+ selected_path=selected_path,
201
+ evidence_payload=evidence_payload,
202
+ trace_payload=trace_payload,
203
+ eval_payload=eval_payload,
204
+ lineage=lineage,
205
+ trace_id=trace_id,
206
+ ),
100
207
  }
101
208
  validation = validate_proof_chain(chain)
102
209
  chain["status"] = validation["status"]
103
210
  chain["blockers"] = validation["blockers"]
211
+
212
+ try:
213
+ from runtime.background_verification import publish_verification_state
214
+
215
+ evidence_links = [selected_path] if selected_path else []
216
+ publish_verification_state(
217
+ project_dir=project_dir,
218
+ run_id=str(chain.get("eval_id", "")),
219
+ status=str(validation["status"]),
220
+ blockers=list(validation.get("blockers", [])),
221
+ evidence_links=evidence_links,
222
+ progress={"phase": "proof_chain_assembled"},
223
+ )
224
+ except Exception:
225
+ pass
226
+
104
227
  return chain
105
228
 
106
229
 
@@ -162,9 +285,10 @@ def build_proof_gate_input(project_dir: str, *, evidence_path: str | None = None
162
285
 
163
286
  if evidence_path:
164
287
  selected_path = str(evidence_path)
165
- evidence_payload = _load_json(output_root / selected_path)
288
+ evidence_payload = _normalize_evidence_pack(_load_json(output_root / selected_path))
166
289
  else:
167
290
  selected_path, evidence_payload = _latest_evidence_pack(output_root)
291
+ evidence_payload = _normalize_evidence_pack(evidence_payload)
168
292
 
169
293
  security_evidence = _resolve_security_evidence(output_root=output_root, evidence_payload=evidence_payload)
170
294
  browser_evidence = _resolve_browser_evidence(output_root=output_root, evidence_payload=evidence_payload)
@@ -212,6 +336,10 @@ def _resolve_browser_evidence(*, output_root: Path, evidence_payload: dict[str,
212
336
  if path:
213
337
  candidates.append(path)
214
338
 
339
+ adapter_matches = sorted(output_root.glob(".omg/evidence/playwright-adapter-*.json"))
340
+ if adapter_matches:
341
+ candidates.append(adapter_matches[0].relative_to(output_root).as_posix())
342
+
215
343
  candidates.extend(
216
344
  [
217
345
  ".omg/evidence/browser-evidence.json",
@@ -1,7 +1,14 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import hashlib
4
+ from pathlib import Path
3
5
  from typing import Any
4
6
 
7
+ from runtime import artifact_parsers
8
+
9
+
10
+ _REQUIRED_ARTIFACT_FIELDS = ("kind", "path", "sha256", "parser", "summary", "trace_id")
11
+
5
12
 
6
13
  def evaluate_proof_gate(input: dict[str, Any]) -> dict[str, Any]:
7
14
  claims = _as_claims(input.get("claims"))
@@ -9,6 +16,7 @@ def evaluate_proof_gate(input: dict[str, Any]) -> dict[str, Any]:
9
16
  eval_output = _as_dict(input.get("eval_output"))
10
17
  security_evidence = _as_dict(input.get("security_evidence"))
11
18
  browser_evidence = _as_dict(input.get("browser_evidence"))
19
+ evidence_pack = _as_dict(input.get("evidence_pack"))
12
20
 
13
21
  blockers: list[str] = []
14
22
  if not claims:
@@ -27,6 +35,7 @@ def evaluate_proof_gate(input: dict[str, Any]) -> dict[str, Any]:
27
35
  blockers.extend(_validate_claim_artifacts(claims))
28
36
  blockers.extend(_validate_trace_linkage(claims=claims, trace_id=trace_id, eval_output=eval_output, browser_evidence=browser_evidence))
29
37
  blockers.extend(_validate_security_and_browser_artifacts(claims=claims, security_evidence=security_evidence, browser_evidence=browser_evidence))
38
+ blockers.extend(_validate_evidence_pack(evidence_pack))
30
39
 
31
40
  unique_blockers = list(dict.fromkeys(item for item in blockers if str(item).strip()))
32
41
  evidence_summary = {
@@ -93,8 +102,10 @@ def _collect_trace_ids(claim: dict[str, Any]) -> set[str]:
93
102
 
94
103
  def _validate_claim_artifacts(claims: list[dict[str, Any]]) -> list[str]:
95
104
  all_artifacts: list[str] = []
105
+ artifact_records: list[dict[str, Any]] = []
96
106
  for claim in claims:
97
107
  all_artifacts.extend(_collect_artifacts(claim))
108
+ artifact_records.extend(_extract_artifact_records(claim))
98
109
 
99
110
  blockers: list[str] = []
100
111
  required_tokens = {
@@ -106,6 +117,24 @@ def _validate_claim_artifacts(claims: list[dict[str, Any]]) -> list[str]:
106
117
  for key, tokens in required_tokens.items():
107
118
  if not any(any(token in artifact for token in tokens) for artifact in all_artifacts):
108
119
  blockers.append(f"proof_gate_missing_artifact_{key}")
120
+
121
+ for artifact in artifact_records:
122
+ kind = str(artifact.get("kind", "")).strip().lower()
123
+ path = str(artifact.get("path", "")).strip()
124
+ if not kind or not path:
125
+ continue
126
+
127
+ parse_result = _parse_artifact(kind=kind, path=path)
128
+ if not parse_result.get("valid"):
129
+ error = str(parse_result.get("error", "")).strip()
130
+ if error == "file_not_found":
131
+ blockers.append(f"proof_gate_artifact_file_missing_{kind}")
132
+ else:
133
+ blockers.append(f"proof_gate_artifact_parse_failed_{kind}")
134
+
135
+ hash_blocker = _validate_artifact_hash(artifact)
136
+ if hash_blocker:
137
+ blockers.append(hash_blocker)
109
138
  return blockers
110
139
 
111
140
 
@@ -161,3 +190,76 @@ def _validate_security_and_browser_artifacts(
161
190
  blockers.append("proof_gate_browser_trace_not_linked_by_claims")
162
191
 
163
192
  return blockers
193
+
194
+
195
+ def _validate_evidence_pack(payload: dict[str, Any]) -> list[str]:
196
+ if not payload:
197
+ return []
198
+ if str(payload.get("schema", "")).strip() != "EvidencePack":
199
+ return ["proof_gate_invalid_evidence_pack"]
200
+
201
+ schema_version = payload.get("schema_version")
202
+ if schema_version is None:
203
+ return []
204
+ if schema_version != 2:
205
+ return ["proof_gate_unsupported_evidence_schema_version"]
206
+
207
+ artifacts = payload.get("artifacts", [])
208
+ if not isinstance(artifacts, list):
209
+ return ["proof_gate_invalid_evidence_pack"]
210
+
211
+ blockers: list[str] = []
212
+ for artifact in artifacts:
213
+ if not isinstance(artifact, dict):
214
+ blockers.append("proof_gate_invalid_evidence_pack")
215
+ continue
216
+ for field in _REQUIRED_ARTIFACT_FIELDS:
217
+ value = str(artifact.get(field, "")).strip()
218
+ if not value:
219
+ blockers.append(f"proof_gate_evidence_artifact_missing_{field}")
220
+ return blockers
221
+
222
+
223
+ def _extract_artifact_records(claim: dict[str, Any]) -> list[dict[str, Any]]:
224
+ evidence = _as_dict(claim.get("evidence"))
225
+ raw_artifacts = evidence.get("artifacts", claim.get("artifacts", []))
226
+ if not isinstance(raw_artifacts, list):
227
+ return []
228
+ return [item for item in raw_artifacts if isinstance(item, dict)]
229
+
230
+
231
+ def _parse_artifact(*, kind: str, path: str) -> dict[str, Any]:
232
+ parser = _PARSERS.get(kind)
233
+ if parser is None:
234
+ return {"valid": False, "summary": {}, "error": "unsupported_artifact_kind"}
235
+ return parser(path)
236
+
237
+
238
+ def _validate_artifact_hash(artifact: dict[str, Any]) -> str | None:
239
+ sha256_value = str(artifact.get("sha256", "")).strip().lower()
240
+ path = str(artifact.get("path", "")).strip()
241
+ kind = str(artifact.get("kind", "artifact")).strip().lower() or "artifact"
242
+ if not sha256_value or not path:
243
+ return None
244
+ if len(sha256_value) != 64 or any(ch not in "0123456789abcdef" for ch in sha256_value):
245
+ return None
246
+
247
+ file_path = Path(path)
248
+ if not file_path.exists():
249
+ return f"proof_gate_artifact_file_missing_{kind}"
250
+ try:
251
+ digest = hashlib.sha256(file_path.read_bytes()).hexdigest()
252
+ except OSError:
253
+ return f"proof_gate_artifact_hash_unreadable_{kind}"
254
+ if digest != sha256_value:
255
+ return f"proof_gate_artifact_hash_mismatch_{kind}"
256
+ return None
257
+
258
+
259
+ _PARSERS: dict[str, Any] = {
260
+ "junit": artifact_parsers.parse_junit,
261
+ "sarif": artifact_parsers.parse_sarif,
262
+ "coverage": artifact_parsers.parse_coverage,
263
+ "browser_trace": artifact_parsers.parse_browser_trace,
264
+ "diff_hunk": artifact_parsers.parse_diff_hunk,
265
+ }
@@ -16,6 +16,13 @@ from runtime.tmux_session_manager import TmuxSessionManager
16
16
 
17
17
  _logger = logging.getLogger(__name__)
18
18
 
19
+ HOST_RULES = {
20
+ "compilation_targets": [".gemini/settings.json"],
21
+ "mcp": ["omg-control"],
22
+ "skills": ["omg/control-plane", "omg/mcp-fabric"],
23
+ "automations": ["contract-validate", "provider-routing"],
24
+ }
25
+
19
26
 
20
27
  class GeminiProvider(CLIProvider):
21
28
  """CLIProvider implementation for the Gemini CLI (``gemini``)."""
@@ -16,6 +16,13 @@ from runtime.tmux_session_manager import TmuxSessionManager
16
16
 
17
17
  _logger = logging.getLogger(__name__)
18
18
 
19
+ HOST_RULES = {
20
+ "compilation_targets": [".kimi/mcp.json"],
21
+ "mcp": ["omg-control"],
22
+ "skills": ["omg/control-plane", "omg/mcp-fabric"],
23
+ "automations": ["contract-validate", "provider-routing"],
24
+ }
25
+
19
26
 
20
27
  class KimiCodeProvider(CLIProvider):
21
28
  """CLIProvider implementation for the Kimi Code CLI (``kimi``)."""