Files
AgentLab/backend/tests/test_bundle_rejected.py
ThearchyHelios e96b0180d9 可验证报告、报告导出写「被驳回」:元信息带 end_reason,核对仍按运行取消
为什么:被驳回的运行导出后,可验证报告的运行结果写「已取消」,报告导出的运行状态也写「已取消」,和系统里「被驳回」两种
说法。封存里被驳回和手动停止是同一种结束事件(运行取消),分开它们的原因在那条事件的内容里,而运行取消的内容不随文件
附带(里面有驳回人和备注)——光看封存分不出来。

做了什么:
- 两种导出的元信息都带 end_reason(bundle/build、report_export/build、对应的接口)。
- verifier.js:运行结果按运行记录写「被驳回」,核对的仍是「最后一条结束类事件是运行取消」,旁边写「已核对运行取消,被驳回
  取自运行记录」,原因里写明这一半要去系统里对照;手动停止、老运行照旧「已取消」;文件里写被驳回、事件却是运行结束的照旧
  不通过。runtime.js 的运行状态照 end_reason 写「被驳回」。
- 样本页加一份 rejected(check-bundle 样本数 17 → 18),断言写被驳回、写明取自运行记录、不提重新封存。

测试:test_bundle_rejected 用 node 载入 verifier.js 核运行信息(被驳回、手动停止、老运行、伪造);test_bundle_build 核
元信息和样本页;test_bundle_api 核两种导出接口都带 end_reason。把实现退回去回跑,被驳回、伪造两条和元信息、样本页、
接口的断言都失败(手动停止、老运行两条守的是原样,照旧通过);改完通过。
2026-10-16 12:46:48 +08:00

80 lines
4.0 KiB
Python
Raw Permalink 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.
"""可验证报告里被驳回的运行写「被驳回」,不写成手动停止的「已取消」(05-已知遗留 (a)、总方案 X9)。
人的驳回让运行结束在 cancelled + end_reason=rejected,手动停止也是 cancelled(end_reason=stopped)。封存里二者是同一种
结束事件(运行取消),分开它们的原因在那条事件的内容里,内容不随文件附带。所以:
- 运行结果照运行记录(元信息的 end_reason)写「被驳回」,核对的仍是「最后一条结束类事件是运行取消」这一半,旁边写明
被驳回取自运行记录,不把它说成已核对出来的;
- 手动停止、老运行(没有 end_reason)照旧「已取消」;
- 文件里写着被驳回、最后一条事件却是运行结束的,照旧不通过,点名文件里写的「被驳回」。
用 node 直接载入 verifier.js(同 test_bundle_shown.py),只看运行信息(⑥)这一块。数据全是虚构的。
"""
from __future__ import annotations
import copy
import json
import shutil
import subprocess
from pathlib import Path
from typing import Any
import pytest
import app.bundle
from tests.bundle_samples import RUN, Sample, WEEKLY, build, bundles
NODE = shutil.which("node")
VERIFIER = Path(app.bundle.__file__).parent / "verifier.js"
pytestmark = pytest.mark.skipif(NODE is None, reason="没有 node,无法执行页面上的核对脚本")
#: 载入 verifier.js(node 里没有 document,它把核对用的纯函数交给 module.exports),交回运行信息每一项
HARNESS = r"""
const fs = require('fs')
const { createHash } = require('crypto')
const input = JSON.parse(fs.readFileSync(0, 'utf8'))
const mod = { exports: {} }
new Function('module', fs.readFileSync(process.argv[1], 'utf8'))(mod)
const digest = async (bytes) => createHash('sha256').update(bytes).digest('hex')
mod.exports.verify(input.data, digest, input.data.meta.manifest_hash).then((out) => {
process.stdout.write(JSON.stringify(out.meta.map((m) => ({ field: m.field, shown: m.shown, claim: m.claim,
result: m.result, reason: m.reason || '', mark: m.mark ?? null }))))
}, (e) => { console.error((e && e.stack) || String(e)); process.exit(1) })
"""
def status_of(bundle: dict[str, Any]) -> dict[str, Any]:
done = subprocess.run([NODE, "-e", HARNESS, str(VERIFIER)], input=json.dumps({"data": bundle}),
capture_output=True, text=True, timeout=120)
assert done.returncode == 0, done.stderr[-2000:]
return next(m for m in json.loads(done.stdout) if m["field"] == "status")
def ended(end_reason: str | None) -> dict[str, Any]:
"""同一串事件(最后一条是运行取消),运行记录分别写被驳回、手动停止、没写(老运行)。"""
s = Sample()
events, bound = s.events({"write": s.doc(WEEKLY, "write")}, ending="rejected")
return build(s, events, bound, run={**RUN, "status": "cancelled", "end_reason": end_reason})
def test_a_rejected_run_reads_rejected_and_says_which_half_is_checked():
got = status_of(bundles()["rejected"])
assert got["result"] == "pass" and got["shown"] == "被驳回" and got["claim"] == "被驳回", got
assert "运行取消" in got["reason"] and "取自运行记录" in got["reason"], got["reason"]
# 旁边那一句不说成全核对过了
assert got["mark"] == "已核对运行取消,被驳回取自运行记录", got
@pytest.mark.parametrize("end_reason", ["stopped", None])
def test_a_stopped_or_old_cancelled_run_still_reads_cancelled(end_reason):
got = status_of(ended(end_reason))
assert (got["result"], got["shown"], got["claim"], got["mark"]) == ("pass", "已取消", "已取消", None), got
def test_a_file_claiming_rejected_over_a_finished_run_fails_and_names_the_claim():
forged = copy.deepcopy(bundles()["base"])
forged["meta"].update(status="cancelled", end_reason="rejected")
got = status_of(forged)
assert got["result"] == "fail" and got["shown"] == "已完成" and "被驳回" in got["reason"], got