Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/omnibase_infra/enums/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
from omnibase_infra.enums.enum_capture_outcome import EnumCaptureOutcome
from omnibase_infra.enums.enum_capture_state import EnumCaptureState
from omnibase_infra.enums.enum_chain_violation_type import EnumChainViolationType
from omnibase_infra.enums.enum_checkpoint_phase import EnumCheckpointPhase
from omnibase_infra.enums.enum_circuit_state import EnumCircuitState
from omnibase_infra.enums.enum_confirmation_event_type import EnumConfirmationEventType
from omnibase_infra.enums.enum_consumer_group_purpose import EnumConsumerGroupPurpose
Expand Down Expand Up @@ -117,6 +118,7 @@
"EnumCaptureOutcome",
"EnumCaptureState",
"EnumChainViolationType",
"EnumCheckpointPhase",
"EnumCircuitState",
"EnumConfirmationEventType",
"EnumConsumerGroupPurpose",
Expand Down
68 changes: 68 additions & 0 deletions src/omnibase_infra/enums/enum_checkpoint_phase.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# SPDX-License-Identifier: MIT
# Copyright (c) 2026 OmniNode Team
"""Pipeline checkpoint phases for resume and replay.

Each phase represents a completed step in the ticket pipeline workflow.
Checkpoints are written after each phase completes (never during).

Ticket: OMN-2143
"""

from __future__ import annotations

from enum import Enum, unique


@unique
class EnumCheckpointPhase(str, Enum):
"""Pipeline phase that a checkpoint records.

Attributes:
IMPLEMENT: Code implementation completed.
LOCAL_REVIEW: Local review iterations completed.
CREATE_PR: Pull request created on remote.
PR_RELEASE_READY: PR passed release-ready review.
READY_FOR_MERGE: PR is ready for merge.
"""

IMPLEMENT = "implement"
"""Code implementation phase completed."""

LOCAL_REVIEW = "local_review"
"""Local review iterations completed."""

CREATE_PR = "create_pr"
"""Pull request created on remote."""

PR_RELEASE_READY = "pr_release_ready"
"""PR passed release-ready review."""

READY_FOR_MERGE = "ready_for_merge"
"""PR is ready for merge."""

def __str__(self) -> str:
"""Return the string value for serialization."""
return self.value

@property
def phase_number(self) -> int:
"""Return the stable 1-based ordinal for this phase.

These ordinals are baked into checkpoint filenames on disk
(``phase_{N}_{value}_a{attempt}.yaml``). They MUST NOT change
even if enum members are reordered or new members are inserted.
"""
return _PHASE_ORDINALS[self]


# Explicit mapping — add new phases at the end with the next available ordinal.
_PHASE_ORDINALS: dict[EnumCheckpointPhase, int] = {
EnumCheckpointPhase.IMPLEMENT: 1,
EnumCheckpointPhase.LOCAL_REVIEW: 2,
EnumCheckpointPhase.CREATE_PR: 3,
EnumCheckpointPhase.PR_RELEASE_READY: 4,
EnumCheckpointPhase.READY_FOR_MERGE: 5,
}


__all__: list[str] = ["EnumCheckpointPhase"]
41 changes: 41 additions & 0 deletions src/omnibase_infra/models/checkpoint/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# SPDX-License-Identifier: MIT
# Copyright (c) 2026 OmniNode Team
"""Checkpoint models for pipeline resilience and resume.

Provides the core checkpoint data model with per-phase payloads for the
five-phase ticket pipeline workflow.

Ticket: OMN-2143
"""

from omnibase_infra.models.checkpoint.model_checkpoint import (
CHECKPOINT_SCHEMA_VERSION,
ModelCheckpoint,
PhasePayload,
)
from omnibase_infra.models.checkpoint.model_phase_payload_create_pr import (
ModelPhasePayloadCreatePr,
)
from omnibase_infra.models.checkpoint.model_phase_payload_implement import (
ModelPhasePayloadImplement,
)
from omnibase_infra.models.checkpoint.model_phase_payload_local_review import (
ModelPhasePayloadLocalReview,
)
from omnibase_infra.models.checkpoint.model_phase_payload_pr_release_ready import (
ModelPhasePayloadPrReleaseReady,
)
from omnibase_infra.models.checkpoint.model_phase_payload_ready_for_merge import (
ModelPhasePayloadReadyForMerge,
)

