mirror of
https://github.com/rustfs/rustfs.git
synced 2026-10-13 16:11:56 +00:00
3afb389247
Require Scanner/Heal G03 scoped ACK evidence fields to carry their own measured provenance and concrete ACK, capability, and mixed-peer observations before release-bundle gate verification. Co-authored-by: zhi22915 <qiuzgang@gmail.com>
324 lines
13 KiB
Python
Executable File
324 lines
13 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Assemble measured Scanner/Heal G03 scoped ACK release evidence.
|
|
|
|
This producer consumes one operator-collected measured JSON proof. It packages
|
|
durable root publication, exact scoped ACK identity, peer capability, and
|
|
mixed-peer fallback evidence into the common release-bundle descriptor shape.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
from datetime import datetime, timezone
|
|
import json
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import check_test_wiring as wiring
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
G03_FIELDS = (
|
|
"durable_root_publication_proof",
|
|
"scoped_ack_request_identity",
|
|
"participating_peer_capability_snapshot",
|
|
"mixed_peer_ack_fallback_oracle",
|
|
)
|
|
|
|
|
|
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_proof(path: Path, source_revision: str) -> dict[str, Any]:
|
|
proof = wiring.read_json(path)
|
|
wiring.require(proof.get("schema") == 1, "proof schema must be 1")
|
|
wiring.require(proof.get("evidence_type") == "measured", "proof must be measured")
|
|
for marker in ("fixture", "fixture_only", "dry_run", "synthetic"):
|
|
wiring.require(proof.get(marker) is not True, f"proof is {marker}")
|
|
wiring.require(proof.get("source_revision") == source_revision, "proof source revision mismatch")
|
|
started_at = timestamp(proof.get("started_at"), "proof.started_at")
|
|
finished_at = timestamp(proof.get("finished_at"), "proof.finished_at")
|
|
wiring.require(
|
|
datetime.fromisoformat(finished_at.replace("Z", "+00:00"))
|
|
>= datetime.fromisoformat(started_at.replace("Z", "+00:00")),
|
|
"proof timestamps are inverted",
|
|
)
|
|
wiring.evidence_string(proof.get("run_id"), "proof.run_id", r"[A-Za-z0-9][A-Za-z0-9._:-]{7,127}")
|
|
wiring.evidence_string(
|
|
proof.get("measurement_window_id"),
|
|
"proof.measurement_window_id",
|
|
r"[A-Za-z0-9][A-Za-z0-9._:-]{7,127}",
|
|
)
|
|
wiring.require(proof["measurement_window_id"] != proof["run_id"], "proof must separate run/window identities")
|
|
versions = proof.get("versions")
|
|
wiring.require(
|
|
isinstance(versions, list)
|
|
and len(set(versions)) >= 2
|
|
and all(isinstance(version, str) and re.fullmatch(r"[0-9a-f]{40}", version) for version in versions),
|
|
"proof requires two source revisions",
|
|
)
|
|
wiring.require(source_revision in versions, "proof versions omit tested source revision")
|
|
return proof
|
|
|
|
|
|
def field_from_proof(proof: dict[str, Any], field: str) -> dict[str, Any]:
|
|
value = proof.get(field)
|
|
wiring.require(isinstance(value, dict), f"proof missing {field}")
|
|
for marker in ("fixture", "fixture_only", "dry_run", "synthetic"):
|
|
wiring.require(value.get(marker) is not True, f"{field} is {marker}")
|
|
wiring.require(value.get("evidence_type") == "measured", f"{field} must be measured")
|
|
wiring.require(value.get("source_revision") == proof["source_revision"], f"{field} source revision mismatch")
|
|
wiring.require(value.get("run_id") == proof["run_id"], f"{field} run_id mismatch")
|
|
wiring.require(
|
|
value.get("measurement_window_id") == proof["measurement_window_id"],
|
|
f"{field} measurement window mismatch",
|
|
)
|
|
evidence = {
|
|
**value,
|
|
"versions": proof["versions"],
|
|
"mixed_version_role": wiring.SCANNER_HEAL_RELEASE_MIXED_VERSION_ROLES[("G03", field)],
|
|
}
|
|
wiring.validate_release_bundle_domain_evidence("G03", field, evidence)
|
|
return evidence
|
|
|
|
|
|
def write_field(out_dir: Path, common: dict[str, Any], field: str, field_evidence: dict[str, Any]) -> dict[str, Any]:
|
|
evidence = {
|
|
**field_evidence,
|
|
**common,
|
|
"evidence_type": "measured",
|
|
"summary": field_evidence.get("summary") or f"Measured Scanner/Heal G03 {field} evidence.",
|
|
}
|
|
artifact = out_dir / "artifacts" / f"G03-{field}.json"
|
|
artifact.parent.mkdir(parents=True, exist_ok=True)
|
|
payload = {
|
|
"schema": 1,
|
|
"evidence_type": "measured",
|
|
"source_revision": evidence["source_revision"],
|
|
"run_id": evidence["run_id"],
|
|
"measurement_window_id": evidence["measurement_window_id"],
|
|
"gate": "G03",
|
|
"field": field,
|
|
}
|
|
for key, value in evidence.items():
|
|
if key not in {"artifact", "sha256", "artifact_format", "summary", "started_at", "finished_at", "command"}:
|
|
payload[key] = value
|
|
wiring.write_json(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()
|
|
proof = measured_proof(args.proof_json.resolve(), source_revision)
|
|
out_dir.mkdir(parents=True)
|
|
common = {
|
|
"evidence_type": "measured",
|
|
"source_revision": source_revision,
|
|
"run_id": proof["run_id"],
|
|
"measurement_window_id": proof["measurement_window_id"],
|
|
"started_at": timestamp(proof["started_at"], "proof.started_at"),
|
|
"finished_at": timestamp(proof["finished_at"], "proof.finished_at"),
|
|
"command": [
|
|
"scripts/run_scanner_heal_scoped_ack_evidence.py",
|
|
"--proof-json",
|
|
"<proof-json>",
|
|
],
|
|
}
|
|
descriptor = out_dir / "release-bundle-scoped-ack.json"
|
|
gates = {
|
|
"G03": {
|
|
"status": "pass",
|
|
"lane": "mixed-version-rollback",
|
|
"evidence_type": "measured",
|
|
"evidence_fields": {
|
|
field: write_field(out_dir, common, field, field_from_proof(proof, field))
|
|
for field in G03_FIELDS
|
|
},
|
|
},
|
|
}
|
|
wiring.write_json(descriptor, {
|
|
"schema": 1,
|
|
"evidence": "measured",
|
|
"source_revision": source_revision,
|
|
"gates": gates,
|
|
})
|
|
subprocess.check_call([
|
|
sys.executable,
|
|
str(ROOT / "scripts/check_test_wiring.py"),
|
|
"--check-scanner-heal-release-bundle-gate",
|
|
str(descriptor),
|
|
"G03",
|
|
], cwd=ROOT)
|
|
return descriptor
|
|
|
|
|
|
def write_self_test_proof(path: Path, source_revision: str) -> None:
|
|
now = datetime.now(timezone.utc).replace(microsecond=0)
|
|
run_id = f"scoped-ack-{source_revision[:12]}"
|
|
measurement_window_id = f"scoped-ack-window-{source_revision[:12]}"
|
|
|
|
def field_payload(field: str, values: dict[str, Any]) -> dict[str, Any]:
|
|
return {
|
|
"evidence_type": "measured",
|
|
"source_revision": source_revision,
|
|
"run_id": run_id,
|
|
"measurement_window_id": measurement_window_id,
|
|
"scoped_ack_cases": list(wiring.SCANNER_HEAL_RELEASE_SCOPED_ACK_CASES[field]),
|
|
**{required: True for required in wiring.SCANNER_HEAL_RELEASE_G03_REQUIRED_TRUE_FIELDS[field]},
|
|
**values,
|
|
}
|
|
|
|
proof = {
|
|
"schema": 1,
|
|
"evidence_type": "measured",
|
|
"source_revision": source_revision,
|
|
"run_id": run_id,
|
|
"measurement_window_id": measurement_window_id,
|
|
"started_at": now.isoformat().replace("+00:00", "Z"),
|
|
"finished_at": now.isoformat().replace("+00:00", "Z"),
|
|
"versions": ["a" * 40, source_revision],
|
|
"durable_root_publication_proof": field_payload("durable_root_publication_proof", {}),
|
|
"scoped_ack_request_identity": field_payload("scoped_ack_request_identity", {}),
|
|
"participating_peer_capability_snapshot": field_payload("participating_peer_capability_snapshot", {}),
|
|
"mixed_peer_ack_fallback_oracle": field_payload("mixed_peer_ack_fallback_oracle", {}),
|
|
}
|
|
wiring.write_json(path, proof)
|
|
|
|
|
|
def run_self_test() -> None:
|
|
import tempfile
|
|
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
root = Path(tmp)
|
|
source_revision = git_head()
|
|
proof = root / "scoped-ack-proof.json"
|
|
write_self_test_proof(proof, source_revision)
|
|
descriptor = build_descriptor(parse_args([
|
|
"--proof-json", str(proof),
|
|
"--out-dir", str(root / "out"),
|
|
]))
|
|
wiring.require(descriptor.is_file(), "self-test descriptor missing")
|
|
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
root = Path(tmp)
|
|
source_revision = git_head()
|
|
proof = root / "scoped-ack-proof.json"
|
|
write_self_test_proof(proof, source_revision)
|
|
payload = wiring.read_json(proof)
|
|
payload["scoped_ack_request_identity"]["scoped_ack_cases"].remove("exact-generation")
|
|
wiring.write_json(proof, payload)
|
|
try:
|
|
build_descriptor(parse_args(["--proof-json", str(proof), "--out-dir", str(root / "out")]))
|
|
except ValueError as err:
|
|
wiring.require("missing cases" in str(err), "wrong self-test failure for scoped ACK cases")
|
|
else:
|
|
raise ValueError("self-test accepted incomplete scoped ACK proof")
|
|
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
root = Path(tmp)
|
|
source_revision = git_head()
|
|
proof = root / "scoped-ack-proof.json"
|
|
write_self_test_proof(proof, source_revision)
|
|
payload = wiring.read_json(proof)
|
|
payload["durable_root_publication_proof"]["root_readback_observed"] = False
|
|
wiring.write_json(proof, payload)
|
|
try:
|
|
build_descriptor(parse_args(["--proof-json", str(proof), "--out-dir", str(root / "out")]))
|
|
except ValueError as err:
|
|
wiring.require("root_readback_observed" in str(err), "wrong self-test failure for root readback")
|
|
else:
|
|
raise ValueError("self-test accepted incomplete root publication proof")
|
|
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
root = Path(tmp)
|
|
source_revision = git_head()
|
|
proof = root / "scoped-ack-proof.json"
|
|
write_self_test_proof(proof, source_revision)
|
|
payload = wiring.read_json(proof)
|
|
payload["participating_peer_capability_snapshot"]["source_revision"] = "b" * 40
|
|
wiring.write_json(proof, payload)
|
|
try:
|
|
build_descriptor(parse_args(["--proof-json", str(proof), "--out-dir", str(root / "out")]))
|
|
except ValueError as err:
|
|
wiring.require("source revision mismatch" in str(err), "wrong self-test failure for field source")
|
|
else:
|
|
raise ValueError("self-test accepted stale scoped ACK field source")
|
|
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
root = Path(tmp)
|
|
source_revision = git_head()
|
|
proof = root / "scoped-ack-proof.json"
|
|
write_self_test_proof(proof, source_revision)
|
|
payload = wiring.read_json(proof)
|
|
payload["participating_peer_capability_snapshot"].pop("capability_probe_observed")
|
|
wiring.write_json(proof, payload)
|
|
try:
|
|
build_descriptor(parse_args(["--proof-json", str(proof), "--out-dir", str(root / "out")]))
|
|
except ValueError as err:
|
|
wiring.require("capability_probe_observed" in str(err), "wrong self-test failure for capability proof")
|
|
else:
|
|
raise ValueError("self-test accepted incomplete capability snapshot proof")
|
|
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
root = Path(tmp)
|
|
source_revision = git_head()
|
|
proof = root / "scoped-ack-proof.json"
|
|
write_self_test_proof(proof, source_revision)
|
|
payload = wiring.read_json(proof)
|
|
payload["evidence_type"] = "synthetic"
|
|
wiring.write_json(proof, payload)
|
|
try:
|
|
build_descriptor(parse_args(["--proof-json", str(proof), "--out-dir", str(root / "out")]))
|
|
except ValueError as err:
|
|
wiring.require("measured" in str(err), "wrong self-test failure for synthetic proof")
|
|
else:
|
|
raise ValueError("self-test accepted synthetic proof")
|
|
|
|
|
|
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--proof-json", type=Path)
|
|
parser.add_argument("--out-dir", type=Path)
|
|
parser.add_argument("--source-revision")
|
|
parser.add_argument("--self-test", action="store_true")
|
|
args = parser.parse_args(argv)
|
|
if not args.self_test:
|
|
if args.proof_json is None:
|
|
parser.error("--proof-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"Scoped ACK 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())
|