108 lines
3.6 KiB
Python
108 lines
3.6 KiB
Python
#!/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())
|