zizi e36cd57010 框架: 评测合同与提示词去支架化 + skill 三层重组(Phase 0-4)
【评测合同与提示词】
- writer/detector/judge 评测提示词去支架化:删评测身份/输出格式叮嘱/盲化反向叮嘱/机器字段
  (改由运行时 --json-schema/--tools ""/--strict-mcp-config/输入设计强制),装回各角色本业纪律
- writer 加写作纪律:戏剧化节拍禁抄细纲概述句、具体压倒抽象、每场戏三件套、篇幅用场景写够不注水不缩水
- 篇幅自纠环修订指令区分偏短(加场景)/偏长(删冗余);机械门要求清单(章末钩子/硬事件/角色/伏笔)注入写手硬约束
- 引文校验从「必须恰好1次」放宽为「0次失败关闭、≥1次绑首次」(检测+盲评同口径)
- llm 立 chat_governed 为主入口(chat 降为仅调试),clean_detect 改用 chat_governed 弃自带降级链
- rewrite/expansion/polish 输出合同对齐 writer.md(返回文本、写手不读写工作区、主会话落工作区)
- 修复 confirm/replay-eval 探针两组坏自测(夹具适配现行合同、探针测试自包含不硬编码漂移哈希);补 clean_detect 离线测试
- AGENTS.md 新增 §10「Agent 提示词与 Skill 审查标准」

【skill 三层重组:单向依赖 底座→能力→编排,断两环+修生产倒挂】
- Phase 0: 新建 runtime 底座(claude_runtime/file_cas/raw_vault),断环 C1、修 continuation 生产倒挂
- Phase 1: 评分尺+门判(writer_rubric/fine_outline_rubric/writer_gate/gate_input_builder)收进 quality-gate,断环 C2、名实相符
- Phase 2: 升格管线从 parse-book 独立成 upgrade skill(备份审计契约键名稳定、仅改路径定位)
- Phase 3: replay-eval 瘦成纯编排
- read-context 共用簇(build_snapshot/audit_leakage/check_snapshot/load_reference_work)下沉到新 snapshot skill,消除能力层向上引用
- Phase 4: run_writer_replay.py(130KB)拆成包(_common/budget/authorization/sample/blind/execute),__init__ 全量 re-export 测试零改动

【base 配置】三角色 effort 提 high;提示词更新;探针重测绑定 writer 合同;预算 writer 45 次/总 450 美元(含篇幅修订)

全量离线测试 36 个文件全绿;函数逻辑零改动(仅搬位置/改 import/改文档,capture_code_identity 仅改路径定位)。
2026-07-26 03:28:22 +08:00

