实现侧: - 上下文:任务范围拆分为 范围校验/范围授权;索引按可发现口径重建、索引新鲜度改对称差;依赖校验统一快照漂移说明。 - 知识方法:方法与材料读取口径统一;超限方法材料按可选省略,核对路径不再二次计费;删除无合同的读时重算。 - 任务运行:新增 context.usage/tool.denied 事件类型;连接池常驻并在装配生命周期内开关;调用结算与核对分列。 - 效果评测/审校修订/交付连载/作者经验/作品规划:凭据冻结、标定消费、导出补证、事实引文核对等收尾修复。 - 资源加载:能力正文不再夹带索引用的导航注记(该注记此前进入角色与技能的模型提示)。 - 元数据:受保护骨架与代码保护属性对齐;字段校验与内置结构口径同步。 - 基础设施:环境预检进入装配生命周期;数据库连接运行期字段不参与相等比较;索引指纹归一化 jsonb 浮点。 - 删除被替代实现:7 份旧提示词模板与空壳 资料来源 读取器。 用例侧: - 用例身份与导航元信息迁移;夹具补生命周期、同库暴露与模板封存; - 本轮定向修复:方法材料省略、事实引文、迁移回执、额度与暂停用例、慢用例超时预算等。
445 lines
20 KiB
Python
445 lines
20 KiB
Python
"""合成文件的边界、字节保全和无损正文转换;不访问真实旧文件或数据库。"""
|
||
|
||
import hashlib
|
||
import json
|
||
import os
|
||
from dataclasses import asdict
|
||
from pathlib import Path
|
||
|
||
import pytest
|
||
from pydantic import TypeAdapter
|
||
|
||
from muse.正文写作.接口 import 可见文本, 正文草稿
|
||
from 建立映射 import 迁移错误
|
||
from 读取旧文件 import 核对文件保全, 解码文件, 读取文件集
|
||
from 转换正文 import 转换文件正文
|
||
|
||
|
||
def _文件(tmp_path: Path, 内容: bytes, *, 修订="1", 输出="保全"):
|
||
源根 = tmp_path / "源"
|
||
源根.mkdir(exist_ok=True)
|
||
(源根 / "章.txt").write_bytes(内容)
|
||
清单 = [{"relpath": "章.txt", "revision": 修订, "sha256": hashlib.sha256(内容).hexdigest()}]
|
||
目标 = tmp_path / 输出
|
||
快照 = 读取文件集(源根, 清单, 数据集="synthetic-files", 输出目录=目标)
|
||
return 快照[0], 目标, 清单
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w21-21e001",
|
||
environment="合成文件与明确私有目录;默认离线",
|
||
given="含BOM/混合换行/表情/组合字符/尾空白的明确文件清单与新私有根",
|
||
when="按文件保全/正文合同执行真实受控入口并回查结果",
|
||
then=["源字节、命名空间、0700/0600权限、完整回执与同内容只读重放"],
|
||
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
|
||
)
|
||
def test_文件字节私有保全与只读重放__21e001(tmp_path):
|
||
原文 = "\ufeff甲\r\n\r乙\n\n😀e\u0301\t末尾 \n".encode()
|
||
源, 目标, 清单 = _文件(tmp_path, 原文)
|
||
assert 解码文件(源) == 原文
|
||
assert (目标 / "原始文件/章.txt").read_bytes() == 原文
|
||
assert set(源.原行) == {"id", "revision", "relpath", "size", "sha256", "content_base64"}
|
||
assert "source_hash" in 源.导出() and 源.source.id == 源.原行["relpath"]
|
||
assert str(tmp_path) not in 源.source.记录键
|
||
for p in (目标, 目标 / "原始文件"):
|
||
assert p.stat().st_mode & 0o777 == 0o700
|
||
for p in (目标 / "快照.json", 目标 / "文件回执.json", 目标 / "原始文件/章.txt"):
|
||
assert p.stat().st_mode & 0o777 == 0o600
|
||
assert 读取文件集(tmp_path / "源", 清单, 数据集="synthetic-files", 输出目录=目标) == [源]
|
||
assert 核对文件保全(目标 / "文件回执.json", [源])["passed"] is True
|
||
assert (tmp_path / "源/章.txt").read_bytes() == 原文
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w21-21e002",
|
||
environment="合成文件与明确私有目录;默认离线",
|
||
given="含绝对地址、点段、空路径段或反斜线的清单项",
|
||
when="按文件保全/正文合同执行真实受控入口并回查结果",
|
||
then=["绝对、越界、空段与反斜线路径在读取前拒绝"],
|
||
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
|
||
)
|
||
@pytest.mark.parametrize("相对", ["../越界", "/绝对", "a/../章.txt", "a//b", "a/ /b", "a\\b"])
|
||
def test_清单越界路径在读取前拒绝__21e002(tmp_path, 相对):
|
||
源根 = tmp_path / "源"
|
||
源根.mkdir()
|
||
with pytest.raises(迁移错误, match="file_path_denied"):
|
||
读取文件集(
|
||
源根,
|
||
[{"relpath": 相对, "revision": "1", "sha256": "0" * 64}],
|
||
数据集="synthetic",
|
||
输出目录=tmp_path / "保全",
|
||
)
|
||
assert not (tmp_path / "保全").exists()
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w21-21e003",
|
||
environment="合成文件与明确私有目录;默认离线",
|
||
given="源叶文件或父目录为符号链接,或源为FIFO",
|
||
when="按文件保全/正文合同执行真实受控入口并回查结果",
|
||
then=["文件链接、父目录链接和FIFO均不读取,外部文件不变"],
|
||
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
|
||
)
|
||
@pytest.mark.parametrize("方式", ["文件链接", "父目录链接", "管道"])
|
||
def test_链接与非普通文件不越过目录描述符__21e003(tmp_path, 方式):
|
||
源根 = tmp_path / "源"
|
||
源根.mkdir()
|
||
外部 = tmp_path / "外部"
|
||
外部.mkdir()
|
||
(外部 / "输入").write_bytes(b"not-authorized")
|
||
相对 = "输入"
|
||
if 方式 == "文件链接":
|
||
(源根 / "输入").symlink_to(外部 / "输入")
|
||
elif 方式 == "父目录链接":
|
||
(源根 / "子").symlink_to(外部, target_is_directory=True)
|
||
相对 = "子/输入"
|
||
else:
|
||
os.mkfifo(源根 / "输入")
|
||
with pytest.raises(迁移错误):
|
||
读取文件集(
|
||
源根,
|
||
[
|
||
{
|
||
"relpath": 相对,
|
||
"revision": "1",
|
||
"sha256": hashlib.sha256(b"not-authorized").hexdigest(),
|
||
}
|
||
],
|
||
数据集="synthetic",
|
||
输出目录=tmp_path / "保全",
|
||
)
|
||
assert not (tmp_path / "保全").exists() and (外部 / "输入").read_bytes() == b"not-authorized"
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w21-21e004",
|
||
environment="合成文件与明确私有目录;默认离线",
|
||
given="输出根等于源根、在源根内或包含源根",
|
||
when="按文件保全/正文合同执行真实受控入口并回查结果",
|
||
then=["源根和保全根双向互含均拒绝,原文件不变"],
|
||
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
|
||
)
|
||
@pytest.mark.parametrize("目标", ["源", "源/保全", "."])
|
||
def test_源和保全根双向互含均拒绝__21e004(tmp_path, 目标):
|
||
源, _, 清单 = _文件(tmp_path, b"original")
|
||
with pytest.raises(迁移错误, match="output_is_input"):
|
||
读取文件集(tmp_path / "源", 清单, 数据集="synthetic-files", 输出目录=tmp_path / 目标)
|
||
assert (tmp_path / "源/章.txt").read_bytes() == 解码文件(源)
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w21-21e005",
|
||
environment="合成文件与明确私有目录;默认离线",
|
||
given="既有保全根缺最终回执、副本被改或对应不同源内容",
|
||
when="按文件保全/正文合同执行真实受控入口并回查结果",
|
||
then=["缺最终回执、篡改副本、不同输入均失败且不覆盖"],
|
||
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
|
||
)
|
||
def test_缺最终回执篡改副本和不同输入均不能覆盖__21e005(tmp_path):
|
||
源, 目标, 清单 = _文件(tmp_path, b"original")
|
||
(目标 / "文件回执.json").unlink()
|
||
assert 核对文件保全(目标 / "文件回执.json", [源])["passed"] is False
|
||
with pytest.raises(迁移错误):
|
||
读取文件集(tmp_path / "源", 清单, 数据集="synthetic-files", 输出目录=目标)
|
||
源, 目标, 清单 = _文件(tmp_path, b"original", 输出="完整保全")
|
||
(目标 / "原始文件/章.txt").write_bytes(b"tampered")
|
||
assert 核对文件保全(目标 / "文件回执.json", [源])["issues"] == [
|
||
{"reason_code": "file_copy_mismatch"}
|
||
]
|
||
with pytest.raises(迁移错误, match="file_copy_mismatch"):
|
||
读取文件集(tmp_path / "源", 清单, 数据集="synthetic-files", 输出目录=目标)
|
||
_, 目标, _ = _文件(tmp_path, b"original", 输出="不覆盖")
|
||
(tmp_path / "源/章.txt").write_bytes(b"changed")
|
||
清单[0]["sha256"] = hashlib.sha256(b"changed").hexdigest()
|
||
with pytest.raises(迁移错误, match="output_conflict"):
|
||
读取文件集(tmp_path / "源", 清单, 数据集="synthetic-files", 输出目录=目标)
|
||
assert (目标 / "原始文件/章.txt").read_bytes() == b"original"
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w21-21e006",
|
||
environment="合成文件与明确私有目录;默认离线",
|
||
given="源文件超过8MiB、清单超过500项或base64快照超过16MiB",
|
||
when="按文件保全/正文合同执行真实受控入口并回查结果",
|
||
then=["8MiB单文件、500项及16MiB总快照界限真实触发,不截断"],
|
||
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
|
||
)
|
||
@pytest.mark.parametrize("方式", ["单文件", "数量", "总字节"])
|
||
def test_文件界限真实触发不截断__21e006(tmp_path, 方式):
|
||
源根 = tmp_path / "源"
|
||
源根.mkdir()
|
||
清单 = [{"relpath": "a", "revision": "1", "sha256": "0" * 64}]
|
||
if 方式 == "单文件":
|
||
with (源根 / "a").open("wb") as f:
|
||
f.truncate(8 * 1024 * 1024 + 1)
|
||
elif 方式 == "数量":
|
||
清单 *= 501
|
||
else:
|
||
内容 = b"a" * (6 * 1024 * 1024)
|
||
清单 = []
|
||
for 名 in ("a", "b"):
|
||
(源根 / 名).write_bytes(内容)
|
||
清单.append(
|
||
{"relpath": 名, "revision": "1", "sha256": hashlib.sha256(内容).hexdigest()}
|
||
)
|
||
with pytest.raises(迁移错误):
|
||
读取文件集(源根, 清单, 数据集="synthetic", 输出目录=tmp_path / "保全")
|
||
assert not (tmp_path / "保全").exists()
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w21-21e007",
|
||
environment="合成文件与明确私有目录;默认离线",
|
||
given="空稿及各种换行/Unicode/空白文本,并含同对象另一个源版本",
|
||
when="按文件保全/正文合同执行真实受控入口并回查结果",
|
||
then=["纯文本含空稿、CR/LF/CRLF、BOM、表情、组合字符无损,跨版段ID稳定"],
|
||
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
|
||
)
|
||
@pytest.mark.parametrize("文本", ["", "\n", "\r\n", "\ufeff甲\r\n乙\r丙\n", "😀e\u0301\t空白 \n\n"])
|
||
def test_纯文本逐码点保留及跨版段ID稳定__21e007(tmp_path, 文本):
|
||
源, _, _ = _文件(tmp_path, 文本.encode())
|
||
结果 = 转换文件正文(源, {"format": "text_utf8"})
|
||
草稿 = TypeAdapter(正文草稿).validate_python(结果["document"])
|
||
assert 可见文本(草稿) == 文本 and len(草稿.paragraphs) == 1
|
||
assert 结果["visible_text_hash"] == hashlib.sha256(文本.encode()).hexdigest()
|
||
新源, _, _ = _文件(tmp_path, (文本 + "新").encode(), 修订="2", 输出="新版本")
|
||
新 = 转换文件正文(新源, {"format": "text_utf8"})
|
||
assert 新["paragraph_map"][0]["paragraph_id"] == 结果["paragraph_map"][0]["paragraph_id"]
|
||
assert 解码文件(源) == 文本.encode()
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w21-21e008",
|
||
environment="合成文件与明确私有目录;默认离线",
|
||
given="明确的旧场景/段ID和恰有单LF间隔的码点范围",
|
||
when="按文件保全/正文合同执行真实受控入口并回查结果",
|
||
then=["明确场景/段落来源对照,无损且不造正文额外场景字段"],
|
||
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
|
||
)
|
||
def test_显式场景段落只保来源对照且无损__21e008(tmp_path):
|
||
源, _, _ = _文件(tmp_path, "甲😀\n乙\n".encode())
|
||
结果 = 转换文件正文(
|
||
源,
|
||
{
|
||
"format": "text_utf8",
|
||
"paragraphs": [
|
||
{"id": "p1", "scene_id": "旧场景", "start": 0, "end": 2},
|
||
{"id": "p2", "scene_id": "旧场景", "start": 3, "end": 5},
|
||
],
|
||
},
|
||
)
|
||
assert 可见文本(TypeAdapter(正文草稿).validate_python(结果["document"])) == "甲😀\n乙\n"
|
||
assert {m["source_scene_id"] for m in 结果["paragraph_map"]} == {"旧场景"}
|
||
assert set(结果["document"]) == {"paragraphs", "format_version"}
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w21-21e009",
|
||
environment="合成文件与明确私有目录;默认离线",
|
||
given="漏字、跨CRLF原子边界或含未知字段的分段表",
|
||
when="按文件保全/正文合同执行真实受控入口并回查结果",
|
||
then=["范围遗漏、切开CRLF或未知分段字段拒绝,原字节保留"],
|
||
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
|
||
)
|
||
@pytest.mark.parametrize(
|
||
"段表",
|
||
[
|
||
[{"id": "p", "start": 1, "end": 4}],
|
||
[{"id": "p1", "start": 0, "end": 2}, {"id": "p2", "start": 3, "end": 4}],
|
||
[{"id": "p1", "start": 0, "end": 1}, {"id": "p2", "start": 3, "end": 4}],
|
||
[{"id": "p", "start": 0, "end": 4, "unknown": 1}],
|
||
],
|
||
)
|
||
def test_显式分段不切CRLF或漏字__21e009(tmp_path, 段表):
|
||
源, _, _ = _文件(tmp_path, "甲\r\n乙".encode())
|
||
with pytest.raises(迁移错误, match="file_paragraph_invalid"):
|
||
转换文件正文(源, {"format": "text_utf8", "paragraphs": 段表})
|
||
assert 解码文件(源) == "甲\r\n乙".encode()
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w21-21e00a",
|
||
environment="合成文件与明确私有目录;默认离线",
|
||
given="正文格式1 JSON含原段ID与可合并的相邻文字节点",
|
||
when="按文件保全/正文合同执行真实受控入口并回查结果",
|
||
then=["document_v1保原JSON字节和原段ID,结构节点规范化不冒充原文"],
|
||
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
|
||
)
|
||
def test_正文JSON保原字节与段ID且不把语法当正文__21e00a(tmp_path):
|
||
数据 = {
|
||
"format_version": 1,
|
||
"paragraphs": [
|
||
{
|
||
"paragraph_id": "原段",
|
||
"content": [{"type": "text", "text": "甲"}, {"type": "text", "text": "乙"}],
|
||
}
|
||
],
|
||
}
|
||
原 = json.dumps(数据, ensure_ascii=False, indent=2).encode()
|
||
源, _, _ = _文件(tmp_path, 原)
|
||
结果 = 转换文件正文(源, {"format": "document_v1"})
|
||
草稿 = TypeAdapter(正文草稿).validate_python(结果["document"])
|
||
assert 可见文本(草稿) == "甲乙" and 草稿.paragraphs[0].paragraph_id == "原段"
|
||
assert len(草稿.paragraphs[0].content) == 1 and 解码文件(源) == 原
|
||
assert 结果["paragraph_map"][0]["coordinate_space"] == "document_visible_text"
|
||
assert asdict(草稿) == 结果["document"]
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w21-21e00b",
|
||
environment="合成文件与明确私有目录;默认离线",
|
||
given="坏UTF8、重复JSON键、非法版本、未知字段或重复/空段ID",
|
||
when="按文件保全/正文合同执行真实受控入口并回查结果",
|
||
then=["坏编码/字段/版本/重复段ID/空段ID拒绝且不回显输入"],
|
||
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
|
||
)
|
||
@pytest.mark.parametrize(
|
||
"输入,格式",
|
||
[
|
||
(b"\xff", "text_utf8"),
|
||
(b'{"format_version":1,"private":"secret-body"}', "document_v1"),
|
||
(b'{"a":1,"a":2}', "document_v1"),
|
||
(b'{"format_version":2,"paragraphs":[]}', "document_v1"),
|
||
(
|
||
b'{"paragraphs":[{"paragraph_id":"p","content":[]},{"paragraph_id":"p","content":[]}]}',
|
||
"document_v1",
|
||
),
|
||
(b'{"paragraphs":[{"paragraph_id":" ","content":[]}]}', "document_v1"),
|
||
],
|
||
)
|
||
def test_坏正文保持字节且异常不回显输入__21e00b(tmp_path, 输入, 格式):
|
||
源, _, _ = _文件(tmp_path, 输入)
|
||
with pytest.raises(迁移错误) as 拒绝:
|
||
转换文件正文(源, {"format": 格式})
|
||
assert "secret-body" not in str(拒绝.value) and 解码文件(源) == 输入
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w21-21e00c",
|
||
environment="合成文件与明确私有目录;默认离线",
|
||
given="完整文件清单/保全根,通过真实CLI读取和核对",
|
||
when="按文件保全/正文合同执行真实受控入口并回查结果",
|
||
then=["读取文件和核对文件CLI真实往返,正文不进入标准输出"],
|
||
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
|
||
)
|
||
def test_文件命令真实往返且不回显正文__21e00c(tmp_path):
|
||
import subprocess
|
||
import sys
|
||
|
||
源, 保全, 清单 = _文件(tmp_path, b"private-synthetic-body")
|
||
清单文件 = tmp_path / "清单.json"
|
||
清单文件.write_text(json.dumps(清单), encoding="utf-8")
|
||
入口 = Path(__file__).resolve().parents[2] / "数据库/旧库迁移/入口.py"
|
||
for 命令 in [
|
||
[
|
||
"读取文件",
|
||
"--源根",
|
||
str(tmp_path / "源"),
|
||
"--清单",
|
||
str(清单文件),
|
||
"--数据集",
|
||
"synthetic-files",
|
||
"--输出目录",
|
||
str(保全),
|
||
],
|
||
[
|
||
"核对文件",
|
||
"--文件回执",
|
||
str(保全 / "文件回执.json"),
|
||
"--输出",
|
||
str(tmp_path / "文件报告.json"),
|
||
],
|
||
]:
|
||
进程 = subprocess.run(
|
||
[sys.executable, str(入口), *命令], text=True, capture_output=True, timeout=20
|
||
)
|
||
assert 进程.returncode == 0, 进程.stderr
|
||
assert "private-synthetic-body" not in 进程.stdout + 进程.stderr
|
||
assert json.loads((tmp_path / "文件报告.json").read_text())["passed"] is True
|
||
assert 解码文件(源) == b"private-synthetic-body"
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w21-21e00d",
|
||
environment="合成文件与明确私有目录;默认离线",
|
||
given="文件路由缺失、重复、源哈希错误或含非法用途/字段",
|
||
when="按文件保全/正文合同执行真实受控入口并回查结果",
|
||
then=["文件路由缺失、多义、漂移及未知用途/字段不猜补,原记录保留"],
|
||
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
|
||
)
|
||
@pytest.mark.parametrize(
|
||
"方式,原因",
|
||
[
|
||
("缺路由", "file_route_missing"),
|
||
("重复", "file_route_ambiguous"),
|
||
("漂移", "file_route_drift"),
|
||
("多余字段", "file_route_invalid"),
|
||
("未知用途", "file_route_invalid"),
|
||
],
|
||
)
|
||
def test_文件路由不猜用途且保原记录__21e00d(tmp_path, 方式, 原因):
|
||
from 建立映射 import 映射配置
|
||
from 知识记录分流 import 分流批次
|
||
|
||
源, _, _ = _文件(tmp_path, b"raw bytes")
|
||
r = {"source_key": 源.source.记录键, "source_hash": 源.源哈希, "route": "archive"}
|
||
路由 = [r]
|
||
if 方式 == "缺路由":
|
||
路由 = []
|
||
elif 方式 == "重复":
|
||
路由 = [r, r]
|
||
elif 方式 == "漂移":
|
||
r["source_hash"] = "0" * 64
|
||
elif 方式 == "多余字段":
|
||
r["format"] = "text_utf8"
|
||
else:
|
||
r["route"] = "guess"
|
||
判定 = 分流批次([源], 映射配置({"works": [], "file_routes": 路由}))[0]
|
||
assert 判定.disposition == "隔离" and 判定.reason_code == 原因
|
||
assert 判定.original == 源.导出()
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w21-21e00e",
|
||
environment="合成文件与明确私有目录;默认离线",
|
||
given="子目录描述符刚打开后原目录路径被替换成外部链接",
|
||
when="按文件保全/正文合同执行真实受控入口并回查结果",
|
||
then=["源子目录被替换成外部链接后,已固定目录描述符仍只读原授权字节"],
|
||
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
|
||
)
|
||
def test_源目录被换成链接后仍只读已授权目录描述符__21e00e(tmp_path, monkeypatch):
|
||
源根 = tmp_path / "源"
|
||
(源根 / "子").mkdir(parents=True)
|
||
(源根 / "子/输入").write_bytes(b"allowed")
|
||
外部 = tmp_path / "外部"
|
||
外部.mkdir()
|
||
(外部 / "输入").write_bytes(b"forbidden")
|
||
原打开 = os.open
|
||
已替换 = False
|
||
|
||
def 打开(路径, *args, **kwargs):
|
||
nonlocal 已替换
|
||
fd = 原打开(路径, *args, **kwargs)
|
||
if 路径 == "子" and not 已替换:
|
||
已替换 = True
|
||
(源根 / "子").rename(源根 / "旧目录")
|
||
(源根 / "子").symlink_to(外部, target_is_directory=True)
|
||
return fd
|
||
|
||
with monkeypatch.context() as m:
|
||
m.setattr(os, "open", 打开)
|
||
快照 = 读取文件集(
|
||
源根,
|
||
[
|
||
{
|
||
"relpath": "子/输入",
|
||
"revision": "1",
|
||
"sha256": hashlib.sha256(b"allowed").hexdigest(),
|
||
}
|
||
],
|
||
数据集="synthetic-race",
|
||
输出目录=tmp_path / "保全",
|
||
)
|
||
assert 已替换 and 解码文件(快照[0]) == b"allowed"
|
||
assert (外部 / "输入").read_bytes() == b"forbidden"
|