"""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": "来源清单仅来自执行记录,不代表断言已验证。界面证据与接口、终端、文件来源分开;界面结果不能单独证明后端持久状态。逐项结论及未验证边界以正式验收依据为准。缺失来源不按网址或业务名称推断执行平台。", }