108 lines
3.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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())