Files
Artemis-Eapil/artemis/data_engine/evidence_sources.py
T
chenmingxuan fac4ccb760 feat: prepare 2.2.0 with UI and execution fixes
Add dark theme, richer Markdown and readable plugin results.
Improve Android input evidence, response integrity and diagnostics.
Unify lifecycle, guidance receipts and evidence sources across clients.
Keep direct action dispatch without an added AI quota approval layer.
Align version metadata at 2.2.0 and bundle Helper 1.2.2.

Validation: 3063 backend tests before the version bump; 44 after.
Frontend: 317 tests and production build passed.
2026-10-26 00:44:13 +08:00

77 lines
3.3 KiB
Python

"""Describe recorded provenance without turning a source into a verified assertion."""
import hashlib
from uuid import UUID
import json
SOURCE_KINDS = {"android", "computer", "terminal", "api", "file", "other"}
def _step_reference(step: dict) -> dict:
value = step.get("step_id")
return {
"step_id": str(value) if isinstance(value, (str, UUID)) else None,
"step_number": step.get("step_number"),
}
def recorded_sources(steps: list[dict], *, limit: int = 100) -> dict:
sources = {}
unattributed = []
for step in steps:
result = step.get("last_execution_result")
execution = result.get("execution") if isinstance(result, dict) else None
records = execution if isinstance(execution, list) else step.get("action_taken")
records = records if isinstance(records, list) else [records]
found = False
missing = False
for record in records:
if not isinstance(record, dict) or not isinstance(record.get("source"), dict):
missing = True
continue
source = record["source"]
raw_kind = source.get("kind")
kind = raw_kind if isinstance(raw_kind, str) and raw_kind in SOURCE_KINDS else "other"
target = source.get("target_id")
if not isinstance(target, str) or not target:
missing = True
continue
found = True
plugin = str(record.get("plugin_id") or "")[:100]
key = (plugin, kind, target[:256])
if key not in sources:
sources[key] = {
"id": hashlib.sha256(json.dumps(key).encode()).hexdigest()[:20],
"kind": kind,
"plugin_id": plugin,
"target_id": target[:256],
"label": str(source.get("label") or "")[:500],
"category": "interface" if kind in {"android", "computer"} else "auxiliary",
"references": [],
"reference_count": 0,
}
reference = _step_reference(step)
for field in ("call_id", "source_id"):
if isinstance(record.get(field), str) and record[field]:
reference[field] = record[field][:256]
if isinstance(record.get("read_only"), bool):
reference["read_only"] = record["read_only"]
entry = sources[key]
entry["reference_count"] += 1
if len(entry["references"]) < 8:
entry["references"].append(reference)
if not found or missing:
# A screenshot or a model-authored URL does not supply missing provenance.
unattributed.append(_step_reference(step))
values = list(sources.values())
return {
"schema_version": 1,
"sources": values[:limit],
"total_sources": len(values),
"sources_omitted": max(0, len(values) - limit),
"unattributed_step_count": len(unattributed),
"unattributed_references": unattributed[:8],
"note": "来源清单仅来自执行记录,不代表断言已验证。界面证据与接口、终端、文件来源分开;界面结果不能单独证明后端持久状态。逐项结论及未验证边界以正式验收依据为准。缺失来源不按网址或业务名称推断执行平台。",
}