Files
rustfs/scripts/run_scanner_heal_authority_evidence.py
T
houseme 8be1e9b2c1 test(scanner): add release bundle evidence descriptors (#7595)
* test(scanner): merge release evidence descriptors

Co-Authored-By: heihutu <heihutu@gmail.com>

Co-Authored-By: zhi22915 <qiuzgang@gmail.com>

* test(scanner): add authority and legacy release descriptors

Co-Authored-By: heihutu <heihutu@gmail.com>

Co-Authored-By: zhi22915 <qiuzgang@gmail.com>

* test(scanner): add scoped ack release descriptor

Co-Authored-By: heihutu <heihutu@gmail.com>

Co-Authored-By: zhi22915 <qiuzgang@gmail.com>

* test(scanner): require all release evidence fields

Co-Authored-By: heihutu <heihutu@gmail.com>

Co-Authored-By: zhi22915 <qiuzgang@gmail.com>

* test(scanner): add mrf release descriptor producer

Add a standalone W13 MRF descriptor producer for measured G07/G08/P4 raw artifacts, with fail-closed self-tests for missing responsibility cases, disk-full ENOSPC observations, synthetic artifacts, and short P4 cleanup soaks.

Also bind G12 quota path evidence to explicit case lists so W16 descriptors cannot pass without reset and settlement coverage.

Co-Authored-By: heihutu <heihutu@gmail.com>

Co-Authored-By: zhi22915 <qiuzgang@gmail.com>

* test(scanner): reuse mrf release descriptor producer

Route the W13 runner through the shared MRF descriptor producer so measured runs and offline re-packaging use the same fail-closed validation.

Co-Authored-By: heihutu <heihutu@gmail.com>

Co-Authored-By: zhi22915 <qiuzgang@gmail.com>

---------

Co-authored-by: zhi22915 <qiuzgang@gmail.com>
2026-09-09 22:55:43 +08:00

287 lines
12 KiB
Python
Executable File

#!/usr/bin/env python3
"""Assemble measured Scanner/Heal G01 authority release evidence.
The producer consumes operator-collected measured JSON. It packages root and
quota authority measurements into the common release-bundle descriptor shape
and lets check_test_wiring.py validate G01 without approving the full release.
"""
from __future__ import annotations
import argparse
from datetime import datetime, timedelta, timezone
import json
import subprocess
import sys
from pathlib import Path
from typing import Any
import check_test_wiring as wiring
ROOT = Path(__file__).resolve().parents[1]
def git_head() -> str:
return subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=ROOT, text=True).strip()
def timestamp(value: Any, name: str) -> str:
wiring.require(isinstance(value, str) and value.strip(), f"missing {name}")
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
wiring.require(parsed.tzinfo is not None, f"{name} must include timezone")
return parsed.isoformat().replace("+00:00", "Z")
def measured_json(path: Path, source_revision: str, label: str) -> dict[str, Any]:
payload = wiring.read_json(path.resolve())
wiring.require(isinstance(payload, dict), f"{label} must be a JSON object")
for marker in ("fixture", "fixture_only", "dry_run", "synthetic"):
wiring.require(payload.get(marker) is not True, f"{label} is {marker}")
wiring.require(payload.get("schema") == 1, f"{label} schema must be 1")
wiring.require(payload.get("evidence_type") == "measured", f"{label} must be measured")
wiring.require(payload.get("source_revision") == source_revision, f"{label} source revision mismatch")
return payload
def common_evidence(args: argparse.Namespace, source_revision: str) -> dict[str, Any]:
duration = wiring.evidence_integer(args.duration_seconds, "duration_seconds", 1, 86400)
started_at = timestamp(args.started_at, "started_at") if args.started_at else datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
if args.finished_at:
finished_at = timestamp(args.finished_at, "finished_at")
else:
started = datetime.fromisoformat(started_at.replace("Z", "+00:00"))
finished_at = (started + timedelta(seconds=duration)).isoformat().replace("+00:00", "Z")
wiring.require(
datetime.fromisoformat(finished_at.replace("Z", "+00:00")) >= datetime.fromisoformat(started_at.replace("Z", "+00:00")),
"authority measurement timestamps are inverted",
)
run_id = args.run_id or f"authority-{source_revision[:12]}"
window_id = args.measurement_window_id or f"authority-window-{source_revision[:12]}"
wiring.evidence_string(run_id, "run_id", r"[A-Za-z0-9][A-Za-z0-9._:-]{7,127}")
wiring.evidence_string(window_id, "measurement_window_id", r"[A-Za-z0-9][A-Za-z0-9._:-]{7,127}")
wiring.require(run_id != window_id, "authority proof must separate run/window identities")
return {
"evidence_type": "measured",
"source_revision": source_revision,
"run_id": run_id,
"measurement_window_id": window_id,
"started_at": started_at,
"finished_at": finished_at,
"command": [
"scripts/run_scanner_heal_authority_evidence.py",
"--root-authority-json",
"<root-authority-json>",
"--quota-authority-json",
"<quota-authority-json>",
],
}
def validate_root_authority(payload: dict[str, Any]) -> None:
wiring.release_bundle_exact_strings(
payload.get("root_authority_cases"),
wiring.SCANNER_HEAL_RELEASE_G01_ROOT_AUTHORITY_CASES,
"root_authority_cases",
)
wiring.release_bundle_bool_true(payload.get("root_cas_observed"), "root_cas_observed")
wiring.release_bundle_bool_true(payload.get("root_readback_observed"), "root_readback_observed")
wiring.release_bundle_bool_true(payload.get("incomplete_root_rejected"), "incomplete_root_rejected")
wiring.release_bundle_bool_true(payload.get("stale_root_rejected"), "stale_root_rejected")
def validate_quota_authority(payload: dict[str, Any]) -> None:
wiring.release_bundle_exact_strings(
payload.get("quota_authority_cases"),
wiring.SCANNER_HEAL_RELEASE_G01_QUOTA_AUTHORITY_CASES,
"quota_authority_cases",
)
wiring.release_bundle_bool_true(payload.get("quota_floor_readback_observed"), "quota_floor_readback_observed")
wiring.release_bundle_bool_true(payload.get("over_limit_put_rejected"), "over_limit_put_rejected")
wiring.release_bundle_bool_true(payload.get("rejected_object_invisible"), "rejected_object_invisible")
wiring.release_bundle_bool_true(payload.get("quota_fails_closed_without_authority"), "quota_fails_closed_without_authority")
def write_field(out_dir: Path, field: str, common: dict[str, Any], payload: dict[str, Any]) -> dict[str, Any]:
evidence = {
**payload,
**common,
"evidence_type": "measured",
"summary": payload.get("summary") or f"Measured Scanner/Heal G01.{field} evidence.",
}
artifact = out_dir / "artifacts" / f"G01-{field}.json"
artifact.parent.mkdir(parents=True, exist_ok=True)
artifact_payload = {
"schema": 1,
"evidence_type": "measured",
"source_revision": evidence["source_revision"],
"run_id": evidence["run_id"],
"measurement_window_id": evidence["measurement_window_id"],
"gate": "G01",
"field": field,
}
for key, value in evidence.items():
if key not in {"artifact", "sha256", "artifact_format", "summary", "started_at", "finished_at", "command"}:
artifact_payload[key] = value
wiring.write_json(artifact, artifact_payload)
evidence["artifact"] = artifact.relative_to(out_dir).as_posix()
evidence["sha256"] = wiring.digest(artifact)
evidence["artifact_format"] = "json"
return evidence
def build_descriptor(args: argparse.Namespace) -> Path:
out_dir = args.out_dir.resolve()
wiring.require(not out_dir.exists(), "output directory must be new")
source_revision = args.source_revision or git_head()
root_authority = measured_json(args.root_authority_json, source_revision, "root authority artifact")
quota_authority = measured_json(args.quota_authority_json, source_revision, "quota authority artifact")
validate_root_authority(root_authority)
validate_quota_authority(quota_authority)
out_dir.mkdir(parents=True)
common = common_evidence(args, source_revision)
fields = {
"root_authority_evidence": write_field(out_dir, "root_authority_evidence", common, root_authority),
"quota_authority_evidence": write_field(out_dir, "quota_authority_evidence", common, quota_authority),
}
descriptor = out_dir / "release-bundle-authority.json"
wiring.write_json(descriptor, {
"schema": 1,
"evidence": "measured",
"source_revision": source_revision,
"gates": {
"G01": {
"status": "pass",
"lane": "authority-coverage",
"evidence_type": "measured",
"evidence_fields": fields,
},
},
})
subprocess.check_call([
sys.executable,
str(ROOT / "scripts/check_test_wiring.py"),
"--check-scanner-heal-release-bundle-gate",
str(descriptor),
"G01",
], cwd=ROOT)
return descriptor
def write_self_test_inputs(root: Path, source_revision: str) -> tuple[Path, Path]:
root_json = root / "root-authority.json"
quota_json = root / "quota-authority.json"
wiring.write_json(root_json, {
"schema": 1,
"evidence_type": "measured",
"source_revision": source_revision,
"root_authority_cases": list(wiring.SCANNER_HEAL_RELEASE_G01_ROOT_AUTHORITY_CASES),
"root_cas_observed": True,
"root_readback_observed": True,
"incomplete_root_rejected": True,
"stale_root_rejected": True,
})
wiring.write_json(quota_json, {
"schema": 1,
"evidence_type": "measured",
"source_revision": source_revision,
"quota_authority_cases": list(wiring.SCANNER_HEAL_RELEASE_G01_QUOTA_AUTHORITY_CASES),
"quota_floor_readback_observed": True,
"over_limit_put_rejected": True,
"rejected_object_invisible": True,
"quota_fails_closed_without_authority": True,
})
return root_json, quota_json
def run_self_test() -> None:
import tempfile
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
source_revision = git_head()
root_json, quota_json = write_self_test_inputs(root, source_revision)
descriptor = build_descriptor(parse_args([
"--root-authority-json", str(root_json),
"--quota-authority-json", str(quota_json),
"--out-dir", str(root / "out"),
"--duration-seconds", "60",
]))
wiring.require(descriptor.is_file(), "self-test descriptor missing")
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
source_revision = git_head()
root_json, quota_json = write_self_test_inputs(root, source_revision)
payload = wiring.read_json(root_json)
payload["synthetic"] = True
wiring.write_json(root_json, payload)
try:
build_descriptor(parse_args([
"--root-authority-json", str(root_json),
"--quota-authority-json", str(quota_json),
"--out-dir", str(root / "out"),
]))
except ValueError as err:
wiring.require("synthetic" in str(err), "wrong self-test failure for synthetic root authority")
else:
raise ValueError("self-test accepted synthetic root authority")
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
source_revision = git_head()
root_json, quota_json = write_self_test_inputs(root, source_revision)
payload = wiring.read_json(quota_json)
payload["quota_authority_cases"] = payload["quota_authority_cases"][:-1]
wiring.write_json(quota_json, payload)
try:
build_descriptor(parse_args([
"--root-authority-json", str(root_json),
"--quota-authority-json", str(quota_json),
"--out-dir", str(root / "out"),
]))
except ValueError as err:
wiring.require("missing cases" in str(err), "wrong self-test failure for incomplete quota authority")
else:
raise ValueError("self-test accepted incomplete quota authority")
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--root-authority-json", type=Path)
parser.add_argument("--quota-authority-json", type=Path)
parser.add_argument("--out-dir", type=Path)
parser.add_argument("--source-revision")
parser.add_argument("--run-id")
parser.add_argument("--measurement-window-id")
parser.add_argument("--started-at")
parser.add_argument("--finished-at")
parser.add_argument("--duration-seconds", type=int, default=60)
parser.add_argument("--self-test", action="store_true")
args = parser.parse_args(argv)
if not args.self_test:
if args.root_authority_json is None:
parser.error("--root-authority-json is required unless --self-test is used")
if args.quota_authority_json is None:
parser.error("--quota-authority-json is required unless --self-test is used")
if args.out_dir is None:
parser.error("--out-dir is required unless --self-test is used")
return args
def main() -> int:
try:
args = parse_args()
if args.self_test:
run_self_test()
return 0
descriptor = build_descriptor(args)
print(f"Authority release descriptor verified: {descriptor}")
return 0
except (ValueError, OSError, json.JSONDecodeError, subprocess.SubprocessError) as err:
print(f"ERROR: {err}", file=sys.stderr)
return 1
if __name__ == "__main__":
sys.exit(main())