Skip to content

feat(OMN-2143): Checkpoint effect + validate compute nodes - #308

Merged
jonahgabriel merged 11 commits into
mainfrom
jonah/omn-2143-omnibase_infra-p4-checkpoint-nodes-checkpoint-effect
Feb 11, 2026
Merged

jonahgabriel merged 11 commits into
mainfrom
jonah/omn-2143-omnibase_infra-p4-checkpoint-nodes-checkpoint-effect

Conversation

@jonahgabriel

@jonahgabriel jonahgabriel commented Feb 11, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • node_checkpoint_effect (EFFECT): Filesystem I/O for pipeline checkpoint persistence — write, read, and list checkpoint YAML files with append-only semantics (per-attempt files, never overwrite)
  • node_checkpoint_validate_compute (COMPUTE): Pure structural validation of checkpoint data — path normalization, SHA format, phase-payload consistency, schema version checks
  • Shared models: ModelCheckpoint with discriminated union of 5 per-phase payloads (implement, local_review, create_pr, pr_release_ready, ready_for_merge)
  • EnumCheckpointPhase: New enum with phase_number and filename properties for canonical checkpoint file naming

Checkpoint storage layout

~/.claude/checkpoints/{ticket_id}/{run_id}/
    phase_1_implement_a1.yaml
    phase_2_local_review_a1.yaml
    phase_3_create_pr_a1.yaml
    ...

Key design decisions

  • Append-only: Re-runs create new files (_a2.yaml, _a3.yaml) — existing checkpoints are never overwritten
  • Path normalization: Absolute artifact paths are rejected at both write-time (effect handler) and validation-time (compute handler)
  • Phase consistency: model_validator on ModelCheckpoint enforces that the header phase matches the discriminated phase_payload.phase
  • No I/O in COMPUTE: Validate handler is pure — only structural checks, no filesystem access

Files (39 changed, +2685)

Area Files
Enum enum_checkpoint_phase.py + __init__.py export
Shared models 7 files in models/checkpoint/
Effect node 11 files (node, contract, 3 handlers, 2 models, registry)
Compute node 9 files (node, contract, handler, 2 models, registry)
Validation config infra_validators.py (union count bump), validation_exemptions.yaml
Tests 6 files, 22 tests

Test plan

  • 22 unit tests pass (write/read/list round-trips, validation logic, edge cases)
  • Full unit suite passes (7347 passed, 5 skipped, 0 regressions)
  • All 20 pre-commit hooks pass (ruff, mypy, architecture, contract validation)
  • Verify checkpoint write creates expected YAML structure
  • Verify read returns latest attempt when multiple exist
  • Verify validate rejects absolute paths and invalid SHAs

Ticket: OMN-2143

Summary by CodeRabbit

  • New Features

    • Checkpoint system with five pipeline phases, structured per‑phase payloads, append‑only persistence, read‑latest semantics, and listing across runs.
    • Pure‑compute validator for strict, no‑I/O checkpoint validation.
  • Chores

    • Infrastructure validation threshold adjusted; two validation exemptions documented for branch_name and ticket_id.
  • Tests

    • Comprehensive unit tests covering checkpoint write, read, list, and validation behavior.

…ne resilience

Add two ONEX nodes for pipeline checkpoint persistence and validation:

- node_checkpoint_effect (EFFECT): Write/read/list checkpoint YAML files with
  append-only semantics (per-attempt files, never overwrite)
- node_checkpoint_validate_compute (COMPUTE): Pure structural validation of
  checkpoint data (path normalization, SHA format, phase consistency)
- Shared models: ModelCheckpoint with discriminated union of 5 per-phase payloads
- EnumCheckpointPhase: IMPLEMENT → LOCAL_REVIEW → CREATE_PR → PR_RELEASE_READY → READY_FOR_MERGE
- 22 unit tests covering write/read/list round-trips and validation logic
@coderabbitai

coderabbitai Bot commented Feb 11, 2026 •

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Add a checkpoint subsystem: phase enum, immutable checkpoint model with five discriminated phase payloads, filesystem effect node (write/read/list) with handlers and registry, compute-only validation node and handler, validation constant/exemptions updates, utility for attempt extraction, and comprehensive unit tests.

Changes

