@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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.gemini/settings.json +11 -0
- package/.kimi/mcp.json +11 -0
- package/CHANGELOG.md +10 -0
- package/OMG-setup.sh +1 -1
- package/OMG_COMPAT_CONTRACT.md +10 -4
- package/README.md +1 -0
- package/build/lib/commands/OMG:forge.md +92 -0
- package/build/lib/commands/OMG:mode.md +13 -13
- package/build/lib/commands/OMG:session-branch.md +17 -1
- package/build/lib/commands/OMG:session-fork.md +5 -1
- package/build/lib/commands/OMG:session-merge.md +5 -1
- package/build/lib/control_plane/server.py +4 -0
- package/build/lib/control_plane/service.py +55 -0
- package/build/lib/hooks/setup_wizard.py +21 -1
- package/build/lib/hooks/shadow_manager.py +25 -2
- package/build/lib/hooks/state_migration.py +3 -0
- package/build/lib/plugins/dephealth/cve_scanner.py +91 -0
- package/build/lib/plugins/dephealth/vuln_analyzer.py +7 -0
- package/build/lib/registry/omg-capability.schema.json +83 -1
- package/build/lib/runtime/adoption.py +12 -4
- package/build/lib/runtime/artifact_parsers.py +161 -0
- package/build/lib/runtime/background_verification.py +48 -0
- package/build/lib/runtime/claim_judge.py +184 -7
- package/build/lib/runtime/contract_compiler.py +118 -9
- package/build/lib/runtime/evidence_query.py +203 -0
- package/build/lib/runtime/omg_mcp_server.py +19 -0
- package/build/lib/runtime/playwright_adapter.py +39 -0
- package/build/lib/runtime/proof_chain.py +136 -8
- package/build/lib/runtime/proof_gate.py +102 -0
- package/build/lib/runtime/providers/gemini_provider.py +7 -0
- package/build/lib/runtime/providers/kimi_provider.py +7 -0
- package/build/lib/runtime/repro_pack.py +292 -0
- package/build/lib/runtime/runtime_profile.py +87 -15
- package/build/lib/runtime/security_check.py +86 -3
- package/build/lib/runtime/test_intent_lock.py +47 -0
- package/build/lib/runtime/tracebank.py +33 -3
- package/build/lib/runtime/verification_loop.py +73 -0
- package/commands/OMG:forge.md +92 -0
- package/commands/OMG:mode.md +13 -13
- package/commands/OMG:session-branch.md +17 -1
- package/commands/OMG:session-fork.md +5 -1
- package/commands/OMG:session-merge.md +5 -1
- package/control_plane/server.py +4 -0
- package/control_plane/service.py +55 -0
- package/dist/enterprise/bundle/.gemini/settings.json +11 -0
- package/dist/enterprise/bundle/.kimi/mcp.json +11 -0
- package/dist/enterprise/bundle/OMG_COMPAT_CONTRACT.md +9 -3
- package/dist/enterprise/bundle/registry/omg-capability.schema.json +83 -1
- package/dist/enterprise/bundle/settings.json +1 -0
- package/dist/enterprise/manifest.json +17 -3
- package/dist/public/bundle/.agents/skills/omg/incident-replay/SKILL.md +1 -1
- package/dist/public/bundle/.agents/skills/omg/incident-replay/openai.yaml +1 -1
- package/dist/public/bundle/.agents/skills/omg/lsp-pack/SKILL.md +1 -1
- package/dist/public/bundle/.agents/skills/omg/lsp-pack/openai.yaml +1 -1
- package/dist/public/bundle/.agents/skills/omg/mcp-fabric/SKILL.md +1 -1
- package/dist/public/bundle/.agents/skills/omg/mcp-fabric/openai.yaml +1 -1
- package/dist/public/bundle/.agents/skills/omg/plan-council/SKILL.md +1 -1
- package/dist/public/bundle/.agents/skills/omg/plan-council/openai.yaml +1 -1
- package/dist/public/bundle/.agents/skills/omg/preflight/SKILL.md +1 -1
- package/dist/public/bundle/.agents/skills/omg/preflight/openai.yaml +1 -1
- package/dist/public/bundle/.agents/skills/omg/proof-gate/SKILL.md +1 -1
- package/dist/public/bundle/.agents/skills/omg/proof-gate/openai.yaml +1 -1
- package/dist/public/bundle/.agents/skills/omg/remote-supervisor/SKILL.md +1 -1
- package/dist/public/bundle/.agents/skills/omg/remote-supervisor/openai.yaml +1 -1
- package/dist/public/bundle/.agents/skills/omg/robotics/SKILL.md +1 -1
- package/dist/public/bundle/.agents/skills/omg/robotics/openai.yaml +1 -1
- package/dist/public/bundle/.agents/skills/omg/secure-worktree-pipeline/SKILL.md +1 -1
- package/dist/public/bundle/.agents/skills/omg/secure-worktree-pipeline/openai.yaml +1 -1
- package/dist/public/bundle/.agents/skills/omg/security-check/SKILL.md +1 -1
- package/dist/public/bundle/.agents/skills/omg/security-check/openai.yaml +1 -1
- package/dist/public/bundle/.agents/skills/omg/test-intent-lock/SKILL.md +1 -1
- package/dist/public/bundle/.agents/skills/omg/test-intent-lock/openai.yaml +1 -1
- package/dist/public/bundle/.agents/skills/omg/tracebank/SKILL.md +1 -1
- package/dist/public/bundle/.agents/skills/omg/tracebank/openai.yaml +1 -1
- package/dist/public/bundle/.agents/skills/omg/vision/SKILL.md +1 -1
- package/dist/public/bundle/.agents/skills/omg/vision/openai.yaml +1 -1
- package/dist/public/bundle/.gemini/settings.json +11 -0
- package/dist/public/bundle/.kimi/mcp.json +11 -0
- package/dist/public/bundle/OMG_COMPAT_CONTRACT.md +9 -3
- package/dist/public/bundle/registry/omg-capability.schema.json +83 -1
- package/dist/public/bundle/settings.json +2 -1
- package/dist/public/manifest.json +43 -29
- package/docs/proof.md +1 -0
- package/hooks/setup_wizard.py +21 -1
- package/hooks/shadow_manager.py +25 -2
- package/hooks/state_migration.py +3 -0
- package/hud/omg-hud.mjs +66 -3
- package/package.json +1 -1
- package/plugins/advanced/plugin.json +1 -1
- package/plugins/core/plugin.json +1 -1
- package/plugins/dephealth/cve_scanner.py +91 -0
- package/plugins/dephealth/vuln_analyzer.py +7 -0
- package/pyproject.toml +1 -1
- package/registry/omg-capability.schema.json +83 -1
- package/runtime/adoption.py +13 -5
- package/runtime/artifact_parsers.py +161 -0
- package/runtime/background_verification.py +48 -0
- package/runtime/claim_judge.py +184 -7
- package/runtime/contract_compiler.py +118 -9
- package/runtime/evidence_query.py +203 -0
- package/runtime/omg_mcp_server.py +19 -0
- package/runtime/playwright_adapter.py +39 -0
- package/runtime/proof_chain.py +136 -8
- package/runtime/proof_gate.py +102 -0
- package/runtime/providers/gemini_provider.py +7 -0
- package/runtime/providers/kimi_provider.py +7 -0
- package/runtime/repro_pack.py +292 -0
- package/runtime/runtime_profile.py +87 -15
- package/runtime/security_check.py +86 -3
- package/runtime/test_intent_lock.py +47 -0
- package/runtime/tracebank.py +33 -3
- package/runtime/verification_loop.py +73 -0
- package/scripts/omg.py +30 -3
- package/settings.json +4 -3
- package/tools/python_sandbox.py +9 -6
- 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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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``)."""
|