__all__: list[str] = [
"CHECKPOINT_SCHEMA_VERSION",
"ModelCheckpoint",
"ModelPhasePayloadCreatePr",
"ModelPhasePayloadImplement",
"ModelPhasePayloadLocalReview",
"ModelPhasePayloadPrReleaseReady",
"ModelPhasePayloadReadyForMerge",
"PhasePayload",
]
172 changes: 172 additions & 0 deletions src/omnibase_infra/models/checkpoint/model_checkpoint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
# SPDX-License-Identifier: MIT
# Copyright (c) 2026 OmniNode Team
"""Core checkpoint model with common header and discriminated phase payload.

A checkpoint records the successful completion of a pipeline phase. Checkpoints
are append-only: re-runs produce a new checkpoint with an incremented
``attempt_number`` rather than overwriting the previous one.

Invariants:
- Written *after* each phase completes, never during.
- ``artifact_paths`` must contain only relative paths (no absolute machine paths).
- ``repo_commit_map`` keys are relative to ``~/.claude/`` or repo root.

Ticket: OMN-2143
"""

from __future__ import annotations

import re
from datetime import UTC, datetime
from typing import Annotated, Union
from uuid import UUID

from pydantic import (
BaseModel,
ConfigDict,
Discriminator,
Field,
Tag,
field_validator,
model_validator,
)

from omnibase_infra.enums.enum_checkpoint_phase import EnumCheckpointPhase
from omnibase_infra.models.checkpoint.model_phase_payload_create_pr import (
ModelPhasePayloadCreatePr,
)
from omnibase_infra.models.checkpoint.model_phase_payload_implement import (
ModelPhasePayloadImplement,
)
from omnibase_infra.models.checkpoint.model_phase_payload_local_review import (
ModelPhasePayloadLocalReview,
)
from omnibase_infra.models.checkpoint.model_phase_payload_pr_release_ready import (
ModelPhasePayloadPrReleaseReady,
)
from omnibase_infra.models.checkpoint.model_phase_payload_ready_for_merge import (
ModelPhasePayloadReadyForMerge,
)

PhasePayload = Annotated[
Union[
Annotated[ModelPhasePayloadImplement, Tag("implement")],
Annotated[ModelPhasePayloadLocalReview, Tag("local_review")],
Annotated[ModelPhasePayloadCreatePr, Tag("create_pr")],
Annotated[ModelPhasePayloadPrReleaseReady, Tag("pr_release_ready")],
Annotated[ModelPhasePayloadReadyForMerge, Tag("ready_for_merge")],
],
Discriminator("phase"),
]
"""Discriminated union of per-phase payloads, keyed on the ``phase`` literal."""

CHECKPOINT_SCHEMA_VERSION = "1.0.0"
"""Current schema version for checkpoint files."""

_HEX_SHA_RE = re.compile(r"^[0-9a-f]+$")


class ModelCheckpoint(BaseModel):
"""Immutable record of a completed pipeline phase.

The common header fields (``schema_version`` through ``attempt_number``)
are present on every checkpoint. The ``phase_payload`` carries
phase-specific data needed to resume the pipeline from this point.
"""

model_config = ConfigDict(frozen=True, extra="forbid", from_attributes=True)

# ── Common header ────────────────────────────────────────────────
schema_version: str = Field(
default=CHECKPOINT_SCHEMA_VERSION,
description="Forward-compatibility version string.",
)
run_id: UUID = Field(
...,
description="Correlation ID for the entire pipeline run.",
)
ticket_id: str = Field(
...,
min_length=1,
max_length=64,
description="Linear ticket identifier (e.g. OMN-2143).",
)
phase: EnumCheckpointPhase = Field(
...,
description="Which pipeline phase completed.",
)
timestamp_utc: datetime = Field(
...,
description="UTC timestamp when the phase completed.",
)

@field_validator("timestamp_utc", mode="after")
@classmethod
def _ensure_utc(cls, v: datetime) -> datetime:
"""Reject naive datetimes; normalize to UTC."""
if v.tzinfo is None:
msg = "timestamp_utc must be timezone-aware (use datetime.now(UTC))"
raise ValueError(msg)
return v.astimezone(UTC)

