@yottameta/yotta-dev-mcp-plugin 0.1.1 → 0.2.0
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/.agents/plugins/marketplace.json +3 -3
- package/.claude-plugin/marketplace.json +3 -3
- package/package.json +1 -1
- package/plugin.json +2 -2
- package/skills/yotta-dev-mcp/SKILL.md +19 -5
- package/skills/yotta-dev-mcp/references/adapters.md +69 -0
- package/skills/yotta-dev-mcp/references/architecture-contract.md +256 -0
- package/skills/yotta-dev-mcp/references/tools.md +147 -1
- package/skills/yotta-dev-mcp/scripts/dev_adapters.py +609 -0
- package/skills/yotta-dev-mcp/scripts/dev_architecture.py +379 -0
- package/skills/yotta-dev-mcp/scripts/dev_common.py +144 -0
- package/skills/yotta-dev-mcp/scripts/dev_contract.py +830 -0
- package/skills/yotta-dev-mcp/scripts/dev_engine.py +258 -134
- package/skills/yotta-dev-mcp/scripts/dev_impact.py +556 -0
- package/skills/yotta-dev-mcp/scripts/dev_model.py +435 -0
- package/skills/yotta-dev-mcp/scripts/dev_selftest.py +534 -0
- package/skills/yotta-dev-mcp/scripts/dev_verify.py +450 -0
- package/skills/yotta-dev-mcp/scripts/yotta_dev_mcp.py +234 -5
- package/skills/yotta-dev-mcp/server.json +3 -3
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""L0-L5 verification ledger for yotta-dev-mcp."""
|
|
4
|
+
|
|
5
|
+
import ast
|
|
6
|
+
import hashlib
|
|
7
|
+
import json
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import dev_contract
|
|
11
|
+
from dev_common import EVIDENCE_LIMIT, JS_EXTS, VERIFY_EXEC_LEVELS, VERIFY_LEVELS
|
|
12
|
+
from dev_impact import impact_analysis
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _run_checks_lazy(kind, cwd, timeout, allow_execute):
|
|
16
|
+
from dev_engine import run_checks
|
|
17
|
+
return run_checks(kind, cwd, timeout=timeout, allow_execute=allow_execute)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _is_within(root, target):
|
|
21
|
+
try:
|
|
22
|
+
Path(target).resolve().relative_to(Path(root).resolve())
|
|
23
|
+
return True
|
|
24
|
+
except ValueError:
|
|
25
|
+
return False
|
|
26
|
+
|
|
27
|
+
def _ledger_entry(check_id, level, claim, status, severity="info", evidence=None,
|
|
28
|
+
next_step=None, command=None, confidence="high"):
|
|
29
|
+
return {
|
|
30
|
+
"id": check_id,
|
|
31
|
+
"level": level,
|
|
32
|
+
"claim": claim,
|
|
33
|
+
"status": status,
|
|
34
|
+
"severity": severity,
|
|
35
|
+
"check": "verify_change",
|
|
36
|
+
"confidence": confidence,
|
|
37
|
+
"evidence": sorted(
|
|
38
|
+
list(evidence or []),
|
|
39
|
+
key=lambda item: (item.get("path") or "", item.get("line") or 0,
|
|
40
|
+
item.get("detail") or ""),
|
|
41
|
+
),
|
|
42
|
+
"next_step": next_step,
|
|
43
|
+
"command": command,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
def _verify_contract_result(contract_result):
|
|
47
|
+
if not contract_result["present"]:
|
|
48
|
+
return "UNKNOWN", [{
|
|
49
|
+
"path": contract_result["path"],
|
|
50
|
+
"line": None,
|
|
51
|
+
"detail": "architecture contract is missing",
|
|
52
|
+
}], "add .yotta/architecture.json"
|
|
53
|
+
if not contract_result["ok"]:
|
|
54
|
+
evidence = []
|
|
55
|
+
for item in contract_result["findings"]:
|
|
56
|
+
if item["severity"] in dev_contract.BLOCKING_SEVERITIES:
|
|
57
|
+
evidence.append({
|
|
58
|
+
"path": item["path"],
|
|
59
|
+
"line": None,
|
|
60
|
+
"detail": "%s: %s" % (item["code"], item["message"]),
|
|
61
|
+
})
|
|
62
|
+
return "FAIL", evidence, "fix the contract findings and rerun verify_change"
|
|
63
|
+
return "PASS", [], None
|
|
64
|
+
|
|
65
|
+
def _verify_syntax(changed, root):
|
|
66
|
+
evidence = []
|
|
67
|
+
unknown = []
|
|
68
|
+
for item in changed:
|
|
69
|
+
rel = item["path"]
|
|
70
|
+
if item.get("change") == "deleted":
|
|
71
|
+
continue
|
|
72
|
+
target = root / rel
|
|
73
|
+
if not target.is_file():
|
|
74
|
+
continue
|
|
75
|
+
suffix = target.suffix.lower()
|
|
76
|
+
if suffix not in (".py", ".json") and suffix not in JS_EXTS:
|
|
77
|
+
continue
|
|
78
|
+
if suffix in JS_EXTS:
|
|
79
|
+
unknown.append({
|
|
80
|
+
"path": rel,
|
|
81
|
+
"line": None,
|
|
82
|
+
"detail": "no zero-dependency JavaScript parser is available at L0",
|
|
83
|
+
})
|
|
84
|
+
continue
|
|
85
|
+
try:
|
|
86
|
+
text = target.read_text(encoding="utf-8")
|
|
87
|
+
except (OSError, UnicodeError) as exc:
|
|
88
|
+
unknown.append({
|
|
89
|
+
"path": rel,
|
|
90
|
+
"line": None,
|
|
91
|
+
"detail": "file could not be read: %s" % exc,
|
|
92
|
+
})
|
|
93
|
+
continue
|
|
94
|
+
try:
|
|
95
|
+
if suffix == ".py":
|
|
96
|
+
ast.parse(text, filename=rel)
|
|
97
|
+
else:
|
|
98
|
+
json.loads(text)
|
|
99
|
+
except SyntaxError as exc:
|
|
100
|
+
evidence.append({
|
|
101
|
+
"path": rel,
|
|
102
|
+
"line": exc.lineno,
|
|
103
|
+
"detail": "syntax error: %s" % exc.msg,
|
|
104
|
+
})
|
|
105
|
+
except json.JSONDecodeError as exc:
|
|
106
|
+
evidence.append({
|
|
107
|
+
"path": rel,
|
|
108
|
+
"line": exc.lineno,
|
|
109
|
+
"detail": "JSON error: %s" % exc.msg,
|
|
110
|
+
})
|
|
111
|
+
if evidence:
|
|
112
|
+
return "FAIL", evidence, "fix the syntax error(s) and rerun verify_change", []
|
|
113
|
+
if unknown:
|
|
114
|
+
return "UNKNOWN", unknown, "use an L2-L4 adapter or a language-specific parser", unknown
|
|
115
|
+
return "PASS", [], None, []
|
|
116
|
+
|
|
117
|
+
def _verify_architecture(impact):
|
|
118
|
+
architecture = impact["architecture"]
|
|
119
|
+
blocking = [
|
|
120
|
+
item for item in architecture["violations_in_scope"]
|
|
121
|
+
if item["severity"] in dev_contract.BLOCKING_SEVERITIES
|
|
122
|
+
]
|
|
123
|
+
if blocking:
|
|
124
|
+
evidence = []
|
|
125
|
+
for item in blocking:
|
|
126
|
+
for entry in item["evidence"]:
|
|
127
|
+
evidence.append({
|
|
128
|
+
"path": entry["path"],
|
|
129
|
+
"line": entry.get("line"),
|
|
130
|
+
"detail": "%s (%s): %s" % (
|
|
131
|
+
item["rule"], item["code"], entry.get("detail") or "",
|
|
132
|
+
),
|
|
133
|
+
})
|
|
134
|
+
return "FAIL", evidence, "fix the in-scope architecture violation(s)"
|
|
135
|
+
scoped = {item["path"] for item in impact["cone"]["nodes"]}
|
|
136
|
+
always_relevant = {
|
|
137
|
+
"contract-missing", "contract-invalid", "model-truncated", "cone-truncated",
|
|
138
|
+
"change-not-found", "symbol-not-found",
|
|
139
|
+
}
|
|
140
|
+
unknowns = []
|
|
141
|
+
for item in list(architecture.get("unknowns") or []) + list(impact.get("unknowns") or []):
|
|
142
|
+
kind = item.get("kind")
|
|
143
|
+
identifier = str(item.get("id") or "")
|
|
144
|
+
if kind in always_relevant or not identifier:
|
|
145
|
+
unknowns.append(item)
|
|
146
|
+
elif identifier in scoped:
|
|
147
|
+
unknowns.append(item)
|
|
148
|
+
elif any(path and path in identifier for path in scoped):
|
|
149
|
+
unknowns.append(item)
|
|
150
|
+
if unknowns:
|
|
151
|
+
evidence = [{
|
|
152
|
+
"path": item.get("id") or item.get("path") or "",
|
|
153
|
+
"line": None,
|
|
154
|
+
"detail": "%s: %s" % (item.get("kind"), item.get("detail") or ""),
|
|
155
|
+
} for item in unknowns]
|
|
156
|
+
return "UNKNOWN", evidence, "resolve the unknown evidence and rerun verify_change"
|
|
157
|
+
return "PASS", [], None
|
|
158
|
+
|
|
159
|
+
def _verify_policy_entries(root, policy, execution_levels, allow_execute, timeout):
|
|
160
|
+
ledger = []
|
|
161
|
+
unverified = []
|
|
162
|
+
for level in execution_levels:
|
|
163
|
+
checks = [item for item in policy["checks"] if item["level"] == level]
|
|
164
|
+
if not checks:
|
|
165
|
+
ledger.append(_ledger_entry(
|
|
166
|
+
"%s-policy" % level, level,
|
|
167
|
+
"%s verification is declared" % level,
|
|
168
|
+
"UNKNOWN", severity="high",
|
|
169
|
+
evidence=[{"path": policy["path"], "line": None,
|
|
170
|
+
"detail": "no %s checks are declared" % level}],
|
|
171
|
+
next_step="declare a whitelisted %s check in .yotta/verification.json" % level,
|
|
172
|
+
))
|
|
173
|
+
unverified.append({
|
|
174
|
+
"level": level,
|
|
175
|
+
"claim": "%s verification is declared and executed" % level,
|
|
176
|
+
"status": "UNVERIFIED",
|
|
177
|
+
"reason": "verification-level-not-declared",
|
|
178
|
+
"required": True,
|
|
179
|
+
"next_step": "declare a whitelisted check in .yotta/verification.json",
|
|
180
|
+
})
|
|
181
|
+
continue
|
|
182
|
+
for check in checks:
|
|
183
|
+
check_id = "%s-%s" % (level, check["id"])
|
|
184
|
+
claim = check["claim"] or "%s check %s passes" % (level, check["id"])
|
|
185
|
+
if not allow_execute:
|
|
186
|
+
ledger.append(_ledger_entry(
|
|
187
|
+
check_id, level, claim, "UNVERIFIED",
|
|
188
|
+
severity="high" if check["required"] else "medium",
|
|
189
|
+
next_step="rerun with allow_execute=true",
|
|
190
|
+
))
|
|
191
|
+
unverified.append({
|
|
192
|
+
"level": level,
|
|
193
|
+
"claim": claim,
|
|
194
|
+
"status": "UNVERIFIED",
|
|
195
|
+
"reason": "allow_execute=false",
|
|
196
|
+
"required": check["required"],
|
|
197
|
+
"next_step": "rerun with allow_execute=true",
|
|
198
|
+
})
|
|
199
|
+
continue
|
|
200
|
+
cwd = (root / check["cwd"]).resolve()
|
|
201
|
+
if not _is_within(root, cwd) or not cwd.is_dir():
|
|
202
|
+
ledger.append(_ledger_entry(
|
|
203
|
+
check_id, level, claim, "FAIL", severity="high",
|
|
204
|
+
evidence=[{"path": check["cwd"], "line": None,
|
|
205
|
+
"detail": "check cwd is not a directory inside the repository"}],
|
|
206
|
+
next_step="fix the policy cwd and rerun verify_change",
|
|
207
|
+
))
|
|
208
|
+
continue
|
|
209
|
+
try:
|
|
210
|
+
result = _run_checks_lazy(
|
|
211
|
+
check["kind"], str(cwd),
|
|
212
|
+
timeout=min(timeout, check["timeout"]),
|
|
213
|
+
allow_execute=True,
|
|
214
|
+
)
|
|
215
|
+
except Exception as exc: # noqa: BLE001
|
|
216
|
+
ledger.append(_ledger_entry(
|
|
217
|
+
check_id, level, claim, "FAIL", severity="high",
|
|
218
|
+
evidence=[{"path": check["cwd"], "line": None,
|
|
219
|
+
"detail": "check could not run: %s" % exc}],
|
|
220
|
+
next_step="fix the verification policy or local toolchain",
|
|
221
|
+
))
|
|
222
|
+
continue
|
|
223
|
+
output = result.get("output") or ""
|
|
224
|
+
command = {
|
|
225
|
+
"kind": result["kind"],
|
|
226
|
+
"cwd": check["cwd"],
|
|
227
|
+
"exit_code": result["exit_code"],
|
|
228
|
+
"output_hash": hashlib.sha256(output.encode("utf-8")).hexdigest(),
|
|
229
|
+
"summary": result.get("summary") or "",
|
|
230
|
+
"timed_out": bool(result.get("timed_out")),
|
|
231
|
+
}
|
|
232
|
+
status = "PASS" if result.get("passed") else "FAIL"
|
|
233
|
+
ledger.append(_ledger_entry(
|
|
234
|
+
check_id, level, claim, status,
|
|
235
|
+
severity="high" if check["required"] else "medium",
|
|
236
|
+
evidence=[{"path": check["cwd"], "line": None,
|
|
237
|
+
"detail": result.get("summary") or "check finished"}],
|
|
238
|
+
next_step=None if status == "PASS"
|
|
239
|
+
else "inspect the check output and fix the failure",
|
|
240
|
+
command=command,
|
|
241
|
+
))
|
|
242
|
+
return sorted(ledger, key=lambda item: (item["level"], item["id"])), unverified
|
|
243
|
+
|
|
244
|
+
def verify_change(path, changed_files=None, diff=None, symbols=None, depth=3,
|
|
245
|
+
levels=None, allow_execute=False, timeout=120,
|
|
246
|
+
max_files=2000, contract_file=None, policy_file=None):
|
|
247
|
+
"""Run the L0-L5 verification ladder and return a deterministic evidence ledger.
|
|
248
|
+
|
|
249
|
+
L0/L1 always run in-process. L2-L4 run only when both a whitelisted
|
|
250
|
+
.yotta/verification.json check is declared and allow_execute is true.
|
|
251
|
+
L5 is always recorded as manual work and is never auto-verified.
|
|
252
|
+
"""
|
|
253
|
+
root = Path(path)
|
|
254
|
+
if not root.exists():
|
|
255
|
+
raise ValueError("路径不存在: %s" % path)
|
|
256
|
+
if not root.is_dir():
|
|
257
|
+
raise ValueError("verify_change 需要目录: %s" % path)
|
|
258
|
+
if isinstance(timeout, bool) or not isinstance(timeout, int) or not 1 <= timeout <= 600:
|
|
259
|
+
raise ValueError("timeout 必须是 1 到 600 之间的整数")
|
|
260
|
+
requested = list(levels or [])
|
|
261
|
+
for level in requested:
|
|
262
|
+
if level not in VERIFY_LEVELS:
|
|
263
|
+
raise ValueError("level 必须是 L0-L5 之一: %s" % level)
|
|
264
|
+
execution_levels = [level for level in VERIFY_EXEC_LEVELS if level in requested]
|
|
265
|
+
|
|
266
|
+
impact = impact_analysis(
|
|
267
|
+
str(root), changed_files=changed_files, diff=diff, symbols=symbols,
|
|
268
|
+
depth=depth, max_files=max_files, contract_file=contract_file,
|
|
269
|
+
)
|
|
270
|
+
contract_result = dev_contract.load_contract(root, contract_file=contract_file)
|
|
271
|
+
policy = dev_contract.load_verification_policy(root, policy_file=policy_file)
|
|
272
|
+
|
|
273
|
+
ledger = []
|
|
274
|
+
unverified = []
|
|
275
|
+
|
|
276
|
+
contract_status, contract_evidence, contract_next = _verify_contract_result(contract_result)
|
|
277
|
+
ledger.append(_ledger_entry(
|
|
278
|
+
"L0-contract", "L0", "the architecture contract is valid",
|
|
279
|
+
contract_status, severity="high",
|
|
280
|
+
evidence=contract_evidence, next_step=contract_next,
|
|
281
|
+
))
|
|
282
|
+
|
|
283
|
+
syntax_status, syntax_evidence, syntax_next, syntax_unknown = _verify_syntax(
|
|
284
|
+
impact["changed"], root
|
|
285
|
+
)
|
|
286
|
+
ledger.append(_ledger_entry(
|
|
287
|
+
"L0-syntax", "L0", "changed source files parse",
|
|
288
|
+
syntax_status, severity="high",
|
|
289
|
+
evidence=syntax_evidence, next_step=syntax_next,
|
|
290
|
+
))
|
|
291
|
+
if syntax_unknown:
|
|
292
|
+
unverified.append({
|
|
293
|
+
"level": "L0",
|
|
294
|
+
"claim": "changed JavaScript or TypeScript files parse",
|
|
295
|
+
"status": "UNVERIFIED",
|
|
296
|
+
"reason": "no zero-dependency JavaScript parser",
|
|
297
|
+
"required": False,
|
|
298
|
+
"next_step": "add a language adapter or run an explicit parser check",
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
architecture_status, architecture_evidence, architecture_next = _verify_architecture(impact)
|
|
302
|
+
advisory = [
|
|
303
|
+
item for item in impact["architecture"]["violations_in_scope"]
|
|
304
|
+
if item["severity"] not in dev_contract.BLOCKING_SEVERITIES
|
|
305
|
+
]
|
|
306
|
+
if architecture_status == "PASS" and advisory:
|
|
307
|
+
architecture_evidence = [{
|
|
308
|
+
"path": entry["path"],
|
|
309
|
+
"line": entry.get("line"),
|
|
310
|
+
"detail": "%s (%s): %s" % (
|
|
311
|
+
item["rule"], item["code"], entry.get("detail") or "",
|
|
312
|
+
),
|
|
313
|
+
} for item in advisory for entry in item["evidence"]]
|
|
314
|
+
ledger.append(_ledger_entry(
|
|
315
|
+
"L1-architecture", "L1", "in-scope architecture rules and boundaries hold",
|
|
316
|
+
architecture_status, severity="high",
|
|
317
|
+
evidence=architecture_evidence, next_step=architecture_next,
|
|
318
|
+
))
|
|
319
|
+
|
|
320
|
+
for invariant in impact["affected_invariants"]:
|
|
321
|
+
unverified.append({
|
|
322
|
+
"level": "L1",
|
|
323
|
+
"claim": invariant["claim"],
|
|
324
|
+
"status": "UNVERIFIED",
|
|
325
|
+
"reason": "invariant requires a static, command or manual check",
|
|
326
|
+
"required": False,
|
|
327
|
+
"next_step": "add a verification policy check or complete the manual review",
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
if execution_levels:
|
|
331
|
+
if not policy["present"]:
|
|
332
|
+
for level in execution_levels:
|
|
333
|
+
ledger.append(_ledger_entry(
|
|
334
|
+
"%s-policy" % level, level,
|
|
335
|
+
"%s verification policy is present" % level,
|
|
336
|
+
"UNKNOWN", severity="high",
|
|
337
|
+
evidence=[{"path": policy["path"], "line": None,
|
|
338
|
+
"detail": "verification policy is missing"}],
|
|
339
|
+
next_step="add .yotta/verification.json with whitelisted checks",
|
|
340
|
+
))
|
|
341
|
+
unverified.append({
|
|
342
|
+
"level": level,
|
|
343
|
+
"claim": "%s verification is declared and executed" % level,
|
|
344
|
+
"status": "UNVERIFIED",
|
|
345
|
+
"reason": "verification-policy-missing",
|
|
346
|
+
"required": True,
|
|
347
|
+
"next_step": "add .yotta/verification.json",
|
|
348
|
+
})
|
|
349
|
+
elif not policy["ok"]:
|
|
350
|
+
evidence = [{
|
|
351
|
+
"path": item["path"],
|
|
352
|
+
"line": None,
|
|
353
|
+
"detail": "%s: %s" % (item["code"], item["message"]),
|
|
354
|
+
} for item in policy["findings"]
|
|
355
|
+
if item["severity"] in dev_contract.BLOCKING_SEVERITIES]
|
|
356
|
+
for level in execution_levels:
|
|
357
|
+
ledger.append(_ledger_entry(
|
|
358
|
+
"%s-policy" % level, level,
|
|
359
|
+
"%s verification policy is valid" % level,
|
|
360
|
+
"FAIL", severity="high", evidence=evidence,
|
|
361
|
+
next_step="fix .yotta/verification.json and rerun verify_change",
|
|
362
|
+
))
|
|
363
|
+
else:
|
|
364
|
+
execution_ledger, execution_unverified = _verify_policy_entries(
|
|
365
|
+
root, policy, execution_levels, allow_execute, timeout,
|
|
366
|
+
)
|
|
367
|
+
ledger.extend(execution_ledger)
|
|
368
|
+
unverified.extend(execution_unverified)
|
|
369
|
+
|
|
370
|
+
verified_levels = {
|
|
371
|
+
item["level"] for item in ledger
|
|
372
|
+
if item["status"] in ("PASS", "FAIL")
|
|
373
|
+
}
|
|
374
|
+
for level in VERIFY_EXEC_LEVELS:
|
|
375
|
+
if level not in verified_levels and not any(
|
|
376
|
+
item["level"] == level for item in unverified
|
|
377
|
+
):
|
|
378
|
+
unverified.append({
|
|
379
|
+
"level": level,
|
|
380
|
+
"claim": "%s verification level is covered by evidence" % level,
|
|
381
|
+
"status": "UNVERIFIED",
|
|
382
|
+
"reason": "level-not-requested",
|
|
383
|
+
"required": False,
|
|
384
|
+
"next_step": "request this level and provide a verification policy",
|
|
385
|
+
})
|
|
386
|
+
if "L5" not in requested:
|
|
387
|
+
unverified.append({
|
|
388
|
+
"level": "L5",
|
|
389
|
+
"claim": "independent review or manual architecture approval",
|
|
390
|
+
"status": "UNVERIFIED",
|
|
391
|
+
"reason": "manual verification required",
|
|
392
|
+
"required": False,
|
|
393
|
+
"next_step": "complete an independent review or manual architecture decision",
|
|
394
|
+
})
|
|
395
|
+
else:
|
|
396
|
+
unverified.append({
|
|
397
|
+
"level": "L5",
|
|
398
|
+
"claim": "independent review or manual architecture approval",
|
|
399
|
+
"status": "UNVERIFIED",
|
|
400
|
+
"reason": "manual verification required",
|
|
401
|
+
"required": True,
|
|
402
|
+
"next_step": "complete an independent review or manual architecture decision",
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
unverified.sort(key=lambda item: (item["level"], item["claim"], item["reason"]))
|
|
406
|
+
ledger.sort(key=lambda item: (item["level"], item["id"]))
|
|
407
|
+
failed = any(item["status"] == "FAIL" for item in ledger)
|
|
408
|
+
unknown = any(item["status"] == "UNKNOWN" for item in ledger) or any(
|
|
409
|
+
item["required"] for item in unverified
|
|
410
|
+
)
|
|
411
|
+
if failed:
|
|
412
|
+
status = "FAIL"
|
|
413
|
+
elif unknown:
|
|
414
|
+
status = "UNKNOWN"
|
|
415
|
+
else:
|
|
416
|
+
status = "PASS"
|
|
417
|
+
required_levels = ["L0", "L1"]
|
|
418
|
+
for item in ledger:
|
|
419
|
+
if item["level"] in VERIFY_EXEC_LEVELS and item["status"] in ("PASS", "FAIL"):
|
|
420
|
+
required_levels.append(item["level"])
|
|
421
|
+
required_levels = sorted(set(required_levels), key=lambda item: int(item[1:]))
|
|
422
|
+
evidence = []
|
|
423
|
+
for item in ledger:
|
|
424
|
+
evidence.extend(item["evidence"])
|
|
425
|
+
if len(evidence) > EVIDENCE_LIMIT:
|
|
426
|
+
evidence = evidence[:EVIDENCE_LIMIT]
|
|
427
|
+
ledger_digest = hashlib.sha256(
|
|
428
|
+
json.dumps(ledger, ensure_ascii=False, sort_keys=True,
|
|
429
|
+
separators=(",", ":")).encode("utf-8")
|
|
430
|
+
).hexdigest()
|
|
431
|
+
return {
|
|
432
|
+
"status": status,
|
|
433
|
+
"root": str(root.resolve()),
|
|
434
|
+
"inputs": {
|
|
435
|
+
"changed_files": list(changed_files or []),
|
|
436
|
+
"symbols": list(symbols or []),
|
|
437
|
+
"diff_provided": bool(str(diff or "").strip()),
|
|
438
|
+
"levels": requested,
|
|
439
|
+
"allow_execute": bool(allow_execute),
|
|
440
|
+
"depth": depth,
|
|
441
|
+
},
|
|
442
|
+
"required_levels": required_levels,
|
|
443
|
+
"ledger": ledger,
|
|
444
|
+
"unverified_claims": unverified,
|
|
445
|
+
"policy": policy,
|
|
446
|
+
"evidence": evidence,
|
|
447
|
+
"ledger_digest": ledger_digest,
|
|
448
|
+
"model_digest": impact["model_digest"],
|
|
449
|
+
"impact_status": impact["status"],
|
|
450
|
+
}
|