from __future__ import annotations import re from typing import Literal from pydantic import BaseModel, ConfigDict, Field, field_validator _REVISION = re.compile(r"^[0-9a-f]{40}$") _DIGEST = re.compile(r"^sha256:[0-9a-f]{64}$") class ForgeBootstrapPreflightV1(BaseModel): """Fixed read-only input for Forge production bootstrap readiness.""" model_config = ConfigDict(extra="forbid", strict=True) api_version: Literal["itworx.forge-bootstrap-preflight/v1"] revision: str = Field(min_length=40, max_length=40) workspace_image_digest: str = Field(min_length=71, max_length=71) frontdoor_image_digest: str = Field(min_length=71, max_length=71) @field_validator("revision") @classmethod def full_lowercase_revision(cls, value: str) -> str: if not _REVISION.fullmatch(value): raise ValueError("revision must be a full lowercase Git SHA") return value @field_validator("workspace_image_digest", "frontdoor_image_digest") @classmethod def immutable_oci_digest(cls, value: str) -> str: if not _DIGEST.fullmatch(value): raise ValueError("image digest must be a lowercase sha256 OCI digest") return value