#!/usr/bin/env python3 """把回放运行结果压缩成不含正文和完整响应的 Markdown 摘要。""" from __future__ import annotations import argparse import json from pathlib import Path from typing import Any, Mapping REPORT_FORBIDDEN_KEYS = frozenset( { "raw", "rawText", "raw_output", "body", "content", "payload", "prompt", "response", "fullPrompt", "fullResponse", "正文", "原文", "正文全文", "完整目标细纲", } ) def _assert_no_forbidden_keys(value: Any, path: str = "result") -> None: """报告输入也不接受原始内容字段,避免误把运行中间件带进最终摘要。""" if isinstance(value, Mapping): for key, item in value.items(): if str(key) in REPORT_FORBIDDEN_KEYS: raise ValueError(f"{path}.{key} 不得进入最终报告") _assert_no_forbidden_keys(item, f"{path}.{key}") elif isinstance(value, list): for index, item in enumerate(value): _assert_no_forbidden_keys(item, f"{path}[{index}]") def _arm_line(name: str, arm: Mapping[str, Any], result: Mapping[str, Any]) -> str: outcome = result.get(name) or {} return ( f"- `{name}`: {outcome.get('status', 'not_run')}," f"候选哈希 `{outcome.get('candidateSha256') or outcome.get('rawOutputSha256') or 'n/a'}`," f"卡策略 `{arm.get('cardStrategy', 'unknown')}`,卡数 `{arm.get('cardInjectionCount', 0)}`" ) def render_report(result: Mapping[str, Any]) -> str: """只从运行结果的白名单字段渲染摘要。""" _assert_no_forbidden_keys(result) lines = [ "# 细纲回放摘要", "", f"- runId: `{result.get('runId', 'unknown')}`", f"- status: `{result.get('status', 'unknown')}`", f"- mode: `{result.get('mode', 'unknown')}`", f"- referenceWork: `{result.get('referenceWork', 'unknown')}@{result.get('referenceWorkVersion', 'unknown')}`", f"- freeze: 第 `{result.get('asOfChapter', 'unknown')}` 章,目标第 `{result.get('targetChapter', 'unknown')}` 章", f"- snapshot: `{result.get('snapshotVersion', 'unknown')}`", "", "## 前置门", "", f"- status: `{(result.get('preflight') or {}).get('status', 'unknown')}`", ] errors = (result.get("preflight") or {}).get("errors") or [] for error in errors: lines.append(f"- 阻断: {error}") lines.extend(["", "## 三臂", ""]) arms = result.get("arms") or {} outcomes = result.get("results") or {} for name in sorted(arms): lines.append(_arm_line(name, arms[name], outcomes)) if result.get("snapshotManifestSha256"): lines.extend(["", f"- snapshotManifestSha256: `{result['snapshotManifestSha256']}`"]) lines.extend( [ "", "> 本摘要不保存原书正文、目标章全文、完整 prompt/response 或供应商原始响应;原始候选仅存在临时运行目录。", "", ] ) return "\n".join(lines) def _parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description="写出细纲回放安全摘要") parser.add_argument("--input", type=Path, required=True) parser.add_argument("--output", type=Path, required=True) return parser.parse_args() def main() -> int: args = _parse_args() result = json.loads(args.input.read_text(encoding="utf-8")) args.output.write_text(render_report(result), encoding="utf-8") return 0 if __name__ == "__main__": raise SystemExit(main())