fix(jobs): bind completed effects to checkpoints

This commit is contained in:
2026-07-12 04:54:00 +02:00
parent c964920f16
commit b6a52995ae
5 changed files with 174 additions and 7 deletions
+15 -1
View File
@@ -15,6 +15,7 @@ _RUN_ID = re.compile(r"^[0-9a-f]{32}$")
_FINGERPRINT = re.compile(r"^sha256:[0-9a-f]{64}$")
JobStatus = Literal["pending", "running", "succeeded", "failed"]
StageStatus = Literal["pending", "running", "succeeded", "failed"]
EffectState = Literal["intent", "completed"]
def utc_now() -> datetime:
@@ -110,13 +111,22 @@ class StageRun(_FrozenModel):
started_at: datetime | None = None
finished_at: datetime | None = None
error: StageError | None = None
effect_state: EffectState | None = None
artifact_manifest_digest: str | None = None
artifact_files: tuple[str, ...] = ()
_name_key = field_validator("name")(_validate_job_key)
_artifact_digest = field_validator("artifact_manifest_digest")(
lambda value: value if value is None or _FINGERPRINT.fullmatch(value) else _invalid_fingerprint()
)
@model_validator(mode="after")
def state_shape(self) -> "StageRun":
if self.status == "pending" and any(
value is not None for value in (self.started_at, self.finished_at, self.error)
value is not None for value in (
self.started_at, self.finished_at, self.error, self.effect_state,
self.artifact_manifest_digest,
)
):
raise ValueError("pending stage cannot contain timestamps or error")
if self.status == "running" and (
@@ -131,6 +141,10 @@ class StageRun(_FrozenModel):
self.started_at is None or self.finished_at is None or self.error is None
):
raise ValueError("failed stage requires timestamps and safe error")
if (self.effect_state is None) != (self.artifact_manifest_digest is None):
raise ValueError("effect state and artifact manifest digest must be persisted together")
if self.artifact_files and self.effect_state is None:
raise ValueError("artifact files require a persisted effect state")
return self