repo_commit_map: dict[str, str] = Field(
default_factory=dict,
description="Mapping of relative repo path to commit SHA.",
)

@field_validator("repo_commit_map", mode="after")
@classmethod
def _validate_commit_shas(cls, v: dict[str, str]) -> dict[str, str]:
"""Ensure every value in repo_commit_map is a valid lowercase hex SHA."""
for repo_path, sha in v.items():
if not _HEX_SHA_RE.match(sha):
msg = (
f"repo_commit_map[{repo_path!r}] value {sha!r} is not a "
f"valid hex commit SHA (expected pattern ^[0-9a-f]+$)"
)
raise ValueError(msg)
return v

artifact_paths: tuple[str, ...] = Field(
default_factory=tuple,
description="Relative filesystem paths serving as resume evidence.",
)

@field_validator("artifact_paths", mode="after")
@classmethod
def _validate_artifact_paths(cls, v: tuple[str, ...]) -> tuple[str, ...]:
"""Reject absolute paths and path-traversal segments in artifact_paths."""
for p in v:
if p.startswith("/"):
msg = f"Absolute artifact path forbidden: {p!r}"
raise ValueError(msg)
if ".." in p.split("/"):
msg = f"Path traversal segment '..' forbidden in artifact path: {p!r}"
raise ValueError(msg)
return v

attempt_number: int = Field(
default=1,
ge=1,
description="Monotonically increasing attempt counter (append-only).",
)

# ── Phase-specific payload ───────────────────────────────────────
phase_payload: PhasePayload = Field(
...,
description="Per-phase data required to resume the pipeline.",
)

@model_validator(mode="after")
def _phase_matches_payload(self) -> ModelCheckpoint:
"""Enforce that the header ``phase`` matches ``phase_payload.phase``."""
if self.phase.value != self.phase_payload.phase:
msg = (
f"Header phase '{self.phase.value}' does not match "
f"payload phase '{self.phase_payload.phase}'"
)
raise ValueError(msg)
return self


__all__: list[str] = ["ModelCheckpoint", "PhasePayload", "CHECKPOINT_SCHEMA_VERSION"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# SPDX-License-Identifier: MIT
# Copyright (c) 2026 OmniNode Team
"""Phase payload for the create_pr phase.

Records PR URL, PR number, and head SHA after PR creation.

Ticket: OMN-2143
"""

from __future__ import annotations

from typing import Literal

from pydantic import BaseModel, ConfigDict, Field


class ModelPhasePayloadCreatePr(BaseModel):
"""Payload captured after the create_pr phase completes."""

model_config = ConfigDict(frozen=True, extra="forbid", from_attributes=True)

phase: Literal["create_pr"] = Field(
default="create_pr",
description="Discriminator field for phase payload union.",
)
pr_url: str = Field(
...,
min_length=1,
description="Full URL of the created pull request.",
)
pr_number: int = Field(
...,
ge=1,
description="Pull request number on the remote.",
)
head_sha: str = Field(
...,
min_length=7,
max_length=40,
pattern=r"^[0-9a-f]+$",
description="HEAD SHA pushed to the remote branch.",
)


__all__: list[str] = ["ModelPhasePayloadCreatePr"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# SPDX-License-Identifier: MIT
# Copyright (c) 2026 OmniNode Team
"""Phase payload for the implement phase.

Records branch name, commit SHA, and files changed during implementation.

Ticket: OMN-2143
"""

from __future__ import annotations

from typing import Literal

from pydantic import BaseModel, ConfigDict, Field


class ModelPhasePayloadImplement(BaseModel):
"""Payload captured after the implement phase completes."""

model_config = ConfigDict(frozen=True, extra="forbid", from_attributes=True)

phase: Literal["implement"] = Field(
default="implement",
description="Discriminator field for phase payload union.",
)
branch_name: str = Field(
...,
min_length=1,
max_length=512,
description="Git branch name created for this ticket.",
)
commit_sha: str = Field(
...,
min_length=7,
max_length=40,
pattern=r"^[0-9a-f]+$",
description="HEAD commit SHA after implementation.",
)
files_changed: tuple[str, ...] = Field(
default_factory=tuple,
description="Relative paths of files changed during implementation.",
)


__all__: list[str] = ["ModelPhasePayloadImplement"]
Loading