Cohort / File(s) Summary
Enums
src/omnibase_infra/enums/enum_checkpoint_phase.py, src/omnibase_infra/enums/__init__.py
New EnumCheckpointPhase with stable ordinals, phase_number/filename helpers; re-exported in package __all__.
Core Checkpoint Models
src/omnibase_infra/models/checkpoint/model_checkpoint.py, src/omnibase_infra/models/checkpoint/__init__.py
Add ModelCheckpoint, PhasePayload discriminated union, CHECKPOINT_SCHEMA_VERSION, and validation rules (UTC timestamps, phase consistency, attempt semantics, artifact path and SHA validation); exports added.
Phase Payload Models
src/omnibase_infra/models/checkpoint/model_phase_payload_implement.py, ..._local_review.py, ..._create_pr.py, ..._pr_release_ready.py, ..._ready_for_merge.py
Five frozen Pydantic per‑phase payload models with strict field validators and exports.
Effect Node & Contract
src/omnibase_infra/nodes/node_checkpoint_effect/node.py, src/omnibase_infra/nodes/node_checkpoint_effect/__init__.py, src/omnibase_infra/nodes/node_checkpoint_effect/contract.yaml
Declare NodeCheckpointEffect, add contract for write/read/list operations, routing, and capabilities.
Effect Handlers & Utils
src/omnibase_infra/nodes/node_checkpoint_effect/handlers/handler_checkpoint_write.py, .../handler_checkpoint_read.py, .../handler_checkpoint_list.py, .../handlers/util_checkpoint_sorting.py, .../handlers/__init__.py
Add HandlerCheckpointWrite/Read/List with lifecycle, atomic append‑only YAML writes, safe path handling, parsing, attempt‑number extraction helper, and package exports.
Effect Models & Registry
src/omnibase_infra/nodes/node_checkpoint_effect/models/*, src/omnibase_infra/nodes/node_checkpoint_effect/registry/registry_infra_checkpoint_effect.py, .../registry/__init__.py
Add ModelCheckpointEffectInput/Output, models package init, and RegistryInfraCheckpointEffect DI registry with handler registration and validation.
Validate Compute Node & Contract
src/omnibase_infra/nodes/node_checkpoint_validate_compute/node.py, .../__init__.py, .../contract.yaml
Declare NodeCheckpointValidateCompute and contract for compute-only validate_checkpoint operation.
Validate Handler, Models & Registry
src/omnibase_infra/nodes/node_checkpoint_validate_compute/handlers/handler_checkpoint_validate.py, .../handlers/__init__.py, .../models/*, .../registry/*
Add HandlerCheckpointValidate (structural checks), ModelCheckpointValidateInput/Output, models package init, and RegistryInfraCheckpointValidateCompute DI registry.
Validation infra
src/omnibase_infra/validation/infra_validators.py, src/omnibase_infra/validation/validation_exemptions.yaml
Bump INFRA_MAX_UNIONS from 121→123 and add two validation exemptions for branch_name and ticket_id (entries duplicated in file).
Tests
tests/unit/nodes/test_node_checkpoint_effect/*, tests/unit/nodes/test_node_checkpoint_validate_compute/*
Add comprehensive unit tests for write/read/list handlers and validate handler, fixtures, and test package inits covering edge and error cases.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Write as Handler_Write
    participant Filesystem as Filesystem
    participant Read as Handler_Read
    participant Validate as Handler_Validate

    Note over Client,Validate: Checkpoint lifecycle (write → read → validate)

    Client->>Write: execute(checkpoint)
    Write->>Filesystem: mkdir rgba(100,150,240,0.5) {base_dir}/{ticket_id}/{run_id}
    Write->>Filesystem: write rgba(100,200,120,0.5) phase_<n>_<name>_a{attempt}.yaml (atomic)
    Write-->>Client: checkpoint_path

    Client->>Read: execute(ticket_id, run_id, phase)
    Read->>Filesystem: list rgba(200,120,160,0.5) phase_*.yaml
    Read->>Filesystem: read & parse latest by attempt
    Read-->>Client: ModelCheckpoint

    Client->>Validate: execute(checkpoint)
    Validate->>Validate: run structural checks (schema, phase, shas, paths, timestamp)
    Validate-->>Client: ModelCheckpointValidateOutput (is_valid, errors, warnings)
Loading
sequenceDiagram
    participant Container as DI_Container
    participant Registry as Registry
    participant Handler as Handler

    Container->>Registry: register(protocol metadata)
    Container->>Handler: instantiate with container
    Container->>Registry: register_handler(handler)
    Registry->>Registry: validate handler methods rgba(120,180,240,0.5) (initialize, shutdown, execute)
    Registry-->>Container: stored handler lookup via get_handler()
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 I hopped through phases, one to five,
I wrote the YAML and kept it alive,
I read the latest, chose the right try,
I checked the schema and watched the sky,
A joyful rabbit, checkpoint high!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 73.40% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly identifies the main changes: adding two new checkpoint nodes (effect and validate compute) and references the associated ticket (OMN-2143).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch jonah/omn-2143-omnibase_infra-p4-checkpoint-nodes-checkpoint-effect

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 Fix all issues with AI agents
In
`@src/omnibase_infra/nodes/node_checkpoint_effect/handlers/handler_checkpoint_list.py`:
- Around line 111-117: The UUID conversion of run_id_raw can raise ValueError
and must be guarded: wrap the conversion in a try/except around the block that
sets run_id (the code handling run_id_raw/run_id in handler_checkpoint_list.py),
catch ValueError (and TypeError if desired), and raise a RuntimeHostError using
ModelInfraErrorContext.with_correlation(...) supplying the required
transport_type and operation parameters; include the original exception as the
cause or message so the stack/context is preserved. Ensure you reference
RuntimeHostError and ModelInfraErrorContext.with_correlation when creating the
new exception and replace the direct UUID(...) call with this guarded logic.

In
`@src/omnibase_infra/nodes/node_checkpoint_effect/handlers/handler_checkpoint_read.py`:
- Around line 154-164: Wrap the yaml.safe_load call in a try/except that catches
yaml.YAMLError, and on error raise RuntimeHostError with a
ModelInfraErrorContext created via
ModelInfraErrorContext.with_correlation(correlation_id=context.correlation_id,
transport_type=..., operation=...), including the original YAMLError message in
the RuntimeHostError message and chaining from the caught exception; similarly
ensure the existing ValidationError branch also constructs its context via
ModelInfraErrorContext.with_correlation and passes the transport_type and
operation parameters when raising RuntimeHostError from
ModelCheckpoint.model_validate.
- Around line 136-153: The current lexicographic sort of matching_files can
misorder attempts like "a10" vs "a2"; update the sorting so it extracts the
numeric attempt portion from filenames and sorts numerically: in the handler
(handler_checkpoint_read) where matching_files = sorted(..., key=lambda p:
p.name), replace the key with a function that uses a regex to capture the
attempt number after phase_prefix (or parses the substring), convert it to int
for ordering, and fall back to p.name or 0 if no number is found so the true
latest attempt is selected deterministically.
- Around line 103-114: Wrap the parsing of phase_raw and run_id so that any
ValueError is caught and re-raised as a RuntimeHostError using a
ModelInfraErrorContext created by ModelInfraErrorContext.with_correlation(...,
transport_type=..., operation=...), i.e. around EnumCheckpointPhase(phase_raw)
and UUID(str(run_id)) ensure you catch ValueError and raise
RuntimeHostError(error_message,
context=ModelInfraErrorContext.with_correlation(correlation_id,
transport_type=<appropriate>, operation=<appropriate>)) while keeping the
existing branch logic for when phase_raw is already an EnumCheckpointPhase or
run_id is already a UUID; reference the symbols EnumCheckpointPhase, UUID,
RuntimeHostError, and ModelInfraErrorContext.with_correlation when making the
change.

In
`@src/omnibase_infra/nodes/node_checkpoint_validate_compute/handlers/handler_checkpoint_validate.py`:
- Around line 76-81: The current code sets corr_id to the zero UUID when
envelope["correlation_id"] is missing or not a UUID; instead, update the logic
in handler_checkpoint_validate (around variables correlation_id_raw, corr_id) to
parse correlation_id if it is a UUID instance or a valid UUID string, and
otherwise generate a new UUID using uuid4(); ensure you import uuid4 from uuid
and use the generated uuid for corr_id and include it in all subsequent
error/context logging to preserve traceability.

In
`@tests/unit/nodes/test_node_checkpoint_effect/test_handler_checkpoint_read.py`:
- Around line 46-203: Add the pytest unit marker to each async test in the
TestHandlerCheckpointRead class: decorate test_read_returns_written_checkpoint,
test_read_returns_latest_attempt, test_read_missing_directory_returns_error, and
test_read_missing_phase_returns_error with `@pytest.mark.unit` (import pytest if
not present) so they are classified correctly; keep markers consistent with
coding guidelines (use `@pytest.mark.unit` for these unit tests) and ensure the
decorator is applied directly above each test coroutine definition.

In
`@tests/unit/nodes/test_node_checkpoint_validate_compute/test_handler_checkpoint_validate.py`:
- Around line 116-138: The test named test_phase_payload_mismatch_is_error is
misleading because it constructs a checkpoint with header
EnumCheckpointPhase.LOCAL_REVIEW and a matching ModelPhasePayloadLocalReview and
asserts validity; either rename the test to something like
test_phase_payload_match_passes (update the test function name and any
docstring) or add a new test (e.g., test_phase_payload_mismatch_is_error) that
uses _valid_checkpoint with a mismatched payload (e.g., header
EnumCheckpointPhase.LOCAL_REVIEW but phase_payload set to a different payload
type such as ModelPhasePayloadImplement) and assert result.result.is_valid is
False after calling HandlerCheckpointValidate.initialize and .execute(env);
locate and update the test function name test_phase_payload_mismatch_is_error
and references to _valid_checkpoint, HandlerCheckpointValidate,
EnumCheckpointPhase.LOCAL_REVIEW, and
ModelPhasePayloadLocalReview/ModelPhasePayloadImplement accordingly.
🧹 Nitpick comments (6)
tests/unit/nodes/test_node_checkpoint_validate_compute/test_handler_checkpoint_validate.py (1)

58-59: Add @pytest.mark.unit marker to the test class.

The test class is missing the required pytest marker for test classification. As per coding guidelines, tests should use pytest markers for classification.

Proposed fix
+@pytest.mark.unit
 class TestHandlerCheckpointValidate:
     """Tests for HandlerCheckpointValidate."""
src/omnibase_infra/nodes/node_checkpoint_effect/contract.yaml (1)

22-34: Consider documenting correlation_id and error in output_fields.

The io_operations output_fields don't include correlation_id and error, which are present in ModelCheckpointEffectOutput (as shown in the relevant code snippets). While this may be intentional to show only operation-specific fields, consider adding these common response fields for completeness, or document that they're implicitly included.

Proposed addition
 io_operations:
   - name: "write_checkpoint"
     description: "Write a phase checkpoint YAML to disk (append-only)."
     input_fields: ["checkpoint"]
-    output_fields: ["success", "checkpoint_path"]
+    output_fields: ["success", "correlation_id", "checkpoint_path", "error"]
   - name: "read_checkpoint"
     description: "Read a specific checkpoint from disk."
     input_fields: ["ticket_id", "run_id", "phase"]
-    output_fields: ["success", "checkpoint"]
+    output_fields: ["success", "correlation_id", "checkpoint", "error"]
   - name: "list_checkpoints"
     description: "List available checkpoints for a ticket/run."
     input_fields: ["ticket_id", "run_id"]
-    output_fields: ["success", "checkpoints"]
+    output_fields: ["success", "correlation_id", "checkpoints", "error"]
src/omnibase_infra/models/checkpoint/model_phase_payload_create_pr.py (1)

26-30: Consider adding URL format validation for pr_url.

The pr_url field only enforces min_length=1. Consider adding a regex pattern or using Pydantic's HttpUrl type to validate that the value is a well-formed URL, preventing invalid data from being persisted.

Option 1: Use Pydantic HttpUrl
-from pydantic import BaseModel, ConfigDict, Field
+from pydantic import BaseModel, ConfigDict, Field, HttpUrl
 
 
 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(
+    pr_url: HttpUrl = Field(
         ...,
-        min_length=1,
         description="Full URL of the created pull request.",
     )
Option 2: Add pattern validation
     pr_url: str = Field(
         ...,
         min_length=1,
+        pattern=r"^https?://",
         description="Full URL of the created pull request.",
     )
tests/unit/nodes/test_node_checkpoint_effect/test_handler_checkpoint_write.py (1)

56-57: Add @pytest.mark.unit marker to the test class.

Per coding guidelines, test files should use pytest markers for classification. This test class is missing the @pytest.mark.unit marker.

♻️ Proposed fix
+@pytest.mark.unit
 class TestHandlerCheckpointWrite:
     """Tests for HandlerCheckpointWrite."""

As per coding guidelines: "Use pytest markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.slow, @pytest.mark.chaos, @pytest.mark.serial for test classification"

src/omnibase_infra/nodes/node_checkpoint_effect/handlers/handler_checkpoint_write.py (1)

97-107: Minor redundancy in correlation_id handling.

Line 107 corr_id = context.correlation_id or uuid4() is redundant since ModelInfraErrorContext.with_correlation() already auto-generates a UUID when None is passed (see relevant snippet at lines 66-95 of model_infra_error_context.py). The context.correlation_id will never be None after that call.

♻️ Suggested simplification
         # with_correlation() auto-generates a UUID when None is passed
-        corr_id = context.correlation_id or uuid4()
+        corr_id = context.correlation_id  # Guaranteed non-None by with_correlation()

You may want to add a type assertion if mypy complains:

assert context.correlation_id is not None  # Guaranteed by with_correlation()
corr_id = context.correlation_id
tests/unit/nodes/test_node_checkpoint_effect/test_handler_checkpoint_list.py (1)

51-52: Add @pytest.mark.unit marker to the test class.

Per coding guidelines, test files should use pytest markers for classification.

♻️ Proposed fix
+@pytest.mark.unit
 class TestHandlerCheckpointList:
     """Tests for HandlerCheckpointList."""

As per coding guidelines: "Use pytest markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.slow, @pytest.mark.chaos, @pytest.mark.serial for test classification"

Comment on lines +111 to +117
run_id_raw = envelope.get("run_id")
if run_id_raw is not None:
run_id = (
UUID(str(run_id_raw))
if not isinstance(run_id_raw, UUID)
else run_id_raw
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard invalid run_id parsing with structured RuntimeHostError.

UUID conversion can raise ValueError and should be wrapped with the existing context.

🐛 Proposed fix
        run_id_raw = envelope.get("run_id")
        if run_id_raw is not None:
-            run_id = (
-                UUID(str(run_id_raw))
-                if not isinstance(run_id_raw, UUID)
-                else run_id_raw
-            )
+            try:
+                run_id = (
+                    UUID(str(run_id_raw))
+                    if not isinstance(run_id_raw, UUID)
+                    else run_id_raw
+                )
+            except (ValueError, TypeError) as exc:
+                raise RuntimeHostError(
+                    "list_checkpoints requires a valid run_id UUID",
+                    context=context,
+                ) from exc
As per coding guidelines, Use MANDATORY error context factory ModelInfraErrorContext.with_correlation() for all exception raises with transport_type and operation parameters.
🤖 Prompt for AI Agents
In
`@src/omnibase_infra/nodes/node_checkpoint_effect/handlers/handler_checkpoint_list.py`
around lines 111 - 117, The UUID conversion of run_id_raw can raise ValueError
and must be guarded: wrap the conversion in a try/except around the block that
sets run_id (the code handling run_id_raw/run_id in handler_checkpoint_list.py),
catch ValueError (and TypeError if desired), and raise a RuntimeHostError using
ModelInfraErrorContext.with_correlation(...) supplying the required
transport_type and operation parameters; include the original exception as the
cause or message so the stack/context is preserved. Ensure you reference
RuntimeHostError and ModelInfraErrorContext.with_correlation when creating the
new exception and replace the direct UUID(...) call with this guarded logic.

Comment on lines +103 to +114
# Coerce phase
if isinstance(phase_raw, EnumCheckpointPhase):
phase = phase_raw
elif isinstance(phase_raw, str):
phase = EnumCheckpointPhase(phase_raw)
else:
raise RuntimeHostError(
f"Unexpected phase type: {type(phase_raw).__name__}",
context=context,
)

run_id_val = UUID(str(run_id)) if not isinstance(run_id, UUID) else run_id

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Wrap invalid phase/run_id parsing with structured RuntimeHostError.

EnumCheckpointPhase(...) and UUID(...) can raise ValueError, which currently bubbles without context.

🐛 Proposed fix
        if isinstance(phase_raw, EnumCheckpointPhase):
            phase = phase_raw
        elif isinstance(phase_raw, str):
-            phase = EnumCheckpointPhase(phase_raw)
+            try:
+                phase = EnumCheckpointPhase(phase_raw)
+            except ValueError as exc:
+                raise RuntimeHostError(
+                    f"Unknown phase value: {phase_raw}",
+                    context=context,
+                ) from exc
        else:
            raise RuntimeHostError(
                f"Unexpected phase type: {type(phase_raw).__name__}",
                context=context,
            )

-        run_id_val = UUID(str(run_id)) if not isinstance(run_id, UUID) else run_id
+        try:
+            run_id_val = UUID(str(run_id)) if not isinstance(run_id, UUID) else run_id
+        except (ValueError, TypeError) as exc:
+            raise RuntimeHostError(
+                "read_checkpoint requires a valid run_id UUID",
+                context=context,
+            ) from exc
As per coding guidelines, Use MANDATORY error context factory ModelInfraErrorContext.with_correlation() for all exception raises with transport_type and operation parameters.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Coerce phase
if isinstance(phase_raw, EnumCheckpointPhase):
phase = phase_raw
elif isinstance(phase_raw, str):
phase = EnumCheckpointPhase(phase_raw)
else:
raise RuntimeHostError(
f"Unexpected phase type: {type(phase_raw).__name__}",
context=context,
)
run_id_val = UUID(str(run_id)) if not isinstance(run_id, UUID) else run_id
# Coerce phase
if isinstance(phase_raw, EnumCheckpointPhase):
phase = phase_raw
elif isinstance(phase_raw, str):
try:
phase = EnumCheckpointPhase(phase_raw)
except ValueError as exc:
raise RuntimeHostError(
f"Unknown phase value: {phase_raw}",
context=ModelInfraErrorContext.with_correlation(
correlation_id=..., # propagate from incoming request
transport_type=...,
operation="checkpoint_read"
),
) from exc
else:
raise RuntimeHostError(
f"Unexpected phase type: {type(phase_raw).__name__}",
context=context,
)
try:
run_id_val = UUID(str(run_id)) if not isinstance(run_id, UUID) else run_id
except (ValueError, TypeError) as exc:
raise RuntimeHostError(
"read_checkpoint requires a valid run_id UUID",
context=ModelInfraErrorContext.with_correlation(
correlation_id=...,
transport_type=...,
operation="checkpoint_read"
),
) from exc
🤖 Prompt for AI Agents
In
`@src/omnibase_infra/nodes/node_checkpoint_effect/handlers/handler_checkpoint_read.py`
around lines 103 - 114, Wrap the parsing of phase_raw and run_id so that any
ValueError is caught and re-raised as a RuntimeHostError using a
ModelInfraErrorContext created by ModelInfraErrorContext.with_correlation(...,
transport_type=..., operation=...), i.e. around EnumCheckpointPhase(phase_raw)
and UUID(str(run_id)) ensure you catch ValueError and raise
RuntimeHostError(error_message,
context=ModelInfraErrorContext.with_correlation(correlation_id,
transport_type=<appropriate>, operation=<appropriate>)) while keeping the
existing branch logic for when phase_raw is already an EnumCheckpointPhase or
run_id is already a UUID; reference the symbols EnumCheckpointPhase, UUID,
RuntimeHostError, and ModelInfraErrorContext.with_correlation when making the
change.

Comment on lines +154 to +164
# Read the latest attempt (last file alphabetically = highest attempt)
latest_file = matching_files[-1]
raw_data = yaml.safe_load(latest_file.read_text(encoding="utf-8"))

try:
checkpoint = ModelCheckpoint.model_validate(raw_data)
except ValidationError as exc:
raise RuntimeHostError(
f"Corrupt checkpoint file {latest_file.name}: {exc.error_count()} errors",
context=context,
) from exc

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Handle YAML parse failures with structured errors.

yaml.safe_load can raise YAMLError, which currently escapes without context.

🐛 Proposed fix
-        raw_data = yaml.safe_load(latest_file.read_text(encoding="utf-8"))
+        try:
+            raw_data = yaml.safe_load(latest_file.read_text(encoding="utf-8"))
+        except yaml.YAMLError as exc:
+            raise RuntimeHostError(
+                f"Failed to parse checkpoint YAML {latest_file.name}",
+                context=context,
+            ) from exc
As per coding guidelines, Use MANDATORY error context factory ModelInfraErrorContext.with_correlation() for all exception raises with transport_type and operation parameters.
🤖 Prompt for AI Agents
In
`@src/omnibase_infra/nodes/node_checkpoint_effect/handlers/handler_checkpoint_read.py`
around lines 154 - 164, Wrap the yaml.safe_load call in a try/except that
catches yaml.YAMLError, and on error raise RuntimeHostError with a
ModelInfraErrorContext created via
ModelInfraErrorContext.with_correlation(correlation_id=context.correlation_id,
transport_type=..., operation=...), including the original YAMLError message in
the RuntimeHostError message and chaining from the caught exception; similarly
ensure the existing ValidationError branch also constructs its context via
ModelInfraErrorContext.with_correlation and passes the transport_type and
operation parameters when raising RuntimeHostError from
ModelCheckpoint.model_validate.

Comment on lines +76 to +81
correlation_id_raw = envelope.get("correlation_id")
corr_id = (
correlation_id_raw
if isinstance(correlation_id_raw, UUID)
else UUID("00000000-0000-0000-0000-000000000000")
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Generate/parse correlation_id instead of using the zero UUID.

A missing or non-UUID input should result in a real uuid4() to preserve traceability.

🐛 Proposed fix
        correlation_id_raw = envelope.get("correlation_id")
-        corr_id = (
-            correlation_id_raw
-            if isinstance(correlation_id_raw, UUID)
-            else UUID("00000000-0000-0000-0000-000000000000")
-        )
+        try:
+            if isinstance(correlation_id_raw, UUID):
+                corr_id = correlation_id_raw
+            elif correlation_id_raw:
+                corr_id = UUID(str(correlation_id_raw))
+            else:
+                corr_id = uuid4()
+        except (ValueError, TypeError):
+            corr_id = uuid4()
As per coding guidelines, Propagate correlation_id from incoming requests; auto-generate with uuid4() if missing; include in all error context.
🤖 Prompt for AI Agents
In
`@src/omnibase_infra/nodes/node_checkpoint_validate_compute/handlers/handler_checkpoint_validate.py`
around lines 76 - 81, The current code sets corr_id to the zero UUID when
envelope["correlation_id"] is missing or not a UUID; instead, update the logic
in handler_checkpoint_validate (around variables correlation_id_raw, corr_id) to
parse correlation_id if it is a UUID instance or a valid UUID string, and
otherwise generate a new UUID using uuid4(); ensure you import uuid4 from uuid
and use the generated uuid for corr_id and include it in all subsequent
error/context logging to preserve traceability.

Comment on lines +46 to +203
class TestHandlerCheckpointRead:
"""Tests for HandlerCheckpointRead."""

async def test_read_returns_written_checkpoint(
self,
writer: HandlerCheckpointWrite,
reader: HandlerCheckpointRead,
tmp_path: Path,
) -> None:
"""Round-trip: write then read returns the same checkpoint."""
await writer.initialize({})
await reader.initialize({})

run_id = uuid4()
checkpoint = ModelCheckpoint(
run_id=run_id,
ticket_id="OMN-2143",
phase=EnumCheckpointPhase.IMPLEMENT,
timestamp_utc=datetime.now(UTC),
repo_commit_map={"infra": "deadbeef"},
attempt_number=1,
phase_payload=ModelPhasePayloadImplement(
branch_name="feature-branch",
commit_sha="deadbeef",
files_changed=("src/foo.py",),
),
)

# Write
write_env: dict[str, object] = {
"checkpoint": checkpoint,
"correlation_id": uuid4(),
"base_dir": str(tmp_path),
}
await writer.execute(write_env)

# Read
read_env: dict[str, object] = {
"ticket_id": "OMN-2143",
"run_id": run_id,
"phase": EnumCheckpointPhase.IMPLEMENT,
"correlation_id": uuid4(),
"base_dir": str(tmp_path),
}
result = await reader.execute(read_env)
output = result.result

assert output.success is True
assert output.checkpoint is not None
assert output.checkpoint.ticket_id == "OMN-2143"
assert output.checkpoint.repo_commit_map == {"infra": "deadbeef"}

async def test_read_returns_latest_attempt(
self,
writer: HandlerCheckpointWrite,
reader: HandlerCheckpointRead,
tmp_path: Path,
) -> None:
"""When multiple attempts exist, read returns the highest attempt."""
await writer.initialize({})
await reader.initialize({})

run_id = uuid4()

for attempt in (1, 2, 3):
cp = ModelCheckpoint(
run_id=run_id,
ticket_id="OMN-2143",
phase=EnumCheckpointPhase.LOCAL_REVIEW,
timestamp_utc=datetime.now(UTC),
attempt_number=attempt,
phase_payload={
"phase": "local_review",
"iteration_count": attempt,
"issue_fingerprints": [],
"last_clean_sha": f"sha{attempt:04d}aa",
},
)
write_env: dict[str, object] = {
"checkpoint": cp,
"correlation_id": uuid4(),
"base_dir": str(tmp_path),
}
await writer.execute(write_env)

# Read should return attempt 3
read_env: dict[str, object] = {
"ticket_id": "OMN-2143",
"run_id": run_id,
"phase": EnumCheckpointPhase.LOCAL_REVIEW,
"correlation_id": uuid4(),
"base_dir": str(tmp_path),
}
result = await reader.execute(read_env)
assert result.result.success is True
assert result.result.checkpoint.attempt_number == 3

async def test_read_missing_directory_returns_error(
self,
reader: HandlerCheckpointRead,
tmp_path: Path,
) -> None:
"""Read returns failure when the checkpoint directory does not exist."""
await reader.initialize({})

read_env: dict[str, object] = {
"ticket_id": "OMN-NOEXIST",
"run_id": uuid4(),
"phase": EnumCheckpointPhase.IMPLEMENT,
"correlation_id": uuid4(),
"base_dir": str(tmp_path),
}
result = await reader.execute(read_env)
assert result.result.success is False
assert "not found" in result.result.error

async def test_read_missing_phase_returns_error(
self,
writer: HandlerCheckpointWrite,
reader: HandlerCheckpointRead,
tmp_path: Path,
) -> None:
"""Read returns failure when the requested phase has no checkpoints."""
await writer.initialize({})
await reader.initialize({})

run_id = uuid4()
# Write implement phase
cp = ModelCheckpoint(
run_id=run_id,
ticket_id="OMN-2143",
phase=EnumCheckpointPhase.IMPLEMENT,
timestamp_utc=datetime.now(UTC),
attempt_number=1,
phase_payload=ModelPhasePayloadImplement(
branch_name="branch",
commit_sha="abc1234",
),
)
await writer.execute(
{
"checkpoint": cp,
"correlation_id": uuid4(),
"base_dir": str(tmp_path),
}
)

# Try to read a different phase
read_env: dict[str, object] = {
"ticket_id": "OMN-2143",
"run_id": run_id,
"phase": EnumCheckpointPhase.CREATE_PR,
"correlation_id": uuid4(),
"base_dir": str(tmp_path),
}
result = await reader.execute(read_env)
assert result.result.success is False
assert "No checkpoint found" in result.result.error

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add unit markers so these tests are classified.

These async tests are missing pytest markers, which breaks the required test classification.

✅ Proposed fix
+@pytest.mark.unit
 class TestHandlerCheckpointRead:
As per coding guidelines, Use pytest markers: `@pytest.mark.unit`, `@pytest.mark.integration`, `@pytest.mark.slow`, `@pytest.mark.chaos`, `@pytest.mark.serial` for test classification.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class TestHandlerCheckpointRead:
"""Tests for HandlerCheckpointRead."""
async def test_read_returns_written_checkpoint(
self,
writer: HandlerCheckpointWrite,
reader: HandlerCheckpointRead,
tmp_path: Path,
) -> None:
"""Round-trip: write then read returns the same checkpoint."""
await writer.initialize({})
await reader.initialize({})
run_id = uuid4()
checkpoint = ModelCheckpoint(
run_id=run_id,
ticket_id="OMN-2143",
phase=EnumCheckpointPhase.IMPLEMENT,
timestamp_utc=datetime.now(UTC),
repo_commit_map={"infra": "deadbeef"},
attempt_number=1,
phase_payload=ModelPhasePayloadImplement(
branch_name="feature-branch",
commit_sha="deadbeef",
files_changed=("src/foo.py",),
),
)
# Write
write_env: dict[str, object] = {
"checkpoint": checkpoint,
"correlation_id": uuid4(),
"base_dir": str(tmp_path),
}
await writer.execute(write_env)
# Read
read_env: dict[str, object] = {
"ticket_id": "OMN-2143",
"run_id": run_id,
"phase": EnumCheckpointPhase.IMPLEMENT,
"correlation_id": uuid4(),
"base_dir": str(tmp_path),
}
result = await reader.execute(read_env)
output = result.result
assert output.success is True
assert output.checkpoint is not None
assert output.checkpoint.ticket_id == "OMN-2143"
assert output.checkpoint.repo_commit_map == {"infra": "deadbeef"}
async def test_read_returns_latest_attempt(
self,
writer: HandlerCheckpointWrite,
reader: HandlerCheckpointRead,
tmp_path: Path,
) -> None:
"""When multiple attempts exist, read returns the highest attempt."""
await writer.initialize({})
await reader.initialize({})
run_id = uuid4()
for attempt in (1, 2, 3):
cp = ModelCheckpoint(
run_id=run_id,
ticket_id="OMN-2143",
phase=EnumCheckpointPhase.LOCAL_REVIEW,
timestamp_utc=datetime.now(UTC),
attempt_number=attempt,
phase_payload={
"phase": "local_review",
"iteration_count": attempt,
"issue_fingerprints": [],
"last_clean_sha": f"sha{attempt:04d}aa",
},
)
write_env: dict[str, object] = {
"checkpoint": cp,
"correlation_id": uuid4(),
"base_dir": str(tmp_path),
}
await writer.execute(write_env)
# Read should return attempt 3
read_env: dict[str, object] = {
"ticket_id": "OMN-2143",
"run_id": run_id,
"phase": EnumCheckpointPhase.LOCAL_REVIEW,
"correlation_id": uuid4(),
"base_dir": str(tmp_path),
}
result = await reader.execute(read_env)
assert result.result.success is True
assert result.result.checkpoint.attempt_number == 3
async def test_read_missing_directory_returns_error(
self,
reader: HandlerCheckpointRead,
tmp_path: Path,
) -> None:
"""Read returns failure when the checkpoint directory does not exist."""
await reader.initialize({})
read_env: dict[str, object] = {
"ticket_id": "OMN-NOEXIST",
"run_id": uuid4(),
"phase": EnumCheckpointPhase.IMPLEMENT,
"correlation_id": uuid4(),
"base_dir": str(tmp_path),
}
result = await reader.execute(read_env)
assert result.result.success is False
assert "not found" in result.result.error
async def test_read_missing_phase_returns_error(
self,
writer: HandlerCheckpointWrite,
reader: HandlerCheckpointRead,
tmp_path: Path,
) -> None:
"""Read returns failure when the requested phase has no checkpoints."""
await writer.initialize({})
await reader.initialize({})
run_id = uuid4()
# Write implement phase
cp = ModelCheckpoint(
run_id=run_id,
ticket_id="OMN-2143",
phase=EnumCheckpointPhase.IMPLEMENT,
timestamp_utc=datetime.now(UTC),
attempt_number=1,
phase_payload=ModelPhasePayloadImplement(
branch_name="branch",
commit_sha="abc1234",
),
)
await writer.execute(
{
"checkpoint": cp,
"correlation_id": uuid4(),
"base_dir": str(tmp_path),
}
)
# Try to read a different phase
read_env: dict[str, object] = {
"ticket_id": "OMN-2143",
"run_id": run_id,
"phase": EnumCheckpointPhase.CREATE_PR,
"correlation_id": uuid4(),
"base_dir": str(tmp_path),
}
result = await reader.execute(read_env)
assert result.result.success is False
assert "No checkpoint found" in result.result.error
`@pytest.mark.unit`
class TestHandlerCheckpointRead:
"""Tests for HandlerCheckpointRead."""
async def test_read_returns_written_checkpoint(
self,
writer: HandlerCheckpointWrite,
reader: HandlerCheckpointRead,
tmp_path: Path,
) -> None:
"""Round-trip: write then read returns the same checkpoint."""
await writer.initialize({})
await reader.initialize({})
run_id = uuid4()
checkpoint = ModelCheckpoint(
run_id=run_id,
ticket_id="OMN-2143",
phase=EnumCheckpointPhase.IMPLEMENT,
timestamp_utc=datetime.now(UTC),
repo_commit_map={"infra": "deadbeef"},
attempt_number=1,
phase_payload=ModelPhasePayloadImplement(
branch_name="feature-branch",
commit_sha="deadbeef",
files_changed=("src/foo.py",),
),
)
# Write
write_env: dict[str, object] = {
"checkpoint": checkpoint,
"correlation_id": uuid4(),
"base_dir": str(tmp_path),
}
await writer.execute(write_env)
# Read
read_env: dict[str, object] = {
"ticket_id": "OMN-2143",
"run_id": run_id,
"phase": EnumCheckpointPhase.IMPLEMENT,
"correlation_id": uuid4(),
"base_dir": str(tmp_path),
}
result = await reader.execute(read_env)
output = result.result
assert output.success is True
assert output.checkpoint is not None
assert output.checkpoint.ticket_id == "OMN-2143"
assert output.checkpoint.repo_commit_map == {"infra": "deadbeef"}
async def test_read_returns_latest_attempt(
self,
writer: HandlerCheckpointWrite,
reader: HandlerCheckpointRead,
tmp_path: Path,
) -> None:
"""When multiple attempts exist, read returns the highest attempt."""
await writer.initialize({})
await reader.initialize({})
run_id = uuid4()
for attempt in (1, 2, 3):
cp = ModelCheckpoint(
run_id=run_id,
ticket_id="OMN-2143",
phase=EnumCheckpointPhase.LOCAL_REVIEW,
timestamp_utc=datetime.now(UTC),
attempt_number=attempt,
phase_payload={
"phase": "local_review",
"iteration_count": attempt,
"issue_fingerprints": [],
"last_clean_sha": f"sha{attempt:04d}aa",
},
)
write_env: dict[str, object] = {
"checkpoint": cp,
"correlation_id": uuid4(),
"base_dir": str(tmp_path),
}
await writer.execute(write_env)
# Read should return attempt 3
read_env: dict[str, object] = {
"ticket_id": "OMN-2143",
"run_id": run_id,
"phase": EnumCheckpointPhase.LOCAL_REVIEW,
"correlation_id": uuid4(),
"base_dir": str(tmp_path),
}
result = await reader.execute(read_env)
assert result.result.success is True
assert result.result.checkpoint.attempt_number == 3
async def test_read_missing_directory_returns_error(
self,
reader: HandlerCheckpointRead,
tmp_path: Path,
) -> None:
"""Read returns failure when the checkpoint directory does not exist."""
await reader.initialize({})
read_env: dict[str, object] = {
"ticket_id": "OMN-NOEXIST",
"run_id": uuid4(),
"phase": EnumCheckpointPhase.IMPLEMENT,
"correlation_id": uuid4(),
"base_dir": str(tmp_path),
}
result = await reader.execute(read_env)
assert result.result.success is False
assert "not found" in result.result.error
async def test_read_missing_phase_returns_error(
self,
writer: HandlerCheckpointWrite,
reader: HandlerCheckpointRead,
tmp_path: Path,
) -> None:
"""Read returns failure when the requested phase has no checkpoints."""
await writer.initialize({})
await reader.initialize({})
run_id = uuid4()
# Write implement phase
cp = ModelCheckpoint(
run_id=run_id,
ticket_id="OMN-2143",
phase=EnumCheckpointPhase.IMPLEMENT,
timestamp_utc=datetime.now(UTC),
attempt_number=1,
phase_payload=ModelPhasePayloadImplement(
branch_name="branch",
commit_sha="abc1234",
),
)
await writer.execute(
{
"checkpoint": cp,
"correlation_id": uuid4(),
"base_dir": str(tmp_path),
}
)
# Try to read a different phase
read_env: dict[str, object] = {
"ticket_id": "OMN-2143",
"run_id": run_id,
"phase": EnumCheckpointPhase.CREATE_PR,
"correlation_id": uuid4(),
"base_dir": str(tmp_path),
}
result = await reader.execute(read_env)
assert result.result.success is False
assert "No checkpoint found" in result.result.error
🤖 Prompt for AI Agents
In `@tests/unit/nodes/test_node_checkpoint_effect/test_handler_checkpoint_read.py`
around lines 46 - 203, Add the pytest unit marker to each async test in the
TestHandlerCheckpointRead class: decorate test_read_returns_written_checkpoint,
test_read_returns_latest_attempt, test_read_missing_directory_returns_error, and
test_read_missing_phase_returns_error with `@pytest.mark.unit` (import pytest if
not present) so they are classified correctly; keep markers consistent with
coding guidelines (use `@pytest.mark.unit` for these unit tests) and ensure the
decorator is applied directly above each test coroutine definition.

… handling, dead code

- Write handler now refuses to overwrite existing checkpoints (append-only guard)
- Read handler catches yaml.YAMLError and rejects non-dict YAML content
- List handler skips empty/non-mapping YAML files instead of crashing
- Removed redundant `or uuid4()` fallback (with_correlation guarantees UUID)
- Fixed __all__ alphabetical ordering in enums/__init__.py
- Added handler classification docstring to HandlerCheckpointValidate
- Added 4 new tests covering overwrite guard, invalid YAML, and empty files

Review iteration: 1/10
…t sort

- All 3 effect handlers now validate resolved paths stay under base_dir
  via is_relative_to() to prevent ticket_id path traversal attacks
- Read handler sorts checkpoint files by extracted attempt number instead
  of lexicographic filename order (fixes incorrect result for attempt >= 10)
- Added 3 new tests: path traversal write/read, numeric sort verification

Review iteration: 2/10
…on hardening fixes

- Atomic checkpoint write via tempfile + os.link(O_CREAT-equivalent) prevents
  partial writes on crash and eliminates TOCTOU race on duplicate detection
- timestamp_utc field_validator rejects naive datetimes, normalizes to UTC
- List handler sorts by numeric attempt_number (consistent with read handler)
- Operation field uses Literal type for compile-time validation
- Removed dead naive-datetime branch in validate handler

Review iteration: 1/10
… hex pattern + UTC validator + dedup

- Chain FileExistsError via `from exc` to preserve traceback
- Assert corr_id is not None in all 3 effect handlers (mypy type narrowing)
- Add hex pattern constraint (^[0-9a-f]+$) to SHA fields across all payload models
- Add _ensure_utc validator to ModelPhasePayloadReadyForMerge.label_applied_at
- Extract _attempt_number to module level in read/list handlers (eliminate per-call overhead)
- Fix test fixture using non-hex SHA values

Review iteration: 2/10
…zation, dedup helper

- Replace fragile list().index() with explicit _PHASE_ORDINALS mapping for
  stable checkpoint filenames across enum reordering
- Add symlink escape guard on scan_dirs in list handler
- Log relative_path instead of full absolute path in write handler
- Deduplicate _attempt_number: single definition in list handler, imported by read
- Fix stale "alphabetically" comment in read handler

Review iteration: 3/10

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In
`@src/omnibase_infra/nodes/node_checkpoint_validate_compute/handlers/handler_checkpoint_validate.py`:
- Around line 163-166: The current loop over checkpoint.artifact_paths only
checks PurePosixPath(path_str).is_absolute(), which misses Windows-style
absolute/UNC paths; update the validation in the artifact path loop (the code
referencing checkpoint.artifact_paths and PurePosixPath) to also detect Windows
absolute paths (e.g., by checking PureWindowsPath(path_str).is_absolute() or
using a cross-platform is-absolute test that covers both POSIX and Windows/UNC
forms) and append the same error message when either check is true.

In
`@tests/unit/nodes/test_node_checkpoint_effect/test_handler_checkpoint_list.py`:
- Around line 51-52: The test class TestHandlerCheckpointList is missing the
required pytest marker; add the `@pytest.mark.unit` decorator above the class
definition to classify it as a unit test, and ensure pytest is imported in the
file (e.g., add "import pytest" if not present) so the marker resolves
correctly.

In
`@tests/unit/nodes/test_node_checkpoint_effect/test_handler_checkpoint_write.py`:
- Around line 56-57: Add the pytest unit marker to the test class declaration:
decorate the TestHandlerCheckpointWrite class with `@pytest.mark.unit` (import
pytest if not already imported) so the class reads with the pytest marker above
it; ensure the marker is applied directly to the TestHandlerCheckpointWrite
class definition.

Comment on lines +163 to +166
# 3. Path normalization: no absolute paths in artifact_paths
for path_str in checkpoint.artifact_paths:
if PurePosixPath(path_str).is_absolute():
errors.append(f"Absolute artifact path forbidden: {path_str}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Reject Windows absolute paths as well as POSIX.

Line 163-166 only use PurePosixPath.is_absolute(), which won’t flag Windows-style absolute paths like C:\... or UNC paths. This can bypass the “no absolute paths” guarantee.

🛡️ Suggested hardening
-from pathlib import PurePosixPath
+from pathlib import PurePosixPath, PureWindowsPath
@@
-        for path_str in checkpoint.artifact_paths:
-            if PurePosixPath(path_str).is_absolute():
+        for path_str in checkpoint.artifact_paths:
+            if (
+                PurePosixPath(path_str).is_absolute()
+                or PureWindowsPath(path_str).is_absolute()
+            ):
                 errors.append(f"Absolute artifact path forbidden: {path_str}")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# 3. Path normalization: no absolute paths in artifact_paths
for path_str in checkpoint.artifact_paths:
if PurePosixPath(path_str).is_absolute():
errors.append(f"Absolute artifact path forbidden: {path_str}")
from pathlib import PurePosixPath, PureWindowsPath
# 3. Path normalization: no absolute paths in artifact_paths
for path_str in checkpoint.artifact_paths:
if (
PurePosixPath(path_str).is_absolute()
or PureWindowsPath(path_str).is_absolute()
):
errors.append(f"Absolute artifact path forbidden: {path_str}")
🤖 Prompt for AI Agents
In
`@src/omnibase_infra/nodes/node_checkpoint_validate_compute/handlers/handler_checkpoint_validate.py`
around lines 163 - 166, The current loop over checkpoint.artifact_paths only
checks PurePosixPath(path_str).is_absolute(), which misses Windows-style
absolute/UNC paths; update the validation in the artifact path loop (the code
referencing checkpoint.artifact_paths and PurePosixPath) to also detect Windows
absolute paths (e.g., by checking PureWindowsPath(path_str).is_absolute() or
using a cross-platform is-absolute test that covers both POSIX and Windows/UNC
forms) and append the same error message when either check is true.

Comment on lines +51 to +52
class TestHandlerCheckpointList:
"""Tests for HandlerCheckpointList."""

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add @pytest.mark.unit marker to the test class.

The test class is missing the required pytest marker for test classification.

🔧 Proposed fix
+@pytest.mark.unit
 class TestHandlerCheckpointList:
     """Tests for HandlerCheckpointList."""

As per coding guidelines, Use pytest markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.slow, @pytest.mark.chaos, @pytest.mark.serial for test classification.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class TestHandlerCheckpointList:
"""Tests for HandlerCheckpointList."""
`@pytest.mark.unit`
class TestHandlerCheckpointList:
"""Tests for HandlerCheckpointList."""
🤖 Prompt for AI Agents
In `@tests/unit/nodes/test_node_checkpoint_effect/test_handler_checkpoint_list.py`
around lines 51 - 52, The test class TestHandlerCheckpointList is missing the
required pytest marker; add the `@pytest.mark.unit` decorator above the class
definition to classify it as a unit test, and ensure pytest is imported in the
file (e.g., add "import pytest" if not present) so the marker resolves
correctly.

Comment on lines +56 to +57
class TestHandlerCheckpointWrite:
"""Tests for HandlerCheckpointWrite."""

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add @pytest.mark.unit marker to the test class.

The test class is missing the required pytest marker for test classification.

🔧 Proposed fix
+@pytest.mark.unit
 class TestHandlerCheckpointWrite:
     """Tests for HandlerCheckpointWrite."""

As per coding guidelines, Use pytest markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.slow, @pytest.mark.chaos, @pytest.mark.serial for test classification.

🤖 Prompt for AI Agents
In
`@tests/unit/nodes/test_node_checkpoint_effect/test_handler_checkpoint_write.py`
around lines 56 - 57, Add the pytest unit marker to the test class declaration:
decorate the TestHandlerCheckpointWrite class with `@pytest.mark.unit` (import
pytest if not already imported) so the class reads with the pytest marker above
it; ensure the marker is applied directly to the TestHandlerCheckpointWrite
class definition.

…dators, [minor] assert→raise, uuid4 fallback, glob precision

Major:
- Extract _attempt_number to shared util_checkpoint_sorting.py (was cross-module private import)
- Add field_validator on repo_commit_map to enforce hex SHA format at model level
- Add field_validator on artifact_paths to reject absolute paths and traversal at model level

Minor:
- Replace bare assert with explicit RuntimeError raise in 3 effect handlers
- Add os.link() same-filesystem constraint comment
- Document attempt_number return-0 behavior for non-matching files
- Use uuid4() fallback instead of zero-UUID in validate handler
- Tighten glob pattern to [0-9]* for attempt-number precision
- Update tests to use model_construct() for intentionally-invalid data
…ove unused filename property

- test_phase_payload_mismatch_is_error now creates a real mismatch
  (header=IMPLEMENT, payload.phase=local_review) and asserts Phase mismatch error
- Remove unused EnumCheckpointPhase.filename property (misleading, no attempt suffix)
… counts

Union threshold: 119 + 2 (OMN-2117) + 2 (OMN-2143) = 123
@jonahgabriel
jonahgabriel merged commit b8de097 into main Feb 11, 2026
15 checks passed
@jonahgabriel
jonahgabriel deleted the jonah/omn-2143-omnibase_infra-p4-checkpoint-nodes-checkpoint-effect branch February 11, 2026 22:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant