fix(dogfood-ops): bind evidence to wave windows

This commit is contained in:
lili 2026-07-23 00:42:58 -07:00
parent f7d0942ab6
commit 3d928c4f8c
2 changed files with 165 additions and 39 deletions

View File

@ -144,6 +144,21 @@ class EvidenceGate:
"""记录一条可追踪失败,不因首个错误隐藏其余缺口。"""
self.errors.append((code, message))
def checked_evidence_time(self, value: Any, context: str) -> datetime | None:
"""统一校验证据时间必须落在波次开始与当前可信时钟之间。"""
parsed = parse_timestamp(value)
if parsed is None:
self.fail("evidence-time.invalid", f"{context} 必须是带时区 ISO 8601 时间")
return None
parsed = parsed.astimezone(timezone.utc)
if self.started_at is not None and (parsed < self.started_at or parsed > self.now):
self.fail(
"evidence-time.window",
f"{context} 必须满足 startedAt <= evidenceTime <= now{value}",
)
return None
return parsed
def load_json(self, name: str, schema: str) -> dict[str, Any] | None:
"""读取固定 JSON 文件并校验对象类型与 schema。"""
path = self.wave_dir / name
@ -288,7 +303,7 @@ class EvidenceGate:
self.fail("rc.clean", "RC 必须来自 tracked 工作区干净构建")
self.check_hash_ref(rc.get("rcArtifactRef"), rc.get("rcArtifactSha256"), "rc.artifact")
if config is not None:
if parse_timestamp(config.get("capturedAt")) is None:
if self.checked_evidence_time(config.get("capturedAt"), "config.capturedAt") is None:
self.fail("config.captured-at", "配置身份缺少带时区采集时间")
self.check_hash_ref(config.get("configBundleRef"), config.get("configSha256"), "config.bundle")
if rc is not None and config.get("rcCommit") != rc.get("commit"):
@ -303,7 +318,7 @@ class EvidenceGate:
self.fail("snapshot.mysql", "缺少 MySQL 快照对象")
else:
self.check_hash_ref(mysql.get("snapshotRef"), mysql.get("sha256"), "snapshot.mysql")
if parse_timestamp(mysql.get("capturedAt")) is None:
if self.checked_evidence_time(mysql.get("capturedAt"), "snapshot.mysql.capturedAt") is None:
self.fail("snapshot.mysql", "MySQL 快照缺少带时区采集时间")
restore = snapshot.get("isolatedRestore")
if not isinstance(restore, dict):
@ -312,13 +327,14 @@ class EvidenceGate:
target = str(restore.get("target", "")).lower()
if restore.get("result") != "PASS" or not target or any(word in target for word in ("staging", "prod", "production")):
self.fail("snapshot.isolated-restore", "隔离恢复必须 PASS 且目标不得是 staging/production")
if parse_timestamp(restore.get("completedAt")) is None:
if self.checked_evidence_time(restore.get("completedAt"), "snapshot.isolatedRestore.completedAt") is None:
self.fail("snapshot.isolated-restore", "隔离恢复缺少带时区完成时间")
self.checked_ref(restore.get("evidenceRef"), "snapshot.isolated-restore")
storage = self.load_json("storage-audit.json", "dogfood-storage-audit/1")
if storage is not None:
if storage.get("result") != "PASS" or parse_timestamp(storage.get("capturedAt")) is None:
if storage.get("result") != "PASS" or self.checked_evidence_time(
storage.get("capturedAt"), "storage-audit.capturedAt") is None:
self.fail("storage-audit.result", "对象存储审计必须含真实 PASS 与采集时间")
self.check_hash_ref(storage.get("reportRef"), storage.get("reportSha256"), "storage-audit.report")
@ -433,7 +449,7 @@ class EvidenceGate:
or not review_matches
or review.get("status") != "APPROVED"
or not review.get("reviewId")
or parse_timestamp(review.get("approvedAt")) is None
or self.checked_evidence_time(review.get("approvedAt"), "main-chain.review.approvedAt") is None
):
self.fail("main-chain.game-artifact", "审核记录未批准或未绑定同一游戏产物身份")
@ -444,7 +460,7 @@ class EvidenceGate:
database.get("schema") != "dogfood-main-chain-db-binding/1"
or not database_matches
or not database.get("queryId")
or parse_timestamp(database.get("capturedAt")) is None
or self.checked_evidence_time(database.get("capturedAt"), "main-chain.database.capturedAt") is None
):
self.fail("main-chain.game-artifact", "主链数据库证据未绑定同一游戏产物身份")
@ -472,8 +488,8 @@ class EvidenceGate:
if not isinstance(item, dict):
self.fail("manual-play.item", "人工真玩记录必须是对象")
continue
played_at = parse_timestamp(item.get("playedAt"))
feed_at = parse_timestamp(item.get("feedEnteredAt"))
played_at = self.checked_evidence_time(item.get("playedAt"), "manual-play.playedAt")
feed_at = self.checked_evidence_time(item.get("feedEnteredAt"), "manual-play.feedEnteredAt")
if not item.get("gameId") or not item.get("tester") or played_at is None or feed_at is None or played_at >= feed_at:
self.fail("manual-play.before-feed", "人工真玩必须发生在进入 feed 前")
if item.get("source") != "/gstack@mini-desktop":
@ -524,11 +540,9 @@ class EvidenceGate:
navigation_ids.append(navigation_id)
if "durationMs" in sample:
self.fail("performance.self-reported-duration", "不接受自填 durationMs必须从原始记录重算")
collected_at = parse_timestamp(sample.get("collectedAt"))
if collected_at is None or collected_at > self.now:
collected_at = self.checked_evidence_time(sample.get("collectedAt"), "performance.collectedAt")
if collected_at is None:
self.fail("performance.collected-at", "每个样本必须包含不晚于当前时间的带时区采集时间")
elif self.started_at is not None and collected_at < self.started_at:
self.fail("performance.collected-at", "性能样本采集时间不得早于波次 startedAt")
raw_ref = sample.get("rawRef")
if isinstance(raw_ref, str):
raw_refs.append(raw_ref)
@ -634,8 +648,10 @@ class EvidenceGate:
if not isinstance(metadata, dict) or metadata.get("collector") != collector or metadata.get("collectedAt") != collected_at:
self.fail("performance.raw-record", f"HAR 采集器身份或采集时间不匹配:{filename}")
return None
start = parse_timestamp(metadata.get("navigationCommitAt"))
end = parse_timestamp(metadata.get("firstInteractiveGameFrameAt"))
start = self.checked_evidence_time(
metadata.get("navigationCommitAt"), f"performance.{filename}.navigationCommitAt")
end = self.checked_evidence_time(
metadata.get("firstInteractiveGameFrameAt"), f"performance.{filename}.firstInteractiveGameFrameAt")
if start is None or end is None or end < start:
self.fail("performance.duration", f"HAR 起止时间非法:{filename}")
return None
@ -667,7 +683,9 @@ class EvidenceGate:
self.fail("alerts.deliveries", "至少需要一条告警真实送达记录")
else:
for item in deliveries:
if not isinstance(item, dict) or parse_timestamp(item.get("receivedAt")) is None or not item.get("recipient") or not item.get("externalMessageId"):
received_at = self.checked_evidence_time(
item.get("receivedAt") if isinstance(item, dict) else None, "alerts.receivedAt")
if not isinstance(item, dict) or received_at is None or not item.get("recipient") or not item.get("externalMessageId"):
self.fail("alerts.delivery", "告警缺少接收时间、接收人或外部消息 id")
continue
self.checked_ref(item.get("evidenceRef"), "alerts.evidence")
@ -678,8 +696,8 @@ class EvidenceGate:
shifts: list[tuple[datetime, datetime]] = []
if oncall is not None:
for row in oncall:
start = parse_timestamp(row.get("start_at"))
end = parse_timestamp(row.get("end_at"))
start = self.checked_evidence_time(row.get("start_at"), "oncall.start_at")
end = self.checked_evidence_time(row.get("end_at"), "oncall.end_at")
if start is None or end is None or start >= end or not row.get("primary") or not row.get("backup") or not row.get("contact"):
self.fail("oncall.row", "值班记录字段不完整或时间无效")
else:
@ -701,7 +719,8 @@ class EvidenceGate:
if feedback is not None:
seen_ids: set[str] = set()
for row in feedback:
if any(not row.get(field) for field in row) or parse_timestamp(row.get("created_at")) is None:
created_at = self.checked_evidence_time(row.get("created_at"), f"feedback.{row.get('id', 'unknown')}.created_at")
if any(not row.get(field) for field in row) or created_at is None:
self.fail("feedback.row", "反馈账本存在字段不完整的行")
continue
if row["id"] in seen_ids:
@ -714,7 +733,9 @@ class EvidenceGate:
by_item = {row.get("item"): row for row in checklist}
for item_name in REQUIRED_CHECKLIST_ITEMS:
row = by_item.get(item_name)
if row is None or row.get("result") != "PASS" or not row.get("operator") or parse_timestamp(row.get("checked_at")) is None:
checked_at = self.checked_evidence_time(
row.get("checked_at") if row else None, f"manual-acceptance.{item_name}.checked_at")
if row is None or row.get("result") != "PASS" or not row.get("operator") or checked_at is None:
self.fail("manual-acceptance.item", f"人工验收项缺失或未通过:{item_name}")
continue
self.checked_ref(row.get("evidence_ref"), f"manual-acceptance.{item_name}.evidence")
@ -730,14 +751,18 @@ class EvidenceGate:
for action in REQUIRED_DRILLS:
item = by_action.get(action)
expected_ack = f"ACK-DOGFOOD-{self.manifest['wave']}-{action}"
if item is None or item.get("result") != "PASS" or item.get("ack") != expected_ack or not item.get("operator") or parse_timestamp(item.get("performedAt")) is None:
performed_at = self.checked_evidence_time(
item.get("performedAt") if item else None, f"drills.{action}.performedAt")
if item is None or item.get("result") != "PASS" or item.get("ack") != expected_ack or not item.get("operator") or performed_at is None:
self.fail("drills.action", f"演练缺失、未通过或 ACK 错误:{action}")
continue
self.checked_ref(item.get("evidenceRef"), f"drills.{action}.evidence")
safety = self.load_json("safety-status.json", "dogfood-safety-status/1")
conditions = safety.get("conditions") if safety else None
if not isinstance(conditions, list) or parse_timestamp(safety.get("assessedAt") if safety else None) is None or not (safety or {}).get("assessor"):
assessed_at = self.checked_evidence_time(
safety.get("assessedAt") if safety else None, "safety-status.assessedAt")
if not isinstance(conditions, list) or assessed_at is None or not (safety or {}).get("assessor"):
self.fail("safety-status.assessment", "安全状态缺少评估人、时间或条件数组")
return
by_condition = {item.get("condition"): item for item in conditions if isinstance(item, dict)}
@ -1128,14 +1153,17 @@ def record_drill(
if ack != expected_ack:
print(f"ACK_REFUSED必须显式输入 {expected_ack}", file=sys.stderr)
return 2
if not operator or parse_timestamp(performed_at) is None:
print("DRILL_REFUSEDoperator 和带时区 performed-at 必填。", file=sys.stderr)
if not operator:
print("DRILL_REFUSEDoperator 必填。", file=sys.stderr)
return 2
# 复用闸门的可信路径边界,但不运行其它证据校验。
# 复用闸门的 manifest、统一证据时间窗和可信路径边界,但不运行其它业务证据校验。
ref_gate = EvidenceGate(wave_dir, "record-drill")
if ref_gate.checked_ref(evidence_ref, f"drills.{action}.evidence") is None:
print("DRILL_REFUSEDevidence-ref 不在波次目录内或不存在。", file=sys.stderr)
ref_gate.validate_manifest()
performed_time = ref_gate.checked_evidence_time(performed_at, f"drills.{action}.performedAt")
evidence_path = ref_gate.checked_ref(evidence_ref, f"drills.{action}.evidence")
if ref_gate.errors or performed_time is None or evidence_path is None:
print("DRILL_REFUSEDperformed-at 越过波次时间窗,或 evidence-ref 非法。", file=sys.stderr)
return 2
record = {
"action": action,

View File

@ -26,6 +26,8 @@ MODULE_SPEC.loader.exec_module(DOGFOOD_OPS)
class DogfoodOpsTest(unittest.TestCase):
"""通过命令行入口验证运维人员实际使用的行为。"""
FIXTURE_STARTED_AT = datetime(2026, 7, 20, 9, 0, tzinfo=timezone.utc)
def setUp(self) -> None:
"""每个用例使用独立临时证据根,避免测试相互污染。"""
self.temp_dir = tempfile.TemporaryDirectory()
@ -46,8 +48,9 @@ class DogfoodOpsTest(unittest.TestCase):
def init_wave(self, wave: str) -> Path:
"""初始化指定波次并返回证据目录。"""
result = self.run_tool("init", "--root", str(self.root), "--wave", wave)
self.assertEqual(result.returncode, 0, result.stderr)
# 公共通过夹具固定在已过去的可信时间,避免用未来证据伪造刚初始化波次的 readiness。
result = DOGFOOD_OPS.initialize_wave(self.root, wave, now=self.FIXTURE_STARTED_AT)
self.assertEqual(result, 0)
return self.root / wave
def write_json(self, path: Path, value: Any) -> None:
@ -87,6 +90,92 @@ class DogfoodOpsTest(unittest.TestCase):
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), 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")
@ -199,7 +288,9 @@ class DogfoodOpsTest(unittest.TestCase):
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 = started_at.isoformat().replace("+00:00", "Z")
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")
@ -238,16 +329,16 @@ class DogfoodOpsTest(unittest.TestCase):
"rcArtifactRef": rc_artifact_ref, "rcArtifactSha256": rc_artifact_hash,
})
self.write_json(wave_dir / "config-identity.json", {
"schema": "dogfood-config-identity/1", "capturedAt": "2026-07-22T00:00:00Z",
"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": "2026-07-22T00:00:00Z"},
"isolatedRestore": {"result": "PASS", "target": "isolated-dogfood-restore-001", "completedAt": "2026-07-22T01:00:00Z", "evidenceRef": restore_ref},
"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": "2026-07-22T00:30:00Z",
"schema": "dogfood-storage-audit/1", "result": "PASS", "capturedAt": captured_at,
"reportRef": storage_ref, "reportSha256": storage_hash,
})
self.write_json(wave_dir / "accounts.json", {
@ -291,7 +382,7 @@ class DogfoodOpsTest(unittest.TestCase):
})
self.write_json(wave_dir / "manual-play.json", {
"schema": "dogfood-manual-play/1", "releasedGameCount": 1,
"games": [{"gameId": "g-1", "tester": "tester-1", "playedAt": "2026-07-22T02:00:00Z", "feedEnteredAt": "2026-07-22T02:10:00Z", "screenshotRef": common_ref, "source": "/gstack@mini-desktop"}],
"games": [{"gameId": "g-1", "tester": "tester-1", "playedAt": played_at, "feedEnteredAt": feed_entered_at, "screenshotRef": common_ref, "source": "/gstack@mini-desktop"}],
})
samples = []
@ -327,7 +418,7 @@ class DogfoodOpsTest(unittest.TestCase):
})
self.write_json(wave_dir / "alerts.json", {
"schema": "dogfood-alert-delivery/1",
"deliveries": [{"alertId": "alert-1", "receivedAt": "2026-07-22T03:00:00Z", "recipient": "oncall-1", "externalMessageId": "msg-1", "evidenceRef": common_ref}],
"deliveries": [{"alertId": "alert-1", "receivedAt": captured_at, "recipient": "oncall-1", "externalMessageId": "msg-1", "evidenceRef": common_ref}],
})
start = started_at
@ -339,19 +430,19 @@ class DogfoodOpsTest(unittest.TestCase):
)
(wave_dir / "feedback.tsv").write_text(
"id\tcreated_at\treporter_id\trole\tcategory\tseverity\tsummary\tstatus\towner\tevidence_ref\n"
f"F-1\t2026-07-22T04:00:00Z\tu-1\tplayer\tux\tP2\tfixture feedback\tCLOSED\tops\t{common_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\t2026-07-22T05:00:00Z\t{common_ref}\n" for item in checklist_items
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": "2026-07-22T06:00:00Z", "operator": "ops-1", "evidenceRef": common_ref} for action in ["stop", "delete", "rollback"]],
"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",
@ -360,7 +451,7 @@ class DogfoodOpsTest(unittest.TestCase):
"acceptance-cost-exceeded",
]
self.write_json(wave_dir / "safety-status.json", {
"schema": "dogfood-safety-status/1", "assessedAt": "2026-07-22T07:00:00Z", "assessor": "ops-1",
"schema": "dogfood-safety-status/1", "assessedAt": captured_at, "assessor": "ops-1",
"conditions": [{"condition": condition, "triggered": False} for condition in stop_conditions],
})
@ -865,6 +956,13 @@ class DogfoodOpsTest(unittest.TestCase):
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")