"""Independently verified check results; never an artifact/build-output cache.""" from __future__ import annotations from copy import deepcopy import threading import re from .common import digest, now from .inputs import InputSnapshotter, FINGERPRINT_VERSION CHECKPOINT_VERSION = 1 def validate_checkpoint_receipt(receipt): """New checkpoints are explicit; legacy status alone never certifies a phase.""" version = receipt.get("fingerprint_version") if version is None: return if version != FINGERPRINT_VERSION: raise ValueError("Unsupported check fingerprint version") for stage in receipt["stages"]: verified = stage.get("checkpoint_verified") if type(verified) is not bool: raise ValueError("Stage checkpoint verification must be boolean") if stage["status"] == "passed" and not verified: raise ValueError( "Passing stage requires an independently verified checkpoint" ) if not verified: continue if ( stage["status"] != "passed" or stage.get("checkpoint_version") != CHECKPOINT_VERSION ): raise ValueError("Invalid verified checkpoint state or version") for field in ( "cache_key", "input_fingerprint", "stage_plan_fingerprint", "log_sha256", ): if not isinstance(stage.get(field), str) or not re.fullmatch( r"[a-f0-9]{64}", stage[field] ): raise ValueError( "Verified checkpoint requires bounded content identities" ) if ( not isinstance(stage.get("checkpoint_at"), str) or not stage["checkpoint_at"] ): raise ValueError("Verified checkpoint requires its recording time") scope = stage.get("input_scope") if ( not isinstance(scope, dict) or scope.get("version") != 1 or scope.get("kind") not in {"workspace", "repositories"} or type(scope.get("declared")) is not bool ): raise ValueError("Verified checkpoint requires a versioned input scope") names = scope.get("repos") if ( not isinstance(names, list) or not 1 <= len(names) <= 256 or any(not isinstance(name, str) for name in names) or len(set(names)) != len(names) ): raise ValueError( "Verified checkpoint requires bounded repository identities" ) class Checkpoints: def __init__(self, project, workspace, plan, environment_probe, cancelled): self.scanner = InputSnapshotter(project, workspace_root=workspace) self.plan = {stage["id"]: stage for stage in plan} self.environment_probe = environment_probe self.cancelled = cancelled self.lock = threading.RLock() self.initial = None self.environment = None def source(self, stages=None): with self.lock: return self.scanner.snapshot( list(self.plan.values()) if stages is None else stages ) def probe_environment(self): value = self.environment_probe() self.check_cancelled() return value def check_cancelled(self): if self.cancelled.is_set(): raise InterruptedError("Check cancelled during input verification") def initialize(self): self.initial = self.source() self.check_cancelled() self.environment = self.probe_environment() return self.initial, self.environment def closure(self, stage): selected = {} def include(item): if item["id"] in selected: return selected[item["id"]] = item for identity in item["deps"]: include(self.plan[identity]) include(self.plan[stage["id"]]) return [selected[key] for key in sorted(selected)] def identity(self, stage): with self.lock: closure = self.closure(stage) snapshot = self.source(closure) self.check_cancelled() environment = self.probe_environment() own = snapshot["stages"][stage["id"]] key = digest( { "checkpoint_version": CHECKPOINT_VERSION, "inputs": { identity: value["fingerprint"] for identity, value in snapshot["stages"].items() }, "environment": environment, } ) return { "checkpoint_version": CHECKPOINT_VERSION, "cache_key": key, "input_fingerprint": own["fingerprint"], "input_scope": own["scope"], "stage_plan_fingerprint": own["plan_fingerprint"], "stage_environment_fingerprint": environment, "dependency_input_fingerprints": { item["id"]: snapshot["stages"][item["id"]]["fingerprint"] for item in closure if item["id"] != stage["id"] }, } def prepare(self, stage, prior, allow_reuse, verify_log): before = self.identity(stage) reason = "No previous verified checkpoint" if stage.get("reuse", "verified") == "never": reason = ( "This stage explicitly disables reuse (outputs/setup must be recreated)" ) elif not allow_reuse: reason = "A data dependency ran again; its consumers must run again" elif ( prior and prior.get("status") == "passed" and prior.get("checkpoint_verified") is True and prior.get("checkpoint_version") == CHECKPOINT_VERSION ): if prior.get("cache_key") == before["cache_key"]: # Receipt command text is never executed. Only the freshly planned # stage runs; a cached log must independently match its content hash. verify_log(prior) result = { **before, **{ key: deepcopy(prior[key]) for key in ( "status", "exit_code", "duration_seconds", "log_path", "log_sha256", "output_truncated", "omitted_output_bytes", "checkpoint_at", ) if key in prior }, } result.update( checkpoint_verified=True, reuse_reason="Verified checkpoint matches current inputs, command, dependencies and environment", ) return before, result reason = ( "Declared inputs, command, dependency inputs or environment changed" ) before["reuse_reason"] = reason return before, None def finish(self, stage, before, result): result = {**result, **before, "checkpoint_verified": False} if result["status"] != "passed" or result.get("exit_code") != 0: return result after = self.identity(stage) if before["cache_key"] != after["cache_key"]: result.update( status="stale", error="Stage inputs or environment changed during execution; no reusable checkpoint was recorded", ) else: result.update(checkpoint_verified=True, checkpoint_at=now()) return result def finalize(self, stages): final = self.source() self.check_cancelled() environment = self.probe_environment() valid = ( final["observed_source_fingerprint"] == self.initial["observed_source_fingerprint"] and environment == self.environment ) for stage in stages: if stage["status"] != "passed": valid = valid and stage["status"] != "stale" continue closure = self.closure(self.plan[stage["id"]]) key = digest( { "checkpoint_version": CHECKPOINT_VERSION, "inputs": { item["id"]: final["stages"][item["id"]]["fingerprint"] for item in closure }, "environment": environment, } ) if ( stage.get("checkpoint_verified") is not True or stage.get("cache_key") != key ): stage.update( status="stale", checkpoint_verified=False, error="Final inputs no longer match this checkpoint", ) valid = False return valid, final, environment