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}$") _SHA256 = re.compile(r"^[0-9a-f]{64}$") _DIGEST = re.compile(r"^sha256:[0-9a-f]{64}$") _PREFLIGHT_JOB = re.compile(r"^forge-preflight-[0-9a-f]{16}$") class ForgeBootstrapApplyV1(BaseModel): """Fixed first-install input; callers cannot choose runtime or secret inputs.""" model_config = ConfigDict(extra="forbid", strict=True) api_version: Literal["itworx.forge-first-install/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) preflight_job_id: str = Field(min_length=32, max_length=32) preflight_sha256: str = Field(min_length=64, max_length=64) @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 @field_validator("preflight_job_id") @classmethod def fixed_preflight_job_id(cls, value: str) -> str: if not _PREFLIGHT_JOB.fullmatch(value): raise ValueError("preflight_job_id must reference a fixed Forge preflight job") return value @field_validator("preflight_sha256") @classmethod def fixed_preflight_digest(cls, value: str) -> str: if not _SHA256.fullmatch(value): raise ValueError("preflight_sha256 must be a lowercase SHA-256 digest") return value