1316 lines
69 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
"""内部狗粮运行保障工具的行为测试。"""
from __future__ import annotations
import hashlib
import importlib.util
import json
import shutil
import subprocess
import tempfile
import unittest
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Any
TOOL = Path(__file__).resolve().parents[1] / "dogfood_ops.py"
OPS_DIR = TOOL.parent
MODULE_SPEC = importlib.util.spec_from_file_location("dogfood_ops", TOOL)
assert MODULE_SPEC is not None and MODULE_SPEC.loader is not None
DOGFOOD_OPS = importlib.util.module_from_spec(MODULE_SPEC)
MODULE_SPEC.loader.exec_module(DOGFOOD_OPS)
class DogfoodOpsTest(unittest.TestCase):
"""通过命令行入口验证运维人员实际使用的行为。"""
FIXTURE_STARTED_AT = datetime(2026, 7, 20, 9, 0, tzinfo=timezone.utc)
FIXTURE_RC_COMMIT = "1" * 40
@classmethod
def setUpClass(cls) -> None:
"""生成测试专用签名根,并复制 CLI 到相同的固定相对信任路径。"""
cls.trust_temp_dir = tempfile.TemporaryDirectory()
deploy_dir = Path(cls.trust_temp_dir.name) / "deploy"
(deploy_dir / "dogfood-ops").mkdir(parents=True)
(deploy_dir / "trust").mkdir(parents=True)
cls.test_tool = deploy_dir / "dogfood-ops" / "dogfood_ops.py"
cls.test_private_key = deploy_dir / "trust" / "r1-private.pem"
cls.test_public_key = deploy_dir / "trust" / "r1-attestation-ed25519.pub"
shutil.copy2(TOOL, cls.test_tool)
subprocess.run(
["openssl", "genpkey", "-algorithm", "ED25519", "-out", str(cls.test_private_key)],
check=True, capture_output=True,
)
subprocess.run(
["openssl", "pkey", "-in", str(cls.test_private_key), "-pubout", "-out", str(cls.test_public_key)],
check=True, capture_output=True,
)
cls.original_public_key = getattr(DOGFOOD_OPS, "ACCOUNT_REPORT_PUBLIC_KEY", None)
DOGFOOD_OPS.ACCOUNT_REPORT_PUBLIC_KEY = cls.test_public_key
@classmethod
def tearDownClass(cls) -> None:
"""恢复模块常量并删除测试签名材料。"""
if cls.original_public_key is not None:
DOGFOOD_OPS.ACCOUNT_REPORT_PUBLIC_KEY = cls.original_public_key
cls.trust_temp_dir.cleanup()
def setUp(self) -> None:
"""每个用例使用独立临时证据根,避免测试相互污染。"""
self.temp_dir = tempfile.TemporaryDirectory()
self.root = Path(self.temp_dir.name) / "evidence"
def tearDown(self) -> None:
"""清理测试创建的本地证据。"""
self.temp_dir.cleanup()
def run_tool(self, *args: str) -> subprocess.CompletedProcess[str]:
"""调用真实 CLI,并保留标准输出供失败断言。"""
return subprocess.run(
["python3", str(self.test_tool), *args],
check=False,
text=True,
capture_output=True,
)
def init_wave(self, wave: str) -> Path:
"""初始化指定波次并返回证据目录。"""
# 公共通过夹具固定在已过去的可信时间,避免用未来证据伪造刚初始化波次的 readiness。
result = DOGFOOD_OPS.initialize_wave(
self.root, wave, now=self.FIXTURE_STARTED_AT, rc_commit=self.FIXTURE_RC_COMMIT)
self.assertEqual(result, 0)
return self.root / wave
def write_json(self, path: Path, value: Any) -> None:
"""写入稳定 JSON fixture。"""
path.write_text(json.dumps(value, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
def add_attachment(self, wave_dir: Path, name: str, content: str = "evidence") -> tuple[str, str]:
"""创建目录内证据附件并返回相对路径与哈希。"""
attachment = wave_dir / "attachments" / name
attachment.parent.mkdir(exist_ok=True)
attachment.write_text(content, encoding="utf-8")
return attachment.relative_to(wave_dir).as_posix(), hashlib.sha256(attachment.read_bytes()).hexdigest()
def sign_report(self, report_path: Path, signature_path: Path) -> None:
"""使用测试专用私钥生成真实 detached signature。"""
subprocess.run(
[
"openssl", "pkeyutl", "-sign", "-inkey", str(self.test_private_key),
"-rawin", "-in", str(report_path), "-out", str(signature_path),
],
check=True,
capture_output=True,
)
report_path.chmod(0o600)
signature_path.chmod(0o600)
def close_valid_wave0(self) -> tuple[Path, datetime]:
"""按两个实际 UTC 运行日构造已通过收口门的 Wave0。"""
started_at = datetime(2026, 7, 20, 9, 0, tzinfo=timezone.utc)
self.assertEqual(DOGFOOD_OPS.initialize_wave(
self.root, "wave0", now=started_at, rc_commit=self.FIXTURE_RC_COMMIT), 0)
wave_dir = self.root / "wave0"
self.populate_valid_evidence(wave_dir)
self.assertEqual(
DOGFOOD_OPS.freeze_evidence(
wave_dir, "2026-07-20", now=datetime(2026, 7, 20, 23, 0, tzinfo=timezone.utc)
),
0,
)
closed_at = datetime(2026, 7, 21, 9, 1, tzinfo=timezone.utc)
self.assertEqual(DOGFOOD_OPS.freeze_evidence(wave_dir, "2026-07-21", now=closed_at), 0)
gate = DOGFOOD_OPS.EvidenceGate(wave_dir, "close", now=closed_at)
self.assertEqual(gate.run(), 0, gate.errors)
return wave_dir, closed_at
def set_trace_duration(self, wave_dir: Path, sample: dict[str, Any], duration_ms: float) -> None:
"""修改测试 trace 的结束时间并同步样本哈希。"""
raw_path = wave_dir / sample["rawRef"]
payload = json.loads(raw_path.read_text(encoding="utf-8"))
payload["traceEvents"][1]["ts"] = payload["traceEvents"][0]["ts"] + duration_ms * 1000
self.write_json(raw_path, payload)
sample["rawSha256"] = hashlib.sha256(raw_path.read_bytes()).hexdigest()
def set_json_time(self, wave_dir: Path, filename: str, keys: tuple[Any, ...], value: str) -> None:
"""修改一处 JSON 证据时间,供统一时间窗接线测试复用。"""
path = wave_dir / filename
payload = json.loads(path.read_text(encoding="utf-8"))
target: Any = payload
for key in keys[:-1]:
target = target[key]
target[keys[-1]] = value
self.write_json(path, payload)
def test_checked_evidence_time_rejects_both_sides_of_wave_window(self) -> None:
"""统一时间窗必须同时拒绝波次开始前和可信当前时钟之后的证据。"""
started_at = self.FIXTURE_STARTED_AT
now = started_at + timedelta(hours=24)
wave_dir = self.init_wave("wave0")
gate = DOGFOOD_OPS.EvidenceGate(wave_dir, "readiness", now=now)
gate.started_at = started_at
self.assertIsNone(gate.checked_evidence_time(
DOGFOOD_OPS.format_timestamp(started_at - timedelta(seconds=1)), "fixture.before"
))
self.assertIsNone(gate.checked_evidence_time(
DOGFOOD_OPS.format_timestamp(now + timedelta(seconds=1)), "fixture.future"
))
self.assertEqual(
[code for code, _ in gate.errors],
["evidence-time.window", "evidence-time.window"],
)
def test_all_operational_evidence_times_use_the_same_wave_window(self) -> None:
"""值班、告警、反馈、人工验收、性能及其余结构化证据时间必须全部接入统一时间窗。"""
started_at = self.FIXTURE_STARTED_AT
now = started_at + timedelta(hours=25)
before = DOGFOOD_OPS.format_timestamp(started_at - timedelta(seconds=1))
future = DOGFOOD_OPS.format_timestamp(now + timedelta(seconds=1))
def mutate_performance(wave_dir: Path, value: str) -> None:
path = wave_dir / "performance.json"
payload = json.loads(path.read_text(encoding="utf-8"))
sample = payload["samples"][0]
sample["collectedAt"] = value
raw_path = wave_dir / sample["rawRef"]
raw = json.loads(raw_path.read_text(encoding="utf-8"))
raw["metadata"]["collectedAt"] = value
self.write_json(raw_path, raw)
sample["rawSha256"] = hashlib.sha256(raw_path.read_bytes()).hexdigest()
self.write_json(path, payload)
def mutate_tsv(wave_dir: Path, filename: str, old: str, value: str) -> None:
path = wave_dir / filename
path.write_text(path.read_text(encoding="utf-8").replace(old, value), encoding="utf-8")
scenarios = [
("config", before, lambda wave_dir, value: self.set_json_time(wave_dir, "config-identity.json", ("capturedAt",), value)),
("snapshot", future, lambda wave_dir, value: self.set_json_time(wave_dir, "snapshot.json", ("mysql", "capturedAt"), value)),
("restore", before, lambda wave_dir, value: self.set_json_time(wave_dir, "snapshot.json", ("isolatedRestore", "completedAt"), value)),
("storage", future, lambda wave_dir, value: self.set_json_time(wave_dir, "storage-audit.json", ("capturedAt",), value)),
("review", before, lambda wave_dir, value: self.set_json_time(wave_dir, "attachments/review-binding.json", ("approvedAt",), value)),
("database", future, lambda wave_dir, value: self.set_json_time(wave_dir, "attachments/database.json", ("capturedAt",), value)),
("manual-play", before, lambda wave_dir, value: self.set_json_time(wave_dir, "manual-play.json", ("games", 0, "playedAt"), value)),
("performance", future, mutate_performance),
("alerts", before, lambda wave_dir, value: self.set_json_time(wave_dir, "alerts.json", ("deliveries", 0, "receivedAt"), value)),
("oncall-start", before, lambda wave_dir, value: mutate_tsv(wave_dir, "oncall.tsv", DOGFOOD_OPS.format_timestamp(started_at), value)),
("oncall-end", future, lambda wave_dir, value: mutate_tsv(wave_dir, "oncall.tsv", DOGFOOD_OPS.format_timestamp(started_at + timedelta(hours=24)), value)),
("feedback", before, lambda wave_dir, value: mutate_tsv(wave_dir, "feedback.tsv", DOGFOOD_OPS.format_timestamp(started_at), value)),
("manual-acceptance", future, lambda wave_dir, value: mutate_tsv(wave_dir, "manual-acceptance.tsv", DOGFOOD_OPS.format_timestamp(started_at), value)),
("drills", before, lambda wave_dir, value: self.set_json_time(wave_dir, "drills.json", ("drills", 0, "performedAt"), value)),
("safety", future, lambda wave_dir, value: self.set_json_time(wave_dir, "safety-status.json", ("assessedAt",), value)),
]
for name, invalid_time, mutate in scenarios:
with self.subTest(evidence=name):
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir) / "evidence"
self.assertEqual(DOGFOOD_OPS.initialize_wave(
root, "wave0", now=started_at, rc_commit=self.FIXTURE_RC_COMMIT), 0)
wave_dir = root / "wave0"
self.populate_valid_evidence(wave_dir)
mutate(wave_dir, invalid_time)
gate = DOGFOOD_OPS.EvidenceGate(wave_dir, "readiness", now=now)
self.assertNotEqual(gate.run(), 0)
self.assertTrue(
any(code == "evidence-time.window" for code, _ in gate.errors),
f"{name} 未接入统一时间窗:{gate.errors}",
)
def test_wave1_init_requires_closed_wave0(self) -> None:
"""没有 Wave0 收口回执时不得初始化 Wave1。"""
result = self.run_tool(
"init", "--root", str(self.root), "--wave", "wave1",
"--rc-commit", self.FIXTURE_RC_COMMIT,
)
self.assertNotEqual(result.returncode, 0)
self.assertIn("WAVE0_CLOSE_REQUIRED", result.stderr)
def test_manifest_and_freeze_use_tool_generated_trusted_times(self) -> None:
"""开始与冻结时间必须由工具生成,并拒绝未来或回填日期。"""
started_at = datetime(2026, 7, 20, 9, 0, tzinfo=timezone.utc)
result = DOGFOOD_OPS.initialize_wave(
self.root, "wave0", now=started_at, rc_commit=self.FIXTURE_RC_COMMIT)
self.assertEqual(result, 0)
wave_dir = self.root / "wave0"
manifest = json.loads((wave_dir / "manifest.json").read_text(encoding="utf-8"))
self.assertEqual(manifest["startedAt"], "2026-07-20T09:00:00Z")
self.assertEqual(manifest["rcCommit"], self.FIXTURE_RC_COMMIT)
backfill = DOGFOOD_OPS.freeze_evidence(wave_dir, "2026-07-19", now=started_at)
future = DOGFOOD_OPS.freeze_evidence(wave_dir, "2026-07-21", now=started_at)
current = DOGFOOD_OPS.freeze_evidence(wave_dir, "2026-07-20", now=started_at)
self.assertNotEqual(backfill, 0)
self.assertNotEqual(future, 0)
self.assertEqual(current, 0)
freeze = json.loads((wave_dir / "freezes/2026-07-20.sha256.json").read_text(encoding="utf-8"))
self.assertEqual(freeze["createdAt"], "2026-07-20T09:00:00Z")
def test_close_requires_elapsed_runtime_and_every_actual_utc_day(self) -> None:
"""收口必须达到真实时长,并覆盖从 startedAt 到 close 的每个 UTC 运行日。"""
started_at = datetime(2026, 7, 20, 9, 0, tzinfo=timezone.utc)
DOGFOOD_OPS.initialize_wave(
self.root, "wave0", now=started_at, rc_commit=self.FIXTURE_RC_COMMIT)
wave_dir = self.root / "wave0"
self.populate_valid_evidence(wave_dir)
DOGFOOD_OPS.freeze_evidence(wave_dir, "2026-07-20", now=started_at.replace(hour=23))
too_early = DOGFOOD_OPS.EvidenceGate(
wave_dir, "close", now=started_at + timedelta(hours=23, minutes=59)
)
self.assertNotEqual(too_early.run(), 0)
self.assertTrue(any(code == "wave.duration" for code, _ in too_early.errors))
close_time = started_at + timedelta(hours=24, minutes=1)
missing_day = DOGFOOD_OPS.EvidenceGate(wave_dir, "close", now=close_time)
self.assertNotEqual(missing_day.run(), 0)
self.assertTrue(any(code == "freeze.actual-days" for code, _ in missing_day.errors))
def test_same_minute_cannot_forge_three_daily_freezes(self) -> None:
"""同一时刻只能冻结当天,不能用三个日期伪造 Wave1 三天运行。"""
now = datetime(2026, 7, 23, 5, 30, tzinfo=timezone.utc)
DOGFOOD_OPS.initialize_wave(
self.root, "wave0", now=now, rc_commit=self.FIXTURE_RC_COMMIT)
wave_dir = self.root / "wave0"
results = [
DOGFOOD_OPS.freeze_evidence(wave_dir, day, now=now)
for day in ("2026-07-21", "2026-07-22", "2026-07-23")
]
self.assertEqual(results, [2, 2, 0])
def test_close_rejects_forged_freeze_created_at(self) -> None:
"""手工把 createdAt 改成未来或与文件日期不一致时不得收口。"""
started_at = datetime(2026, 7, 20, 9, 0, tzinfo=timezone.utc)
DOGFOOD_OPS.initialize_wave(
self.root, "wave0", now=started_at, rc_commit=self.FIXTURE_RC_COMMIT)
wave_dir = self.root / "wave0"
self.populate_valid_evidence(wave_dir)
freeze_now = datetime(2026, 7, 20, 23, 0, tzinfo=timezone.utc)
self.assertEqual(DOGFOOD_OPS.freeze_evidence(wave_dir, "2026-07-20", now=freeze_now), 0)
freeze_path = wave_dir / "freezes/2026-07-20.sha256.json"
freeze = json.loads(freeze_path.read_text(encoding="utf-8"))
freeze["createdAt"] = "2026-07-21T23:00:00Z"
self.write_json(freeze_path, freeze)
gate = DOGFOOD_OPS.EvidenceGate(wave_dir, "close", now=started_at + timedelta(hours=24))
self.assertNotEqual(gate.run(), 0)
self.assertTrue(any(code == "freeze.created-at" for code, _ in gate.errors))
def test_performance_rejects_self_reported_duration_without_raw_gstack_record(self) -> None:
"""自填 durationMs 且未绑定原始 /gstack 记录时必须失败。"""
wave_dir = self.init_wave("wave0")
self.populate_valid_evidence(wave_dir)
path = wave_dir / "performance.json"
payload = json.loads(path.read_text(encoding="utf-8"))
for sample in payload["samples"]:
sample["durationMs"] = 2000
sample.pop("rawRef")
sample.pop("rawSha256")
self.write_json(path, payload)
result = self.run_tool("check", "--wave-dir", str(wave_dir), "--phase", "readiness")
self.assertNotEqual(result.returncode, 0)
self.assertIn("performance.raw-record", result.stdout)
def test_rc_and_game_artifact_hashes_are_independent_and_game_is_bound_three_ways(self) -> None:
"""RC 制品可不同于游戏包,游戏哈希必须同时绑定审核、运行包和数据库证据。"""
wave_dir = self.init_wave("wave0")
self.populate_valid_evidence(wave_dir)
rc = json.loads((wave_dir / "rc.json").read_text(encoding="utf-8"))
chain = json.loads((wave_dir / "main-chain.json").read_text(encoding="utf-8"))
self.assertNotEqual(rc["rcArtifactSha256"], chain["identity"]["gameArtifactSha256"])
result = self.run_tool("check", "--wave-dir", str(wave_dir), "--phase", "readiness")
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertNotIn("main-chain.rc-artifact", result.stdout)
self.assertNotIn("main-chain.game-artifact", result.stdout)
def test_accounts_rejects_self_reported_counts_without_database_report(self) -> None:
"""自填人数和 FREE 数不能替代绑定激活批次的权威数据库报告。"""
wave_dir = self.init_wave("wave0")
self.populate_valid_evidence(wave_dir)
self.write_json(wave_dir / "accounts.json", {
"schema": "dogfood-accounts/1",
"batchId": "forged-batch",
"creatorCount": 3,
"playerCount": 5,
"freeQuotaCount": 12,
})
result = self.run_tool("check", "--wave-dir", str(wave_dir), "--phase", "readiness")
self.assertNotEqual(result.returncode, 0)
self.assertIn("accounts.json schema 必须为 dogfood-accounts/2", result.stdout)
def test_accounts_rejects_report_with_incomplete_canonical_account_set(self) -> None:
"""权威报告必须明确覆盖 dogfoodc01-10 与 dogfoodp01-30,不能只报波次人数。"""
wave_dir = self.init_wave("wave0")
self.populate_valid_evidence(wave_dir)
accounts = json.loads((wave_dir / "accounts.json").read_text(encoding="utf-8"))
report_path = wave_dir / accounts["databaseReportRef"]
report = json.loads(report_path.read_text(encoding="utf-8"))
report["accounts"].pop()
self.write_json(report_path, report)
self.sign_report(report_path, wave_dir / accounts["databaseReportSignatureRef"])
accounts["databaseReportSha256"] = hashlib.sha256(report_path.read_bytes()).hexdigest()
self.write_json(wave_dir / "accounts.json", accounts)
result = self.run_tool("check", "--wave-dir", str(wave_dir), "--phase", "readiness")
self.assertNotEqual(result.returncode, 0)
self.assertIn("accounts.database-report.accounts", result.stdout)
def test_accounts_rejects_activation_or_quota_status_drift(self) -> None:
"""激活 run-id、CLAIMED 账号额度和 12 条 FREE 预留任一漂移都必须失败关闭。"""
for mutation, expected_code in (
("activation", "accounts.activation-run-id"),
("captured-at", "accounts.database-report.captured-at"),
("claimed-quota", "accounts.database-report.account-quota"),
("token-disabled", "accounts.database-report.account-quota"),
("token-balance", "accounts.database-report.account-quota"),
("token-unlimited", "accounts.database-report.account-quota"),
("token-expiry", "accounts.database-report.account-quota"),
("newapi-user-duplicate", "accounts.database-report.account-quota"),
("newapi-token-duplicate", "accounts.database-report.account-quota"),
("reserve-status", "accounts.database-report.reserve-quota"),
):
with self.subTest(mutation=mutation):
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir) / "evidence"
self.assertEqual(DOGFOOD_OPS.initialize_wave(
root, "wave0", rc_commit=self.FIXTURE_RC_COMMIT), 0)
wave_dir = root / "wave0"
self.populate_valid_evidence(wave_dir)
accounts_path = wave_dir / "accounts.json"
accounts = json.loads(accounts_path.read_text(encoding="utf-8"))
report_path = wave_dir / accounts["databaseReportRef"]
report = json.loads(report_path.read_text(encoding="utf-8"))
if mutation == "activation":
report["activationRunId"] = "different-activation"
elif mutation == "captured-at":
started_at = datetime.fromisoformat(
json.loads((wave_dir / "manifest.json").read_text(encoding="utf-8"))["startedAt"]
.replace("Z", "+00:00")
)
stale_time = DOGFOOD_OPS.format_timestamp(started_at - timedelta(hours=3))
report["capturedAt"] = stale_time
accounts["databaseReportCapturedAt"] = stale_time
elif mutation == "claimed-quota":
report["accounts"][0]["grantQuota"] = 0
elif mutation == "token-disabled":
report["accounts"][0]["tokenStatus"] = "DISABLED"
elif mutation == "token-balance":
report["accounts"][0]["tokenRemainQuota"] = 0
elif mutation == "token-unlimited":
report["accounts"][0]["tokenUnlimitedQuota"] = True
elif mutation == "token-expiry":
report["accounts"][0]["tokenExpiredTime"] = 1800000000
elif mutation == "newapi-user-duplicate":
report["accounts"][1]["newapiUserId"] = report["accounts"][0]["newapiUserId"]
elif mutation == "newapi-token-duplicate":
report["accounts"][1]["newapiTokenId"] = report["accounts"][0]["newapiTokenId"]
else:
report["reserves"][0]["quotaStatus"] = "CLAIMED"
self.write_json(report_path, report)
self.sign_report(report_path, wave_dir / accounts["databaseReportSignatureRef"])
accounts["databaseReportSha256"] = hashlib.sha256(report_path.read_bytes()).hexdigest()
self.write_json(accounts_path, accounts)
gate = DOGFOOD_OPS.EvidenceGate(wave_dir, "readiness")
self.assertNotEqual(gate.run(), 0)
self.assertTrue(any(code == expected_code for code, _ in gate.errors), gate.errors)
def test_accounts_rejects_selected_identity_not_present_in_report(self) -> None:
"""波次成员必须按 ID+用户名精确引用权威报告中的同一账号。"""
wave_dir = self.init_wave("wave0")
self.populate_valid_evidence(wave_dir)
accounts_path = wave_dir / "accounts.json"
accounts = json.loads(accounts_path.read_text(encoding="utf-8"))
accounts["selectedAccounts"][0]["username"] = "dogfoodc10"
self.write_json(accounts_path, accounts)
gate = DOGFOOD_OPS.EvidenceGate(wave_dir, "readiness")
self.assertNotEqual(gate.run(), 0)
self.assertTrue(any(code == "accounts.selected.identity" for code, _ in gate.errors), gate.errors)
def test_accounts_rejects_missing_or_tampered_detached_signature(self) -> None:
"""缺签或报告签名后被篡改时必须失败关闭。"""
for mutation in ("missing", "tampered"):
with self.subTest(mutation=mutation):
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir) / "evidence"
self.assertEqual(DOGFOOD_OPS.initialize_wave(
root, "wave0", rc_commit=self.FIXTURE_RC_COMMIT), 0)
wave_dir = root / "wave0"
self.populate_valid_evidence(wave_dir)
accounts = json.loads((wave_dir / "accounts.json").read_text(encoding="utf-8"))
signature_path = wave_dir / accounts["databaseReportSignatureRef"]
if mutation == "missing":
signature_path.unlink()
else:
report_path = wave_dir / accounts["databaseReportRef"]
report_path.write_text(report_path.read_text(encoding="utf-8") + " ", encoding="utf-8")
accounts["databaseReportSha256"] = hashlib.sha256(report_path.read_bytes()).hexdigest()
self.write_json(wave_dir / "accounts.json", accounts)
gate = DOGFOOD_OPS.EvidenceGate(wave_dir, "readiness")
self.assertNotEqual(gate.run(), 0)
self.assertTrue(
any(code == "accounts.database-report.signature" for code, _ in gate.errors),
gate.errors,
)
def test_accounts_rejects_signed_issuer_rc_or_query_drift(self) -> None:
"""即使签名有效,固定签发者、manifest RC 和固定查询身份也不能漂移。"""
for field, value, expected_code in (
("issuer", "operator-self-signed", "accounts.database-report.issuer"),
("rcCommit", "2" * 40, "accounts.database-report.rc-commit"),
("queryId", "handwritten-query", "accounts.database-report.query"),
("querySchema", "handwritten-row/1", "accounts.database-report.query"),
):
with self.subTest(field=field):
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir) / "evidence"
self.assertEqual(DOGFOOD_OPS.initialize_wave(
root, "wave0", rc_commit=self.FIXTURE_RC_COMMIT), 0)
wave_dir = root / "wave0"
self.populate_valid_evidence(wave_dir)
accounts_path = wave_dir / "accounts.json"
accounts = json.loads(accounts_path.read_text(encoding="utf-8"))
report_path = wave_dir / accounts["databaseReportRef"]
report = json.loads(report_path.read_text(encoding="utf-8"))
report[field] = value
self.write_json(report_path, report)
self.sign_report(report_path, wave_dir / accounts["databaseReportSignatureRef"])
accounts["databaseReportSha256"] = hashlib.sha256(report_path.read_bytes()).hexdigest()
self.write_json(accounts_path, accounts)
gate = DOGFOOD_OPS.EvidenceGate(wave_dir, "readiness")
self.assertNotEqual(gate.run(), 0)
self.assertTrue(any(code == expected_code for code, _ in gate.errors), gate.errors)
def populate_valid_evidence(self, wave_dir: Path) -> None:
"""构造不依赖 staging 的完整本地通过 fixture。"""
manifest = json.loads((wave_dir / "manifest.json").read_text(encoding="utf-8"))
wave = manifest["wave"]
started_at = datetime.fromisoformat(manifest["startedAt"].replace("Z", "+00:00"))
captured_at = DOGFOOD_OPS.format_timestamp(started_at)
played_at = DOGFOOD_OPS.format_timestamp(started_at + timedelta(seconds=1))
feed_entered_at = DOGFOOD_OPS.format_timestamp(started_at + timedelta(seconds=2))
commit = "1" * 40
rc_artifact_ref, rc_artifact_hash = self.add_attachment(wave_dir, "rc-artifact.bin", "rc-package")
runtime_ref, game_artifact_hash = self.add_attachment(wave_dir, "game-runtime.zip", "game-package")
config_ref, config_hash = self.add_attachment(wave_dir, "config-bundle.txt")
dump_ref, dump_hash = self.add_attachment(wave_dir, "mysql.sql.gz")
restore_ref, _ = self.add_attachment(wave_dir, "restore-report.json")
storage_ref, storage_hash = self.add_attachment(wave_dir, "storage-audit.json")
common_ref, _ = self.add_attachment(wave_dir, "browser-proof.png")
network_ref, _ = self.add_attachment(wave_dir, "main-chain.har")
log_ref, _ = self.add_attachment(wave_dir, "application.log")
review_ref, _ = self.add_attachment(wave_dir, "review-binding.json")
database_ref, _ = self.add_attachment(wave_dir, "database.json")
account_report_ref = "attachments/account-database-report.json"
game_identity = {
"projectId": "p-1",
"versionId": "v-1",
"gameId": "g-1",
"gameArtifactSha256": game_artifact_hash,
}
self.write_json(wave_dir / review_ref, {
"schema": "dogfood-review-artifact-binding/1",
**game_identity,
"reviewId": "review-1",
"status": "APPROVED",
"approvedAt": captured_at,
})
self.write_json(wave_dir / database_ref, {
"schema": "dogfood-main-chain-db-binding/1",
**game_identity,
"queryId": "query-1",
"capturedAt": captured_at,
})
self.write_json(wave_dir / "rc.json", {
"schema": "dogfood-rc/2", "commit": commit, "repoClean": True,
"rcArtifactRef": rc_artifact_ref, "rcArtifactSha256": rc_artifact_hash,
})
self.write_json(wave_dir / "config-identity.json", {
"schema": "dogfood-config-identity/1", "capturedAt": captured_at,
"rcCommit": commit, "configBundleRef": config_ref, "configSha256": config_hash,
})
self.write_json(wave_dir / "snapshot.json", {
"schema": "dogfood-snapshot/1",
"mysql": {"snapshotRef": dump_ref, "sha256": dump_hash, "capturedAt": captured_at},
"isolatedRestore": {"result": "PASS", "target": "isolated-dogfood-restore-001", "completedAt": captured_at, "evidenceRef": restore_ref},
})
self.write_json(wave_dir / "storage-audit.json", {
"schema": "dogfood-storage-audit/1", "result": "PASS", "capturedAt": captured_at,
"reportRef": storage_ref, "reportSha256": storage_hash,
})
all_accounts = []
for index in range(1, 11):
all_accounts.append({
"id": 1000 + index,
"username": f"dogfoodc{index:02d}",
"role": "creator",
"accountStatus": "ACTIVE",
"quotaPoolId": 2000 + index,
"quotaStatus": "CLAIMED",
"grantQuota": 6849315,
"newapiUserId": 4000 + index,
"newapiTokenId": 5000 + index,
"newapiUsername": f"neice_{index:03d}",
"tokenStatus": "ENABLED",
"tokenRemainQuota": 6849315,
"tokenUnlimitedQuota": False,
"tokenExpiredTime": -1,
})
for index in range(1, 31):
all_accounts.append({
"id": 1100 + index,
"username": f"dogfoodp{index:02d}",
"role": "player",
"accountStatus": "ACTIVE",
"quotaPoolId": 2100 + index,
"quotaStatus": "CLAIMED",
"grantQuota": 6849315,
"newapiUserId": 4100 + index,
"newapiTokenId": 5100 + index,
"newapiUsername": f"neice_{10 + index:03d}",
"tokenStatus": "ENABLED",
"tokenRemainQuota": 6849315,
"tokenUnlimitedQuota": False,
"tokenExpiredTime": -1,
})
reserves = [{
"poolId": 3000 + index,
"newapiUserId": 4200 + index,
"newapiTokenId": 5200 + index,
"newapiUsername": f"neice_{40 + index:03d}",
"quotaStatus": "FREE",
"grantQuota": 6849315,
"tokenStatus": "ENABLED",
"tokenRemainQuota": 6849315,
"tokenUnlimitedQuota": False,
"tokenExpiredTime": -1,
} for index in range(1, 13)]
activation_run_id = "activation-20260723T010203Z"
self.write_json(wave_dir / account_report_ref, {
"schema": "dogfood-account-database-report/2",
"issuer": "r1-mini-infra",
"activationRunId": activation_run_id,
"rcCommit": commit,
"capturedAt": captured_at,
"queryId": "dogfood-account-readiness-v2",
"querySchema": "dogfood-account-readiness-row/2",
"accounts": all_accounts,
"reserves": reserves,
})
account_signature_ref = account_report_ref + ".sig"
self.sign_report(wave_dir / account_report_ref, wave_dir / account_signature_ref)
account_report_hash = hashlib.sha256((wave_dir / account_report_ref).read_bytes()).hexdigest()
selected = (
all_accounts[:3] + all_accounts[10:15]
if wave == "wave0"
else all_accounts
)
self.write_json(wave_dir / "accounts.json", {
"schema": "dogfood-accounts/2",
"activationRunId": activation_run_id,
"databaseReportRef": account_report_ref,
"databaseReportSignatureRef": account_signature_ref,
"databaseReportSha256": account_report_hash,
"databaseReportCapturedAt": captured_at,
"selectedAccounts": [
{"id": account["id"], "username": account["username"]}
for account in selected
],
})
permission_cases = {
"creator-own-project": "ALLOW",
"creator-cross-account-denied": "DENY",
"reviewer-review-only": "ALLOW",
"reviewer-config-write-denied": "DENY",
"readonly-write-denied": "DENY",
}
self.write_json(wave_dir / "role-permissions.json", {
"schema": "dogfood-role-permissions/1",
"checks": [{"case": case, "result": result, "screenshotRef": common_ref} for case, result in permission_cases.items()],
})
evidence_bundle = {"screenshotRef": common_ref, "networkRef": network_ref, "logRef": log_ref, "databaseRef": database_ref}
stages = ["create", "generate", "preview", "submit-review", "approve", "feed", "play", "telemetry"]
self.write_json(wave_dir / "main-chain.json", {
"schema": "dogfood-main-chain/2",
"identity": {
**game_identity,
"reviewRecordRef": review_ref,
"runtimePackageRef": runtime_ref,
"mainChainDatabaseRef": database_ref,
},
"stages": [{"stage": stage, "result": "PASS", **evidence_bundle} for stage in stages],
})
negative_cases = [
"draft-publish", "reviewing-publish", "rejected-publish", "direct-publish-bypass",
"artifact-drift", "delisted-feed", "delisted-detail", "delisted-deep-link",
]
self.write_json(wave_dir / "release-negatives.json", {
"schema": "dogfood-release-negatives/1",
"checks": [{"case": case, "blocked": True, "evidenceRef": common_ref} for case in negative_cases],
})
self.write_json(wave_dir / "manual-play.json", {
"schema": "dogfood-manual-play/1", "releasedGameCount": 1,
"games": [{"gameId": "g-1", "tester": "tester-1", "playedAt": played_at, "feedEnteredAt": feed_entered_at, "screenshotRef": common_ref, "source": "/gstack@mini-desktop"}],
})
samples = []
for index in range(30):
navigation_id = f"nav-{index:02d}"
collector = {"tool": "/gstack", "host": "mini-desktop", "sessionId": f"session-{index:02d}"}
raw_ref, raw_hash = self.add_attachment(wave_dir, f"perf-{index:02d}.trace.json")
self.write_json(wave_dir / raw_ref, {
"metadata": {"collector": collector, "collectedAt": captured_at},
"traceEvents": [
{"name": "dogfood.navigationCommit", "ts": 1_000_000, "args": {"navigationId": navigation_id}},
{"name": "dogfood.firstInteractiveGameFrame", "ts": 3_000_000, "args": {"navigationId": navigation_id}},
],
})
raw_hash = hashlib.sha256((wave_dir / raw_ref).read_bytes()).hexdigest()
samples.append({
"navigationId": navigation_id,
"cache": "cold",
"source": "/gstack@mini-desktop",
"rawRef": raw_ref,
"rawSha256": raw_hash,
"collectedAt": captured_at,
"collector": collector,
})
self.write_json(wave_dir / "performance.json", {
"schema": "dogfood-first-frame-performance/2",
"context": {"host": "mini-desktop", "browser": "Chrome", "browserVersion": "146", "viewport": "390x844@1", "network": "Tailscale staging", "timing": "navigation-commit-to-first-interactive-game-frame"},
"samples": samples,
})
self.write_json(wave_dir / "telemetry.json", {
"schema": "dogfood-telemetry-completeness/1", "expectedCount": 20, "completeCount": 19,
"evidenceRef": common_ref,
})
self.write_json(wave_dir / "alerts.json", {
"schema": "dogfood-alert-delivery/1",
"deliveries": [{"alertId": "alert-1", "receivedAt": captured_at, "recipient": "oncall-1", "externalMessageId": "msg-1", "evidenceRef": common_ref}],
})
start = started_at
end = start + timedelta(hours=manifest["durationHours"])
(wave_dir / "oncall.tsv").write_text(
"start_at\tend_at\tprimary\tbackup\tcontact\n"
f"{start.isoformat().replace('+00:00', 'Z')}\t{end.isoformat().replace('+00:00', 'Z')}\toncall-1\toncall-2\tinternal-roster\n",
encoding="utf-8",
)
(wave_dir / "feedback.tsv").write_text(
"id\tcreated_at\treporter_id\trole\tcategory\tseverity\tsummary\tstatus\towner\tevidence_ref\n"
f"F-1\t{captured_at}\tu-1\tplayer\tux\tP2\tfixture feedback\tCLOSED\tops\t{common_ref}\n",
encoding="utf-8",
)
checklist_items = ["role-permissions", "main-chain", "release-negatives", "manual-play-before-feed"]
(wave_dir / "manual-acceptance.tsv").write_text(
"item\tresult\toperator\tchecked_at\tevidence_ref\n" + "".join(
f"{item}\tPASS\toperator-1\t{captured_at}\t{common_ref}\n" for item in checklist_items
),
encoding="utf-8",
)
self.write_json(wave_dir / "drills.json", {
"schema": "dogfood-drills/1",
"drills": [{"action": action, "result": "PASS", "ack": f"ACK-DOGFOOD-{wave}-{action}", "performedAt": captured_at, "operator": "ops-1", "evidenceRef": common_ref} for action in ["stop", "delete", "rollback"]],
})
stop_conditions = [
"security-incident", "cross-account-access", "data-loss", "real-charge", "real-ad-impression",
"unreviewed-content-in-feed", "generation-success-below-80", "confirmed-false-positive",
"same-root-cause-blocked-two-users", "telemetry-missing-above-5", "review-backlog-over-4h",
"acceptance-cost-exceeded",
]
self.write_json(wave_dir / "safety-status.json", {
"schema": "dogfood-safety-status/1", "assessedAt": captured_at, "assessor": "ops-1",
"conditions": [{"condition": condition, "triggered": False} for condition in stop_conditions],
})
def test_init_wave0_creates_fixed_ledgers_and_scale(self) -> None:
"""wave0 必须固定为 3 名创作者和 5 名玩家。"""
wave_dir = self.init_wave("wave0")
manifest = json.loads((wave_dir / "manifest.json").read_text(encoding="utf-8"))
self.assertEqual(manifest["wave"], "wave0")
self.assertEqual(manifest["creatorCount"], 3)
self.assertEqual(manifest["playerCount"], 5)
self.assertTrue((wave_dir / "oncall.tsv").is_file())
self.assertTrue((wave_dir / "feedback.tsv").is_file())
self.assertTrue((wave_dir / "p0.tsv").is_file())
self.assertTrue((wave_dir / "config-identity.json").is_file())
self.assertTrue((wave_dir / "manual-acceptance.tsv").is_file())
for name in (
"rc.json", "snapshot.json", "storage-audit.json", "accounts.json", "role-permissions.json",
"main-chain.json", "release-negatives.json", "manual-play.json", "performance.json",
"telemetry.json", "alerts.json", "drills.json", "safety-status.json",
):
self.assertTrue((wave_dir / name).is_file(), name)
self.assertTrue((wave_dir / "attachments").is_dir())
self.assertTrue((wave_dir / "freezes").is_dir())
def test_init_wave1_uses_approved_scale(self) -> None:
"""wave1 必须固定为累计 10 名创作者和 30 名玩家。"""
wave0_dir, closed_at = self.close_valid_wave0()
self.assertTrue((wave0_dir / "close-result.json").is_file())
self.assertEqual(DOGFOOD_OPS.initialize_wave(
self.root, "wave1", now=closed_at + timedelta(minutes=1), rc_commit=self.FIXTURE_RC_COMMIT), 0)
wave_dir = self.root / "wave1"
manifest = json.loads((wave_dir / "manifest.json").read_text(encoding="utf-8"))
self.assertEqual((manifest["creatorCount"], manifest["playerCount"]), (10, 30))
self.assertEqual(manifest["wave0CloseResultSha256"], hashlib.sha256((wave0_dir / "close-result.json").read_bytes()).hexdigest())
final_freeze = wave0_dir / "freezes/2026-07-21.sha256.json"
self.assertEqual(manifest["wave0FinalFreezeSha256"], hashlib.sha256(final_freeze.read_bytes()).hexdigest())
def test_wave1_revalidates_bound_wave0_close_and_final_freeze(self) -> None:
"""Wave1 开波门必须持续复核 Wave0 回执和最终冻结哈希。"""
wave0_dir, closed_at = self.close_valid_wave0()
wave1_started_at = closed_at + timedelta(minutes=1)
self.assertEqual(DOGFOOD_OPS.initialize_wave(
self.root, "wave1", now=wave1_started_at, rc_commit=self.FIXTURE_RC_COMMIT), 0)
wave1_dir = self.root / "wave1"
self.populate_valid_evidence(wave1_dir)
(wave0_dir / "close-result.json").write_text(
(wave0_dir / "close-result.json").read_text(encoding="utf-8") + " ",
encoding="utf-8",
)
gate = DOGFOOD_OPS.EvidenceGate(wave1_dir, "readiness", now=wave1_started_at)
self.assertNotEqual(gate.run(), 0)
self.assertTrue(any(code == "wave1.wave0-binding" for code, _ in gate.errors))
def test_wave1_init_rejects_wave0_with_p0(self) -> None:
"""Wave0 收口后出现 P0 时不得初始化 Wave1。"""
wave0_dir, closed_at = self.close_valid_wave0()
(wave0_dir / "p0.tsv").write_text(
"id\tseverity\tstatus\tsummary\tevidence_ref\n"
"P0-1\tP0\tOPEN\tsecurity\tattachments/browser-proof.png\n",
encoding="utf-8",
)
result = DOGFOOD_OPS.initialize_wave(
self.root, "wave1", now=closed_at + timedelta(minutes=1), rc_commit=self.FIXTURE_RC_COMMIT)
self.assertNotEqual(result, 0)
_, errors = DOGFOOD_OPS.inspect_wave0_close(self.root, closed_at + timedelta(minutes=1))
self.assertTrue(any(code == "wave1.wave0-p0" for code, _ in errors))
def test_wave1_init_rejects_wave0_with_false_positive(self) -> None:
"""Wave0 存在确认发布假阳时不得初始化 Wave1。"""
wave0_dir, closed_at = self.close_valid_wave0()
path = wave0_dir / "safety-status.json"
payload = json.loads(path.read_text(encoding="utf-8"))
next(item for item in payload["conditions"] if item["condition"] == "confirmed-false-positive")["triggered"] = True
self.write_json(path, payload)
result = DOGFOOD_OPS.initialize_wave(
self.root, "wave1", now=closed_at + timedelta(minutes=1), rc_commit=self.FIXTURE_RC_COMMIT)
self.assertNotEqual(result, 0)
_, errors = DOGFOOD_OPS.inspect_wave0_close(self.root, closed_at + timedelta(minutes=1))
self.assertTrue(any(code == "wave1.wave0-false-positive" for code, _ in errors))
def test_wave1_init_rejects_wave0_with_review_backlog(self) -> None:
"""Wave0 存在超过四小时待审积压时不得初始化 Wave1。"""
wave0_dir, closed_at = self.close_valid_wave0()
path = wave0_dir / "safety-status.json"
payload = json.loads(path.read_text(encoding="utf-8"))
next(item for item in payload["conditions"] if item["condition"] == "review-backlog-over-4h")["triggered"] = True
self.write_json(path, payload)
result = DOGFOOD_OPS.initialize_wave(
self.root, "wave1", now=closed_at + timedelta(minutes=1), rc_commit=self.FIXTURE_RC_COMMIT)
self.assertNotEqual(result, 0)
_, errors = DOGFOOD_OPS.inspect_wave0_close(self.root, closed_at + timedelta(minutes=1))
self.assertTrue(any(code == "wave1.wave0-review-backlog" for code, _ in errors))
def test_repeated_init_is_idempotent_and_does_not_overwrite(self) -> None:
"""重复初始化应成功返回,但不得覆盖已经填写的证据。"""
wave_dir = self.init_wave("wave0")
feedback = wave_dir / "feedback.tsv"
feedback.write_text(feedback.read_text(encoding="utf-8") + "F-1\t2026-07-22T00:00:00Z\tu1\tplayer\tbug\tP1\t卡住\tOPEN\tops\tattachments/f.txt\n", encoding="utf-8")
before = hashlib.sha256(feedback.read_bytes()).hexdigest()
result = self.run_tool(
"init", "--root", str(self.root), "--wave", "wave0",
"--rc-commit", self.FIXTURE_RC_COMMIT,
)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(hashlib.sha256(feedback.read_bytes()).hexdigest(), before)
self.assertIn("INIT_ALREADY_EXISTS", result.stdout)
def test_repeated_init_repairs_missing_templates_without_overwrite(self) -> None:
"""初始化中断后重试应补回缺失模板,同时保留已经填写的账本。"""
wave_dir = self.init_wave("wave0")
feedback = wave_dir / "feedback.tsv"
feedback.write_text(feedback.read_text(encoding="utf-8") + "kept\n", encoding="utf-8")
(wave_dir / "rc.json").unlink()
(wave_dir / "attachments").rmdir()
result = self.run_tool(
"init", "--root", str(self.root), "--wave", "wave0",
"--rc-commit", self.FIXTURE_RC_COMMIT,
)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn("INIT_REPAIRED", result.stdout)
self.assertTrue((wave_dir / "rc.json").is_file())
self.assertTrue((wave_dir / "attachments").is_dir())
self.assertTrue(feedback.read_text(encoding="utf-8").endswith("kept\n"))
def test_init_rejects_unknown_wave_instead_of_creating_a_path(self) -> None:
"""波次名不得被当成任意相对路径使用。"""
result = self.run_tool("init", "--root", str(self.root), "--wave", "../wave0")
self.assertNotEqual(result.returncode, 0)
self.assertFalse((Path(self.temp_dir.name) / "wave0").exists())
self.assertIn("invalid choice", result.stderr)
def test_readiness_fails_closed_when_evidence_is_missing(self) -> None:
"""仅初始化模板不得被误判为可开波。"""
wave_dir = self.init_wave("wave0")
result = self.run_tool("check", "--wave-dir", str(wave_dir), "--phase", "readiness")
self.assertNotEqual(result.returncode, 0)
self.assertIn("rc.missing", result.stdout)
self.assertNotIn("GATE_PASS", result.stdout)
def test_readiness_accepts_complete_structured_evidence(self) -> None:
"""完整且互相对账的结构化证据应通过开波门。"""
wave_dir = self.init_wave("wave0")
self.populate_valid_evidence(wave_dir)
result = self.run_tool("check", "--wave-dir", str(wave_dir), "--phase", "readiness")
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertIn("GATE_PASS phase=readiness", result.stdout)
def test_performance_requires_at_least_30_cold_independent_samples(self) -> None:
"""冷样本少于 30 个时必须直接失败。"""
wave_dir = self.init_wave("wave0")
self.populate_valid_evidence(wave_dir)
path = wave_dir / "performance.json"
payload = json.loads(path.read_text(encoding="utf-8"))
payload["samples"] = payload["samples"][:29]
self.write_json(path, payload)
result = self.run_tool("check", "--wave-dir", str(wave_dir), "--phase", "readiness")
self.assertNotEqual(result.returncode, 0)
self.assertIn("performance.sample-count", result.stdout)
def test_performance_p75_uses_nearest_rank_and_rejects_3000ms(self) -> None:
"""30 样本的第 23 个值是 P75,恰好 3000ms 不满足严格小于。"""
wave_dir = self.init_wave("wave0")
self.populate_valid_evidence(wave_dir)
path = wave_dir / "performance.json"
payload = json.loads(path.read_text(encoding="utf-8"))
for index, sample in enumerate(payload["samples"]):
self.set_trace_duration(wave_dir, sample, 1000 if index < 22 else 3000)
self.write_json(path, payload)
result = self.run_tool("check", "--wave-dir", str(wave_dir), "--phase", "readiness")
self.assertNotEqual(result.returncode, 0)
self.assertIn("performance.p75", result.stdout)
def test_performance_accepts_p75_of_2999ms(self) -> None:
"""严格阈值内的第 23 个值应通过。"""
wave_dir = self.init_wave("wave0")
self.populate_valid_evidence(wave_dir)
path = wave_dir / "performance.json"
payload = json.loads(path.read_text(encoding="utf-8"))
for index, sample in enumerate(payload["samples"]):
self.set_trace_duration(wave_dir, sample, 1000 if index < 22 else 2999)
self.write_json(path, payload)
result = self.run_tool("check", "--wave-dir", str(wave_dir), "--phase", "readiness")
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
def test_performance_rejects_hot_cache_mixed_into_cold_samples(self) -> None:
"""冷样本文件中混入热缓存记录时不得计算 P75。"""
wave_dir = self.init_wave("wave0")
self.populate_valid_evidence(wave_dir)
path = wave_dir / "performance.json"
payload = json.loads(path.read_text(encoding="utf-8"))
payload["samples"][0]["cache"] = "hot"
self.write_json(path, payload)
result = self.run_tool("check", "--wave-dir", str(wave_dir), "--phase", "readiness")
self.assertNotEqual(result.returncode, 0)
self.assertIn("performance.cold-only", result.stdout)
def test_performance_rejects_non_finite_raw_trace_timestamp(self) -> None:
"""原始 trace 的 NaN 时间戳不得绕过 P75 阈值。"""
wave_dir = self.init_wave("wave0")
self.populate_valid_evidence(wave_dir)
path = wave_dir / "performance.json"
payload = json.loads(path.read_text(encoding="utf-8"))
for sample in payload["samples"]:
self.set_trace_duration(wave_dir, sample, float("nan"))
self.write_json(path, payload)
result = self.run_tool("check", "--wave-dir", str(wave_dir), "--phase", "readiness")
self.assertNotEqual(result.returncode, 0)
self.assertIn("performance.raw-record", result.stdout)
def test_performance_rejects_tampered_raw_record_hash(self) -> None:
"""原始 trace 内容变化但样本哈希未同步时必须失败。"""
wave_dir = self.init_wave("wave0")
self.populate_valid_evidence(wave_dir)
performance = json.loads((wave_dir / "performance.json").read_text(encoding="utf-8"))
raw_path = wave_dir / performance["samples"][0]["rawRef"]
raw_path.write_text(raw_path.read_text(encoding="utf-8") + " ", encoding="utf-8")
result = self.run_tool("check", "--wave-dir", str(wave_dir), "--phase", "readiness")
self.assertNotEqual(result.returncode, 0)
self.assertIn("performance.raw-record.hash-mismatch", result.stdout)
def test_performance_recomputes_har_timestamps(self) -> None:
"""HAR 样本也必须从原始起止时间重算,不读取 durationMs。"""
wave_dir = self.init_wave("wave0")
self.populate_valid_evidence(wave_dir)
path = wave_dir / "performance.json"
payload = json.loads(path.read_text(encoding="utf-8"))
sample = payload["samples"][0]
old_path = wave_dir / sample["rawRef"]
old_path.unlink()
har_ref = "attachments/perf-00.har"
har_path = wave_dir / har_ref
self.write_json(har_path, {
"log": {
"pages": [{
"id": sample["navigationId"],
"_gstack": {
"collector": sample["collector"],
"collectedAt": sample["collectedAt"],
"navigationCommitAt": "2026-07-23T00:00:00Z",
"firstInteractiveGameFrameAt": "2026-07-23T00:00:02.999Z",
},
}],
},
})
sample["rawRef"] = har_ref
sample["rawSha256"] = hashlib.sha256(har_path.read_bytes()).hexdigest()
self.write_json(path, payload)
result = self.run_tool("check", "--wave-dir", str(wave_dir), "--phase", "readiness")
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
def test_performance_rejects_collector_identity_mismatch_with_raw_record(self) -> None:
"""样本声明的采集器身份必须与原始记录内身份完全一致。"""
wave_dir = self.init_wave("wave0")
self.populate_valid_evidence(wave_dir)
path = wave_dir / "performance.json"
payload = json.loads(path.read_text(encoding="utf-8"))
payload["samples"][0]["collector"]["sessionId"] = "forged-session"
self.write_json(path, payload)
result = self.run_tool("check", "--wave-dir", str(wave_dir), "--phase", "readiness")
self.assertNotEqual(result.returncode, 0)
self.assertIn("performance.raw-record", result.stdout)
def test_performance_rejects_missing_or_future_collection_time(self) -> None:
"""每条原始记录都必须绑定合法且不在未来的采集时间。"""
for collected_at in (None, "2999-01-01T00:00:00Z"):
with self.subTest(collected_at=collected_at):
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir) / "evidence"
self.assertEqual(DOGFOOD_OPS.initialize_wave(
root, "wave0", rc_commit=self.FIXTURE_RC_COMMIT), 0)
wave_dir = root / "wave0"
self.populate_valid_evidence(wave_dir)
path = wave_dir / "performance.json"
payload = json.loads(path.read_text(encoding="utf-8"))
payload["samples"][0]["collectedAt"] = collected_at
self.write_json(path, payload)
gate = DOGFOOD_OPS.EvidenceGate(wave_dir, "readiness")
self.assertNotEqual(gate.run(), 0)
self.assertTrue(any(code == "performance.collected-at" for code, _ in gate.errors))
def test_main_chain_requires_distinct_evidence_modalities(self) -> None:
"""同一个占位附件不得同时冒充截图、网络、日志和数据库证据。"""
wave_dir = self.init_wave("wave0")
self.populate_valid_evidence(wave_dir)
path = wave_dir / "main-chain.json"
payload = json.loads(path.read_text(encoding="utf-8"))
for field in ("networkRef", "logRef", "databaseRef"):
payload["stages"][0][field] = payload["stages"][0]["screenshotRef"]
self.write_json(path, payload)
result = self.run_tool("check", "--wave-dir", str(wave_dir), "--phase", "readiness")
self.assertNotEqual(result.returncode, 0)
self.assertIn("main-chain.evidence-modalities", result.stdout)
def test_telemetry_accepts_exactly_95_percent_and_rejects_below(self) -> None:
"""完整率 95% 可过,低于 95% 必须失败。"""
wave_dir = self.init_wave("wave0")
self.populate_valid_evidence(wave_dir)
telemetry = wave_dir / "telemetry.json"
pass_result = self.run_tool("check", "--wave-dir", str(wave_dir), "--phase", "readiness")
self.assertEqual(pass_result.returncode, 0, pass_result.stdout + pass_result.stderr)
self.write_json(telemetry, {"schema": "dogfood-telemetry-completeness/1", "expectedCount": 100, "completeCount": 94, "evidenceRef": "attachments/browser-proof.png"})
fail_result = self.run_tool("check", "--wave-dir", str(wave_dir), "--phase", "readiness")
self.assertNotEqual(fail_result.returncode, 0)
self.assertIn("telemetry.completeness", fail_result.stdout)
def test_top_level_pass_cannot_forge_restore_evidence(self) -> None:
"""写一个 PASS 字符串不能替代隔离恢复目标和证据。"""
wave_dir = self.init_wave("wave0")
self.populate_valid_evidence(wave_dir)
self.write_json(wave_dir / "snapshot.json", {"schema": "dogfood-snapshot/1", "result": "PASS"})
result = self.run_tool("check", "--wave-dir", str(wave_dir), "--phase", "readiness")
self.assertNotEqual(result.returncode, 0)
self.assertIn("snapshot.mysql", result.stdout)
self.assertIn("snapshot.isolated-restore", result.stdout)
def test_evidence_reference_cannot_escape_wave_directory(self) -> None:
"""即使目录外文件存在,证据引用也不得越过波次根。"""
wave_dir = self.init_wave("wave0")
self.populate_valid_evidence(wave_dir)
outside = wave_dir.parent / "outside.png"
outside.write_text("outside", encoding="utf-8")
payload = json.loads((wave_dir / "release-negatives.json").read_text(encoding="utf-8"))
payload["checks"][0]["evidenceRef"] = "../outside.png"
self.write_json(wave_dir / "release-negatives.json", payload)
result = self.run_tool("check", "--wave-dir", str(wave_dir), "--phase", "readiness")
self.assertNotEqual(result.returncode, 0)
self.assertIn("reference.outside-wave", result.stdout)
def test_main_chain_game_artifact_must_match_runtime_package(self) -> None:
"""游戏产物哈希必须与实际 runtime package 内容一致。"""
wave_dir = self.init_wave("wave0")
self.populate_valid_evidence(wave_dir)
path = wave_dir / "main-chain.json"
payload = json.loads(path.read_text(encoding="utf-8"))
payload["identity"]["gameArtifactSha256"] = "f" * 64
self.write_json(path, payload)
result = self.run_tool("check", "--wave-dir", str(wave_dir), "--phase", "readiness")
self.assertNotEqual(result.returncode, 0)
self.assertIn("main-chain.game-artifact", result.stdout)
def test_main_chain_game_artifact_must_match_review_and_database_records(self) -> None:
"""审核记录或主链数据库任一身份漂移都必须拒绝。"""
for ref_key in ("reviewRecordRef", "mainChainDatabaseRef"):
with self.subTest(ref_key=ref_key):
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir) / "evidence"
self.assertEqual(DOGFOOD_OPS.initialize_wave(
root, "wave0", rc_commit=self.FIXTURE_RC_COMMIT), 0)
wave_dir = root / "wave0"
self.populate_valid_evidence(wave_dir)
chain = json.loads((wave_dir / "main-chain.json").read_text(encoding="utf-8"))
binding_path = wave_dir / chain["identity"][ref_key]
binding = json.loads(binding_path.read_text(encoding="utf-8"))
binding["gameArtifactSha256"] = "f" * 64
self.write_json(binding_path, binding)
gate = DOGFOOD_OPS.EvidenceGate(wave_dir, "readiness")
self.assertNotEqual(gate.run(), 0)
self.assertTrue(any(code == "main-chain.game-artifact" for code, _ in gate.errors))
def test_game_database_binding_must_be_used_by_main_chain_stage(self) -> None:
"""独立伪造的数据库绑定不能替代主链阶段实际采集的数据库证据。"""
wave_dir = self.init_wave("wave0")
self.populate_valid_evidence(wave_dir)
chain_path = wave_dir / "main-chain.json"
chain = json.loads(chain_path.read_text(encoding="utf-8"))
original = wave_dir / chain["identity"]["mainChainDatabaseRef"]
detached_ref = "attachments/detached-database-binding.json"
(wave_dir / detached_ref).write_bytes(original.read_bytes())
chain["identity"]["mainChainDatabaseRef"] = detached_ref
self.write_json(chain_path, chain)
result = self.run_tool("check", "--wave-dir", str(wave_dir), "--phase", "readiness")
self.assertNotEqual(result.returncode, 0)
self.assertIn("main-chain.game-artifact", result.stdout)
def test_close_requires_freeze_and_detects_post_freeze_tampering(self) -> None:
"""收口门必须验证最近一次 SHA-256 冻结,冻结后改动立即失效。"""
started_at = datetime(2026, 7, 20, 9, 0, tzinfo=timezone.utc)
DOGFOOD_OPS.initialize_wave(
self.root, "wave0", now=started_at, rc_commit=self.FIXTURE_RC_COMMIT)
wave_dir = self.root / "wave0"
self.populate_valid_evidence(wave_dir)
closed_at = started_at + timedelta(hours=24, minutes=1)
missing = DOGFOOD_OPS.EvidenceGate(wave_dir, "close", now=closed_at)
self.assertNotEqual(missing.run(), 0)
self.assertTrue(any(code == "freeze.missing" for code, _ in missing.errors))
self.assertEqual(DOGFOOD_OPS.freeze_evidence(wave_dir, "2026-07-20", now=started_at.replace(hour=23)), 0)
self.assertEqual(DOGFOOD_OPS.freeze_evidence(wave_dir, "2026-07-21", now=closed_at), 0)
passed = DOGFOOD_OPS.EvidenceGate(wave_dir, "close", now=closed_at)
self.assertEqual(passed.run(), 0, passed.errors)
self.assertTrue((wave_dir / "close-result.json").is_file())
with (wave_dir / "feedback.tsv").open("a", encoding="utf-8") as stream:
stream.write("F-2\t2026-07-22T08:00:00Z\tu-2\tplayer\tbug\tP1\ttampered\tOPEN\tops\tattachments/browser-proof.png\n")
tampered = DOGFOOD_OPS.EvidenceGate(wave_dir, "close", now=closed_at)
self.assertNotEqual(tampered.run(), 0)
self.assertTrue(any(code == "freeze.hash-mismatch" for code, _ in tampered.errors))
def test_close_rejects_malformed_freeze_without_traceback(self) -> None:
"""畸形冻结清单应形成可追踪失败,而不是让闸门异常退出。"""
started_at = datetime(2026, 7, 20, 9, 0, tzinfo=timezone.utc)
DOGFOOD_OPS.initialize_wave(
self.root, "wave0", now=started_at, rc_commit=self.FIXTURE_RC_COMMIT)
wave_dir = self.root / "wave0"
self.populate_valid_evidence(wave_dir)
freeze_dir = wave_dir / "freezes"
(freeze_dir / "2026-07-20.sha256.json").write_text("[]\n", encoding="utf-8")
gate = DOGFOOD_OPS.EvidenceGate(wave_dir, "close", now=started_at + timedelta(hours=24))
result = gate.run()
self.assertEqual(result, 1)
self.assertTrue(any(code == "freeze.schema" for code, _ in gate.errors))
def test_close_rejects_symlink_added_after_freeze_without_traceback(self) -> None:
"""冻结后加入符号链接时应 fail-closed,并保持稳定退出码。"""
started_at = datetime(2026, 7, 20, 9, 0, tzinfo=timezone.utc)
DOGFOOD_OPS.initialize_wave(
self.root, "wave0", now=started_at, rc_commit=self.FIXTURE_RC_COMMIT)
wave_dir = self.root / "wave0"
self.populate_valid_evidence(wave_dir)
self.assertEqual(DOGFOOD_OPS.freeze_evidence(wave_dir, "2026-07-20", now=started_at.replace(hour=23)), 0)
(wave_dir / "attachments" / "late-link").symlink_to(wave_dir / "attachments" / "browser-proof.png")
gate = DOGFOOD_OPS.EvidenceGate(wave_dir, "close", now=started_at + timedelta(hours=24))
result = gate.run()
self.assertEqual(result, 1)
self.assertTrue(any(code == "freeze.collection" for code, _ in gate.errors))
def test_wave1_close_requires_three_chained_daily_freezes(self) -> None:
"""Wave1 必须真实运行 72 小时并覆盖四个实际 UTC 日期,且哈希链不可篡改。"""
_, wave0_closed_at = self.close_valid_wave0()
started_at = wave0_closed_at + timedelta(minutes=1)
self.assertEqual(DOGFOOD_OPS.initialize_wave(
self.root, "wave1", now=started_at, rc_commit=self.FIXTURE_RC_COMMIT), 0)
wave_dir = self.root / "wave1"
self.populate_valid_evidence(wave_dir)
close_time = started_at + timedelta(hours=72, minutes=1)
for offset in range(4):
freeze_now = datetime.combine(
started_at.date() + timedelta(days=offset),
datetime.min.time(),
tzinfo=timezone.utc,
) + timedelta(hours=23)
if freeze_now > close_time:
freeze_now = close_time
self.assertEqual(DOGFOOD_OPS.freeze_evidence(wave_dir, freeze_now.date().isoformat(), now=freeze_now), 0)
passed = DOGFOOD_OPS.EvidenceGate(wave_dir, "close", now=close_time)
self.assertEqual(passed.run(), 0, passed.errors)
first_path = sorted((wave_dir / "freezes").glob("*.sha256.json"))[0]
first_path.write_text(first_path.read_text(encoding="utf-8") + " ", encoding="utf-8")
tampered = DOGFOOD_OPS.EvidenceGate(wave_dir, "close", now=close_time)
self.assertNotEqual(tampered.run(), 0)
self.assertTrue(any(code == "freeze.chain" for code, _ in tampered.errors))
def test_record_drill_requires_exact_ack_and_is_idempotent(self) -> None:
"""演练记录必须使用精确 ACK,重复签收同一证据不得制造重复行。"""
wave_dir = self.init_wave("wave0")
evidence_ref, _ = self.add_attachment(wave_dir, "stop-drill.txt")
common_args = (
"record-drill", "--wave-dir", str(wave_dir), "--action", "stop",
"--operator", "ops-1", "--performed-at", "2026-07-22T06:00:00Z",
"--evidence-ref", evidence_ref,
)
refused = self.run_tool(*common_args, "--ack", "yes")
self.assertNotEqual(refused.returncode, 0)
self.assertIn("ACK_REFUSED", refused.stderr)
for invalid_time in ("2026-07-20T08:59:59Z", "2999-01-01T00:00:00Z"):
invalid_args = list(common_args)
invalid_args[invalid_args.index("--performed-at") + 1] = invalid_time
refused_time = self.run_tool(*invalid_args, "--ack", "ACK-DOGFOOD-wave0-stop")
self.assertNotEqual(refused_time.returncode, 0)
self.assertIn("DRILL_REFUSED", refused_time.stderr)
accepted = self.run_tool(*common_args, "--ack", "ACK-DOGFOOD-wave0-stop")
self.assertEqual(accepted.returncode, 0, accepted.stdout + accepted.stderr)
repeated = self.run_tool(*common_args, "--ack", "ACK-DOGFOOD-wave0-stop")
self.assertEqual(repeated.returncode, 0, repeated.stdout + repeated.stderr)
self.assertIn("DRILL_ALREADY_RECORDED", repeated.stdout)
payload = json.loads((wave_dir / "drills.json").read_text(encoding="utf-8"))
self.assertEqual(len(payload["drills"]), 1)
def test_freeze_is_idempotent_but_refuses_same_day_rewrite(self) -> None:
"""同一证据可重复冻结,内容变化后不得覆盖同日清单。"""
now = datetime(2026, 7, 22, 8, 0, tzinfo=timezone.utc)
DOGFOOD_OPS.initialize_wave(
self.root, "wave0", now=now, rc_commit=self.FIXTURE_RC_COMMIT)
wave_dir = self.root / "wave0"
self.assertEqual(DOGFOOD_OPS.freeze_evidence(wave_dir, "2026-07-22", now=now), 0)
self.assertEqual(DOGFOOD_OPS.freeze_evidence(wave_dir, "2026-07-22", now=now + timedelta(minutes=1)), 0)
(wave_dir / "feedback.tsv").write_text("changed\n", encoding="utf-8")
self.assertNotEqual(DOGFOOD_OPS.freeze_evidence(wave_dir, "2026-07-22", now=now + timedelta(minutes=2)), 0)
def test_shell_entrypoints_delegate_to_the_single_cli(self) -> None:
"""值班入口应直接调用同一套实现,避免脚本间口径漂移。"""
scripts = [
"init-wave.sh", "check-readiness.sh", "check-close.sh",
"freeze-evidence.sh", "record-drill.sh",
]
for name in scripts:
result = subprocess.run(
["bash", str(OPS_DIR / name), "--help"],
check=False,
text=True,
capture_output=True,
)
self.assertEqual(result.returncode, 0, f"{name}: {result.stdout}{result.stderr}")
if __name__ == "__main__":
unittest.main()