467 lines
18 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
"""离线评测敏感原文的短生命周期 raw vault。
raw 字节只能在非敏感 lease 已原子落盘后写入。所有路径都由管理器生成并通过
dir-fd、O_NOFOLLOW 和相对分量访问;日志与回执不包含 vault 绝对路径。
"""
from __future__ import annotations
import hashlib
import json
import os
import pathlib
import secrets
import stat
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from typing import Any, Mapping, Sequence
HASH_PATTERN = __import__("re").compile(r"^sha256:[0-9a-f]{64}$")
APPROVED_ROLES = frozenset({"用户", "创始人", "user", "founder"})
MAX_RETENTION = timedelta(hours=24)
class RawVaultError(RuntimeError):
"""携带稳定码且不暴露 raw 路径或原文的 vault 错误。"""
def __init__(self, code: str, message: str) -> None:
super().__init__(message)
self.code = code
self.acceptance_eligible = False
@dataclass(frozen=True)
class VaultLease:
"""调用方可持有的非敏感 vault lease 身份。"""
authorization_id: str
approved_by: str
run_id: str
vault_id: str
source_version: str
content_hashes: tuple[str, ...]
created_at: str
retain_until: str
purpose: str
def _utc_now() -> datetime:
"""返回带时区的 UTC 当前时间,便于测试和审计。"""
return datetime.now(timezone.utc)
def _canonical_json(value: Any) -> str:
"""生成非敏感 journal 的规范 JSON。"""
return json.dumps(
value,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
)
def _fsync_directory(path: pathlib.Path) -> None:
"""持久化目录项变更,避免 rename 仅停留在页缓存。"""
descriptor = os.open(path, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)
try:
os.fsync(descriptor)
finally:
os.close(descriptor)
def _atomic_write_json(path: pathlib.Path, value: Mapping[str, Any]) -> None:
"""以 0600 临时文件、fsync、rename 和目录 fsync 原子发布 JSON。"""
temporary = path.parent / f".{path.name}.{secrets.token_hex(8)}.tmp"
descriptor = os.open(
temporary,
os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW,
0o600,
)
try:
encoded = (_canonical_json(value) + "\n").encode("utf-8")
with os.fdopen(descriptor, "wb", closefd=True) as handle:
handle.write(encoded)
handle.flush()
os.fsync(handle.fileno())
os.replace(temporary, path)
os.chmod(path, 0o600, follow_symlinks=False)
_fsync_directory(path.parent)
except BaseException:
try:
temporary.unlink(missing_ok=True)
finally:
raise
def _parse_time(value: str, field: str) -> datetime:
"""解析必须带时区的 ISO-8601 时间。"""
if not isinstance(value, str) or not value:
raise RawVaultError("RAW_LEASE_INVALID", f"{field} 不能为空")
try:
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
except ValueError as exc:
raise RawVaultError("RAW_LEASE_INVALID", f"{field} 格式非法") from exc
if parsed.tzinfo is None:
raise RawVaultError("RAW_LEASE_INVALID", f"{field} 必须带时区")
return parsed.astimezone(timezone.utc)
class RawVaultManager:
"""管理一个 journal 命名空间内的不可猜测 raw vault。"""
def __init__(
self,
journal_root: str | pathlib.Path,
*,
vault_root: str | pathlib.Path = "/private/tmp",
) -> None:
"""创建受限 journal,并拒绝 journal/vault 根软链接。"""
self.journal_root = pathlib.Path(journal_root).absolute()
self.vault_root = pathlib.Path(vault_root).absolute()
for path, create in ((self.vault_root, False), (self.journal_root, True)):
if path.is_symlink():
raise RawVaultError("RAW_PATH_INVALID", "vault 管理根不能是软链接")
if create:
path.mkdir(parents=True, exist_ok=True, mode=0o700)
if not path.is_dir():
raise RawVaultError("RAW_PATH_INVALID", "vault 管理根必须是目录")
# `/private/tmp` 是系统共享根,绝不能改权限;只有本管理器创建的 journal 固定 0700。
if create:
os.chmod(path, 0o700)
self.leases_root = self.journal_root / "leases"
if self.leases_root.is_symlink():
raise RawVaultError("RAW_PATH_INVALID", "lease 目录不能是软链接")
self.leases_root.mkdir(exist_ok=True, mode=0o700)
os.chmod(self.leases_root, 0o700)
namespace = hashlib.sha256(str(self.journal_root).encode("utf-8")).hexdigest()[:16]
self.vault_prefix = f"muse-raw-vault-{namespace}-"
def _lease_path(self, vault_id: str) -> pathlib.Path:
"""把受控 vault ID 映射到非敏感 lease 文件。"""
if not isinstance(vault_id, str) or not __import__("re").fullmatch(r"[0-9a-f]{32}", vault_id):
raise RawVaultError("RAW_LEASE_INVALID", "vault ID 非法")
return self.leases_root / f"{vault_id}.json"
def _vault_name(self, vault_id: str) -> str:
"""生成同一管理器命名空间内的 vault 目录名。"""
self._lease_path(vault_id)
return f"{self.vault_prefix}{vault_id}"
def _vault_path(self, vault_id: str) -> pathlib.Path:
"""仅供内部文件操作生成 vault 路径,调用结果绝不写入回执。"""
return self.vault_root / self._vault_name(vault_id)
def create_vault(
self,
*,
authorization_id: str,
approved_by: str,
run_id: str,
source_version: str,
content_hashes: Sequence[str],
purpose: str,
retain_until: str,
) -> VaultLease:
"""先持久化 lease,再创建 0700 不可猜测 vault。"""
text_fields = {
"authorizationId": authorization_id,
"runId": run_id,
"sourceVersion": source_version,
"purpose": purpose,
}
if any(not isinstance(value, str) or not value.strip() for value in text_fields.values()):
raise RawVaultError("RAW_LEASE_INVALID", "lease 身份字段不能为空")
if approved_by not in APPROVED_ROLES:
raise RawVaultError("RAW_LEASE_INVALID", "lease 审批角色不受信任")
hashes = tuple(content_hashes)
if not hashes or any(not isinstance(value, str) or not HASH_PATTERN.fullmatch(value) for value in hashes):
raise RawVaultError("RAW_LEASE_INVALID", "contentHashes 必须是非空 SHA-256 集合")
now = _utc_now()
retention = _parse_time(retain_until, "retainUntil")
if retention <= now or retention - now > MAX_RETENTION:
raise RawVaultError("RAW_LEASE_INVALID", "raw 保留期限必须在未来 24 小时内")
vault_id = secrets.token_hex(16)
lease = VaultLease(
authorization_id=authorization_id,
approved_by=approved_by,
run_id=run_id,
vault_id=vault_id,
source_version=source_version,
content_hashes=hashes,
created_at=now.isoformat(),
retain_until=retention.isoformat(),
purpose=purpose,
)
record = self._lease_record(lease, status="open")
lease_path = self._lease_path(vault_id)
if lease_path.exists():
raise RawVaultError("RAW_LEASE_INVALID", "vault ID 冲突")
# 顺序是安全合同:lease fsync 完成前,磁盘上不能出现 raw vault。
_atomic_write_json(lease_path, record)
try:
os.mkdir(self._vault_path(vault_id), 0o700)
os.chmod(self._vault_path(vault_id), 0o700, follow_symlinks=False)
_fsync_directory(self.vault_root)
except OSError as exc:
# lease 保持 open,恢复扫描会补记缺失或清理半成品,不能静默撤销审计记录。
raise RawVaultError("RAW_CREATE_FAILED", "raw vault 创建失败") from exc
return lease
def _lease_record(
self,
lease: VaultLease,
*,
status: str,
closed_at: str | None = None,
close_reason: str | None = None,
) -> dict[str, Any]:
"""生成不含路径和正文的 lease journal 内容。"""
record: dict[str, Any] = {
"schemaVersion": "raw-vault-lease-v1",
"authorizationId": lease.authorization_id,
"approvedBy": lease.approved_by,
"runId": lease.run_id,
"vaultId": lease.vault_id,
"sourceVersion": lease.source_version,
"contentHashes": list(lease.content_hashes),
"createdAt": lease.created_at,
"retainUntil": lease.retain_until,
"purpose": lease.purpose,
"status": status,
}
if closed_at is not None:
record["closedAt"] = closed_at
if close_reason is not None:
record["closeReason"] = close_reason
return record
def _load_record(self, vault_id: str) -> dict[str, Any]:
"""以 O_NOFOLLOW 读取并校验 lease journal。"""
path = self._lease_path(vault_id)
try:
descriptor = os.open(path, os.O_RDONLY | os.O_NOFOLLOW)
with os.fdopen(descriptor, "r", encoding="utf-8") as handle:
record = json.load(handle)
except (OSError, json.JSONDecodeError) as exc:
raise RawVaultError("RAW_LEASE_INVALID", "lease journal 不可读取") from exc
required = {
"schemaVersion",
"authorizationId",
"approvedBy",
"runId",
"vaultId",
"sourceVersion",
"contentHashes",
"createdAt",
"retainUntil",
"purpose",
"status",
}
if not isinstance(record, dict) or required - set(record) or record.get("vaultId") != vault_id:
raise RawVaultError("RAW_LEASE_INVALID", "lease journal 结构非法")
return record
def _lease_from_record(self, record: Mapping[str, Any]) -> VaultLease:
"""把已校验 journal 转回只含非敏感身份的 lease。"""
try:
return VaultLease(
authorization_id=str(record["authorizationId"]),
approved_by=str(record["approvedBy"]),
run_id=str(record["runId"]),
vault_id=str(record["vaultId"]),
source_version=str(record["sourceVersion"]),
content_hashes=tuple(str(item) for item in record["contentHashes"]),
created_at=str(record["createdAt"]),
retain_until=str(record["retainUntil"]),
purpose=str(record["purpose"]),
)
except (KeyError, TypeError) as exc:
raise RawVaultError("RAW_LEASE_INVALID", "lease journal 结构非法") from exc
def _assert_open_lease(self, lease: VaultLease) -> None:
"""确认调用方 lease 与 journal 完全一致且仍为 open。"""
record = self._load_record(lease.vault_id)
if record.get("status") != "open" or self._lease_from_record(record) != lease:
raise RawVaultError("RAW_LEASE_INVALID", "lease 已关闭或身份不匹配")
def write_bytes(self, lease: VaultLease, relative_path: str, content: bytes) -> None:
"""通过 openat/no-follow 在 vault 内排他创建 0600 raw 文件。"""
self._assert_open_lease(lease)
if not isinstance(content, bytes):
raise RawVaultError("RAW_WRITE_FAILED", "raw 内容必须是 bytes")
if not isinstance(relative_path, str):
raise RawVaultError("RAW_PATH_INVALID", "raw 相对路径非法")
candidate = pathlib.PurePosixPath(relative_path)
parts = candidate.parts
if candidate.is_absolute() or not parts or any(part in {"", ".", ".."} for part in parts):
raise RawVaultError("RAW_PATH_INVALID", "raw 相对路径越界")
root_fd = os.open(
self._vault_path(lease.vault_id),
os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW,
)
current_fd = root_fd
try:
for part in parts[:-1]:
try:
os.mkdir(part, 0o700, dir_fd=current_fd)
except FileExistsError:
pass
next_fd = os.open(
part,
os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW,
dir_fd=current_fd,
)
if current_fd != root_fd:
os.close(current_fd)
current_fd = next_fd
descriptor = os.open(
parts[-1],
os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW,
0o600,
dir_fd=current_fd,
)
with os.fdopen(descriptor, "wb") as handle:
handle.write(content)
handle.flush()
os.fsync(handle.fileno())
os.fsync(current_fd)
except (OSError, ValueError) as exc:
if isinstance(exc, (NotADirectoryError, FileExistsError, OSError)):
code = "RAW_PATH_INVALID"
else:
code = "RAW_WRITE_FAILED"
raise RawVaultError(code, "raw 文件创建失败") from exc
finally:
if current_fd != root_fd:
os.close(current_fd)
os.close(root_fd)
def _remove_tree_at(self, parent_fd: int, name: str) -> None:
"""递归删除目录项,软链接只 unlink,绝不跟随到 vault 外。"""
metadata = os.stat(name, dir_fd=parent_fd, follow_symlinks=False)
if stat.S_ISDIR(metadata.st_mode):
directory_fd = os.open(
name,
os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW,
dir_fd=parent_fd,
)
try:
for child in os.listdir(directory_fd):
self._remove_tree_at(directory_fd, child)
os.fsync(directory_fd)
finally:
os.close(directory_fd)
os.rmdir(name, dir_fd=parent_fd)
else:
os.unlink(name, dir_fd=parent_fd)
def _delete_vault_name(self, vault_name: str) -> bool:
"""按受控目录名清理 vault,返回目录是否原本存在。"""
if not vault_name.startswith(self.vault_prefix) or "/" in vault_name:
raise RawVaultError("RAW_PATH_INVALID", "vault 名称非法")
root_fd = os.open(self.vault_root, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)
try:
try:
self._remove_tree_at(root_fd, vault_name)
except FileNotFoundError:
return False
os.fsync(root_fd)
return True
finally:
os.close(root_fd)
def _close_lease(self, lease: VaultLease, reason: str) -> dict[str, Any]:
"""原子补记 lease 关闭状态并返回不含路径的清理回执。"""
closed_at = _utc_now().isoformat()
record = self._lease_record(
lease,
status="closed",
closed_at=closed_at,
close_reason=reason,
)
receipt = {
"schemaVersion": "raw-vault-cleanup-receipt-v1",
"vaultId": lease.vault_id,
"runId": lease.run_id,
"status": "closed",
"closedAt": closed_at,
"reason": reason,
}
record["cleanupReceiptSha256"] = "sha256:" + hashlib.sha256(
_canonical_json(receipt).encode("utf-8")
).hexdigest()
_atomic_write_json(self._lease_path(lease.vault_id), record)
return receipt
def cleanup(self, lease: VaultLease) -> dict[str, Any]:
"""清空 vault 后关闭 lease;清理未成功前不写 closed。"""
self._assert_open_lease(lease)
try:
existed = self._delete_vault_name(self._vault_name(lease.vault_id))
except OSError as exc:
raise RawVaultError("RAW_CLEANUP_FAILED", "raw vault 清理失败") from exc
return self._close_lease(lease, "cleaned" if existed else "vault_missing")
def recover(self) -> dict[str, list[str]]:
"""扫描 open lease、缺失 vault 和同命名空间孤儿目录并安全收敛。"""
cleaned: list[str] = []
closed_missing: list[str] = []
known_vault_ids: set[str] = set()
for lease_path in sorted(self.leases_root.glob("*.json")):
if lease_path.is_symlink():
raise RawVaultError("RAW_LEASE_INVALID", "lease journal 不能是软链接")
vault_id = lease_path.stem
record = self._load_record(vault_id)
known_vault_ids.add(vault_id)
if record.get("status") != "open":
# closed lease 理论上不再有 raw;若崩溃或外部复制留下同名目录,恢复仍立即清理。
if self._delete_vault_name(self._vault_name(vault_id)):
cleaned.append(vault_id)
continue
lease = self._lease_from_record(record)
existed = self._delete_vault_name(self._vault_name(vault_id))
self._close_lease(lease, "recovery_cleaned" if existed else "recovery_missing")
(cleaned if existed else closed_missing).append(vault_id)
cleaned_orphans: list[str] = []
for entry in sorted(os.listdir(self.vault_root)):
if not entry.startswith(self.vault_prefix):
continue
suffix = entry.removeprefix(self.vault_prefix)
if suffix not in known_vault_ids:
self._delete_vault_name(entry)
cleaned_orphans.append(suffix)
return {
"cleanedVaultIds": cleaned,
"closedMissingVaultIds": closed_missing,
"cleanedOrphanIds": cleaned_orphans,
}
__all__ = ["RawVaultError", "RawVaultManager", "VaultLease"]