Repository navigation
feat(OMN-2117): Canonical state nodes — session state effect + lifecycle FSM reducer - #300
Conversation
…cle FSM reducer
Implement two new canonical nodes for concurrent pipeline session state management:
**node_session_state_effect (EFFECT)**:
- 5 handlers: SessionIndexRead, SessionIndexWrite, RunContextRead, RunContextWrite, StaleRunGC
- Atomic writes via write-tmp-fsync-rename pattern
- flock-based locking for session.json (concurrent pipeline safety)
- Run context documents are single-writer (no lock needed)
- Stale run GC with configurable TTL (default 4hr)
**node_session_lifecycle_reducer (REDUCER)**:
- Pure FSM: idle → run_created → run_active → run_ended → idle
- Immutable state transitions via with_* methods
- State guard methods (can_create_run, can_activate_run, etc.)
- Idempotent event replay via last_processed_event_id tracking
Models: ModelSessionIndex (session.json), ModelRunContext (runs/{run_id}.json),
ModelSessionLifecycleState (FSM state), ModelSessionStateResult (operation result)
49 new tests covering: model validation, handler I/O, concurrent pipeline
isolation, stale run GC, full FSM lifecycle, idempotent replay
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a session lifecycle enum and reducer plus a contract-driven session-state filesystem effect node: new immutable models, handlers for atomic I/O, locking and GC, DI registries, contracts, validation exemptions and INFRA_MAX_UNIONS bump, with extensive unit tests. Changes
Sequence Diagram(s)sequenceDiagram
participant Caller as Pipeline Caller
participant Reducer as NodeSessionLifecycleReducer
participant State as ModelSessionLifecycleState
Caller->>Reducer: create_run(run_id, event_id)
Reducer->>State: can_create_run()
State-->>Reducer: allowed/denied
alt allowed
Reducer->>State: with_run_created(run_id, event_id)
State-->>Reducer: new state RUN_CREATED
Reducer-->>Caller: updated state
else denied
Reducer-->>Caller: error
end
Caller->>Reducer: activate_run(event_id)
Reducer->>State: can_activate_run()
State-->>Reducer: allowed/denied
alt allowed
Reducer->>State: with_run_activated(event_id)
State-->>Reducer: new state RUN_ACTIVE
Reducer-->>Caller: updated state
else denied
Reducer-->>Caller: error
end
sequenceDiagram
participant Caller as Pipeline Caller
participant Effect as NodeSessionStateEffect
participant Handler as HandlerSessionIndexRead
participant FS as Filesystem (~/.claude/state/)
Caller->>Effect: session.index.read(correlation_id)
Effect->>Handler: handle(correlation_id)
Handler->>FS: stat/read session.json
alt file exists
FS-->>Handler: file contents
Handler->>Handler: parse & validate -> ModelSessionIndex
Handler-->>Effect: ModelSessionIndex + success
else missing
Handler-->>Effect: default ModelSessionIndex + success
end
Effect-->>Caller: ModelSessionStateResult
Estimated code review effort🎯 4 (Complex) | ⏱️ ~55 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
No actionable comments were generated in the recent review. 🎉 Comment |
There was a problem hiding this comment.
Actionable comments posted: 10
🤖 Fix all issues with AI agents
In
`@src/omnibase_infra/nodes/node_session_state_effect/handlers/handler_run_context_read.py`:
- Around line 24-34: The HandlerRunContextRead class is missing the required
handler_type and handler_category properties; import EnumHandlerType and
EnumHandlerTypeCategory and add two `@property` methods on HandlerRunContextRead
named handler_type and handler_category returning the appropriate enums (follow
the same pattern as other handlers such as handler_ledger_projection.py and
handler_postgres_cleanup_topics.py); typically implement handler_type to return
EnumHandlerType.READ and handler_category to return the corresponding category
used for session/state handlers (e.g., EnumHandlerTypeCategory.STATE or the
category used by other read handlers in the codebase).
In
`@src/omnibase_infra/nodes/node_session_state_effect/handlers/handler_run_context_write.py`:
- Around line 27-32: The class HandlerRunContextWrite is missing the mandatory
handler_type and handler_category properties required by the handler contract;
add two `@property` methods on HandlerRunContextWrite named handler_type(self) ->
EnumHandlerType and handler_category(self) -> EnumHandlerTypeCategory that
return the appropriate EnumHandlerType and EnumHandlerTypeCategory values for
this handler (use the enum members that represent run-context write behavior in
your codebase), so registration/routing can discover this handler.
- Around line 94-101: In the OSError except block in handler_run_context_write,
don't embed raw exception text into the returned ModelSessionStateResult.error
or logs; instead import and call the sanitization utility from
omnibase_infra.utils.util_error_sanitization (e.g., sanitize_error_message) on
str(e) and use the sanitized string when populating
ModelSessionStateResult(error=...) and the logger.warning message (keep
context.run_id and correlation_id as before). Ensure you replace direct uses of
e with the sanitized message so no filesystem paths or sensitive data are
returned or logged.
- Around line 56-69: The code uses context.run_id directly to build filesystem
names (run_path and the tempfile prefix in handler_run_context_write), which
allows path traversal; validate or sanitize run_id before using it by enforcing
a strict safe pattern (e.g., UUID or slug regex like ^[A-Za-z0-9_-]+$) or
reject/raise if it fails, and only then construct run_path = runs_dir /
f"{safe_run_id}.json" and use safe_run_id in tempfile.mkstemp prefix;
alternatively, if you must accept arbitrary IDs, hex-encode or base64-url-safe
encode context.run_id into a filesystem-safe token and use that token for both
run_path and tmp file prefixes (apply this check/sanitation in the handler where
run_path and tempfile.mkstemp are created).
In
`@src/omnibase_infra/nodes/node_session_state_effect/handlers/handler_session_index_read.py`:
- Around line 24-29: Add the required handler properties to the
HandlerSessionIndexRead class: implement a handler_type property that returns
EnumHandlerType.READ and a handler_category property that returns
EnumHandlerTypeCategory.STATE (import both EnumHandlerType and
EnumHandlerTypeCategory if not already imported); ensure the properties are
simple `@property` methods on HandlerSessionIndexRead so the handler contract can
register/route the class correctly.
- Around line 80-102: The exception handlers in handler_session_index_read.py
currently embed raw exception text (variable e) into logger messages and the
ModelSessionStateResult.error field; sanitize these strings by calling
omnibase_infra.utils.util_error_sanitization on str(e) (or the error message)
before using them. Specifically update the except blocks catching
(json.JSONDecodeError, ValueError) and OSError: call
util_error_sanitization(e_msg) to produce a safe_error and use safe_error in
logger.warning and in the ModelSessionStateResult(error=...), leaving other
fields (success, operation, correlation_id, error_code, files_affected)
unchanged.
In
`@src/omnibase_infra/nodes/node_session_state_effect/handlers/handler_session_index_write.py`:
- Around line 32-38: HandlerSessionIndexWrite is missing the required
handler_type and handler_category properties required by the handler contract;
add two read-only properties on the class named handler_type and
handler_category that return the appropriate EnumHandlerType and
EnumHandlerTypeCategory values (import them if necessary) so the class exposes
its type and category for registration/routing; ensure the properties are
implemented as simple getters on HandlerSessionIndexWrite and return the correct
enum members consistent with other handlers.
- Around line 109-116: The exception handler in the session index write path
currently embeds the raw OSError text into the ModelSessionStateResult.error,
potentially leaking sensitive paths; update the except OSError as e block in
handler_session_index_write to pass e through the sanitizer in
omnibase_infra.utils.util_error_sanitization (e.g. call the provided sanitize
function) and use the sanitized string when constructing
ModelSessionStateResult(error=...), leaving error_code and other fields
unchanged; ensure you import the sanitizer and apply it to the error message
before populating the error field (you may keep the existing logger.warning but
do not return the raw e).
In
`@src/omnibase_infra/nodes/node_session_state_effect/handlers/handler_stale_run_gc.py`:
- Around line 28-50: The HandlerStaleRunGC class is missing the required
handler_type and handler_category properties used by the handler
registration/routing system; add two read-only `@property` methods on
HandlerStaleRunGC: handler_type() -> EnumHandlerType and handler_category() ->
EnumHandlerTypeCategory that return the correct enum members for this
garbage-collect stale-run handler (choose the appropriate EnumHandlerType and
EnumHandlerTypeCategory values used elsewhere in the codebase), and import
EnumHandlerType and EnumHandlerTypeCategory if not already imported so the class
exposes these properties for the handler contract.
In `@tests/unit/nodes/node_session_lifecycle_reducer/test_state_transitions.py`:
- Around line 8-17: Add a module-level pytest marker so the file is classified
as a unit test: declare pytestmark = pytest.mark.unit at module scope (after the
existing imports) in tests referencing EnumSessionLifecycleState and
ModelSessionLifecycleState; this uses the existing pytest import and ensures all
tests in the module are marked as unit tests.
🧹 Nitpick comments (6)
tests/unit/nodes/node_session_state_effect/test_handlers.py (2)
16-16: Unused import:timezone.The
timezoneimport is not used anywhere in this file; onlyUTCis used.🧹 Proposed fix
-from datetime import UTC, datetime, timezone +from datetime import UTC, datetime
49-50: Consider adding@pytest.mark.unitmarker to test classes.Per coding guidelines, test files should use pytest markers for classification. Adding
@pytest.mark.unitto test classes ensures proper test categorization and filtering.🏷️ Proposed fix example
+@pytest.mark.unit class TestHandlerSessionIndexRead: """Tests for HandlerSessionIndexRead."""Apply similarly to other test classes:
TestHandlerSessionIndexWrite,TestHandlerRunContextRead,TestHandlerRunContextWrite,TestHandlerStaleRunGC,TestConcurrentPipelineIsolation.As per coding guidelines: "Use pytest markers:
@pytest.mark.unit,@pytest.mark.integration,@pytest.mark.slow,@pytest.mark.chaos,@pytest.mark.serialfor test classification".src/omnibase_infra/nodes/node_session_state_effect/models/model_session_index.py (1)
18-18: Unused import:timezone.The
timezoneimport is not used; onlyUTCis referenced throughout the file.🧹 Proposed cleanup
-from datetime import UTC, datetime, timezone +from datetime import UTC, datetimesrc/omnibase_infra/nodes/node_session_state_effect/models/model_run_context.py (2)
12-12: Unused import:timezone.The
timezoneimport is not used; onlyUTCis referenced.🧹 Proposed cleanup
-from datetime import UTC, datetime, timezone +from datetime import UTC, datetime
49-52: Consider immutability implications formetadatafield.The model is frozen, but
dict[str, object]is inherently mutable. While Pydantic prevents field reassignment, external code could still mutate the dict's contents if a reference is held. Thewith_metadatamethod correctly creates a shallow copy, but the copied dict could still contain mutable values.For strict immutability, consider using
MappingProxyTypefor read-only access or documenting that callers must not mutate the dict. Given that the transition helpers follow the pure pattern, the current approach is acceptable for pragmatic use.As per coding guidelines: "use tuple for immutable collections in frozen models."
src/omnibase_infra/nodes/node_session_state_effect/contract.yaml (1)
32-39: Input model may not accurately represent all operations.The
input_modelis declared asModelSessionIndex, but this node handles multiple operations with different input requirements:
session.index.readrequires no inputsession.run.readrequiresrun_idsession.gcrequires no input (or optional TTL)Consider whether the contract schema supports operation-specific input models, or add a clarifying comment that the actual input varies per operation and is documented in
io_operations.
… code hygiene - Fixed: path traversal vulnerability in run_id — added field_validator rejecting '..', '/', '\', '\0' in ModelRunContext.run_id, plus pre-validation in HandlerRunContextRead for raw string parameter - Fixed: blocking flock inside async — all 5 handlers now offload sync I/O to asyncio.to_thread() to avoid blocking the event loop - Fixed: POSIX-only fcntl import — guarded with sys.platform check, raises RuntimeError on Windows at construction time - Fixed: unused timezone imports in 3 model/handler files - Fixed: __all__ alphabetical order in enums/__init__.py - Fixed: misleading YAML comment indentation in validation_exemptions - Fixed: GC handler docstring incorrectly claiming session index cleanup Review iteration: 1/10
- Fixed: unused timezone imports in test_handlers.py and test_models.py - Fixed: missing test coverage for path traversal rejection — added parametrized tests covering ../, /, \, and null byte in both ModelRunContext validator and HandlerRunContextRead handler Review iteration: 2/10
…lock perms - Fixed: handler_session_index_write.py:122 - fd leak if flock(LOCK_UN) raises - Fixed: handler_session_index_write.py:96 - lock file now uses explicit 0o600 mode - Fixed: model_session_index.py:38 - recent_run_ids capped at MAX_RECENT_RUNS=1000 - Fixed: model_session_lifecycle_state.py - with_* methods now enforce FSM preconditions - Fixed: infra_validators.py:455 - comment corrected dict[str, Any] -> dict[str, object] Review iteration: 1/10
…symlink safety, docs - Added path traversal defense-in-depth in HandlerRunContextWrite (parity with read handler) - Added symlink/resolve guard in HandlerStaleRunGC to prevent deletion outside runs dir - Documented intentional lock file persistence in HandlerSessionIndexWrite - Documented metadata dict mutability limitation in ModelRunContext Review iteration: 1/10
…id allowlist, resolve guards - Added max_deletions cap (500) to HandlerStaleRunGC to bound per-pass work - Replaced json.loads(model_dump_json()) with model_dump(mode="json") in both write handlers - Switched run_id validator from denylist to allowlist (alphanumeric + dot/hyphen/underscore) - Added resolve()-based path containment check in HandlerRunContextRead - Aligned handler-level validation to use same allowlist regex - Documented malformed GC deleted_ids as best-effort for callers Review iteration: 2/10
… defensive metadata copy - Fixed FD leak if os.fdopen() raises after tempfile.mkstemp() in both write handlers - Removed exists() gate on resolve() containment check to eliminate TOCTOU gap - Added defensive metadata dict copy in with_status() (parity with with_metadata()) Review iteration: 3/10
…dge-case flakiness - Changed is_stale() from strict > to >= for correct boundary semantics - Prevents potential flakiness when ttl_seconds=0.0 and clock resolution is coarse Review iteration: 4/10
…on catch, metadata docs - Added resolve()-path defense-in-depth check in handler_run_context_write (parity with read handler's two-layer path traversal guard) - Added except-Exception clause in handler_run_context_write and handler_session_index_write to catch non-OSError exceptions and return structured ModelSessionStateResult instead of unhandled tracebacks - Elevated metadata mutability warning to class-level docstring in ModelRunContext Review iteration: 1/10
…d validation, GC symlink guard - Narrow metadata from dict[str, object] to dict[str, StrictJsonPrimitive] (omnibase_core) for lossless JSON round-trip and frozen-model immutability - Use logger.exception() in broad except blocks (run_context_write, session_index_write) to capture full tracebacks for unexpected errors - Return None from session_index_read on parse/IO error to prevent silent overwrite with empty default index - Add field_validator + with_run_added guard on ModelSessionIndex for run_id path safety - Skip symlinks explicitly in stale run GC to prevent stem/data mismatch - Document best-effort semantics of GC deleted_ids in docstring - Reorder path traversal tests before write section; fix malformed JSON assertion
…C stem cross-check - Fixed: model_session_index.py:38 - Added field_validator for active_run_id to apply the same _RUN_ID_PATTERN allowlist, preventing path-traversal via model_validate() from deserialized JSON - Fixed: handler_stale_run_gc.py:121 - Added missing_ok=True to unlink() on stale run path for consistency with malformed file cleanup - Fixed: handler_stale_run_gc.py:122 - Cross-check run_file.stem vs ctx.run_id, log warning and include both in deleted_ids if they differ Review iteration: 1/10
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In
`@src/omnibase_infra/nodes/node_session_lifecycle_reducer/models/model_session_lifecycle_state.py`:
- Around line 89-160: Replace plain ValueError raises in the lifecycle
transition methods (e.g., with_run_created, with_run_activated, with_run_ended,
with_reset) with exceptions created via
ModelInfraErrorContext.with_correlation(...). Use the incoming correlation_id if
available or uuid4() when missing, pass transport_type and operation (use a
clear transport_type like "internal" or the node name and operation equal to the
method name, e.g., "with_run_activated"), and ensure the message is sanitized
via omnibase_infra.utils.util_error_sanitization (sanitize_message) before
putting it into the error context; then raise/return the context-created
exception so the node-layer receives correlation-aware, sanitized errors.
- Around line 167-211: The can_activate_run and can_end_run predicates currently
only check status and should also guard that a valid run_id exists; update
ModelSessionLifecycleState.can_activate_run and can_end_run to return True only
when status == EnumSessionLifecycleState.RUN_CREATED (or RUN_ACTIVE
respectively) AND self.run_id is not None (or use a more strict validation if
run_id must be a UUID), so malformed states with run_id=None cannot transition
to active/ended.
In
`@src/omnibase_infra/nodes/node_session_state_effect/handlers/handler_run_context_read.py`:
- Around line 115-139: Import the sanitizer from
omnibase_infra.utils.util_error_sanitization (e.g., sanitize_error_message) and
use it in the exception handlers in handler_run_context_read.py: call sanitized
= sanitize_error_message(e) and replace uses of the raw exception text in
logger.warning and the ModelSessionStateResult.error fields (the blocks that
create ModelSessionStateResult on JSONDecodeError/ValueError and on OSError) to
include sanitized instead of f"{e}"; keep run_id and correlation_id as-is.
🧹 Nitpick comments (7)
tests/unit/nodes/node_session_state_effect/test_models.py (3)
23-106: Missing@pytest.mark.unitmarker on test class.Per coding guidelines, test files should use pytest markers (
@pytest.mark.unit,@pytest.mark.integration, etc.) for test classification. Add@pytest.mark.unittoTestModelSessionIndexand other test classes in this file.🔧 Suggested fix
+@pytest.mark.unit class TestModelSessionIndex: """Tests for ModelSessionIndex."""
113-170: Test classTestModelRunContextalso needs@pytest.mark.unitmarker.Same issue as above - add the pytest marker for proper test classification.
178-203: Test classTestModelSessionStateResultneeds@pytest.mark.unitmarker.Additionally, the
uuid4import on lines 183 and 194 could be moved to the module-level imports for consistency.🔧 Suggested fix for imports
from datetime import UTC, datetime +from uuid import uuid4 import pytestThen remove the inline
from uuid import uuid4statements inside the test methods.tests/unit/nodes/node_session_state_effect/test_handlers.py (2)
49-86: Missing@pytest.mark.unitmarker on test classes.All test classes in this file (
TestHandlerSessionIndexRead,TestHandlerSessionIndexWrite, etc.) should have@pytest.mark.unitdecorator per coding guidelines for test classification.🔧 Suggested fix example
+@pytest.mark.unit class TestHandlerSessionIndexRead: """Tests for HandlerSessionIndexRead."""
133-148: Good concurrent safety test, but consider adding assertion for data integrity.The test verifies that JSON is valid after concurrent writes, but doesn't assert that the final state is consistent (one complete write won). Consider adding an assertion that
recent_run_idshas exactly one entry matching one of the written values.🔧 Optional enhancement
# File should be valid JSON (one of the writes won) data = json.loads((state_dir / "session.json").read_text()) assert "recent_run_ids" in data + # Verify the final state has exactly one run from one writer + assert len(data["recent_run_ids"]) == 1 + assert data["recent_run_ids"][0] in [f"run-{i}" for i in range(10)]src/omnibase_infra/nodes/node_session_state_effect/handlers/handler_stale_run_gc.py (1)
120-128: Consider logging the file age at debug level for non-stale files.Currently, only stale files get logged. For debugging GC behavior, it might be helpful to log at debug level when files are checked but not deleted.
src/omnibase_infra/nodes/node_session_state_effect/models/model_run_context.py (1)
55-61: Consider documenting the defensive copy behavior for metadata.The
with_statusmethod creates a defensive copy of metadata ({**self.metadata}), which is good practice. However, external code could still mutate the dict after construction if they hold a reference to the original. Since the model is frozen, this is a minor concern, but you might consider usingMappingProxyTypeor documenting this limitation.Also applies to: 104-110
…id validation, TOCTOU fix - Fixed: model_session_index.py:135 - with_run_added() now clears active_run_id when it gets trimmed by MAX_RECENT_RUNS cap - Fixed: handler_session_index_write.py - Added handle_read_modify_write() for atomic read-transform-write under flock, preventing lost-update races when concurrent pipelines modify the session index - Fixed: model_run_context.py - Extracted validate_run_id() and RUN_ID_PATTERN into shared functions, eliminating 4-way duplication across models and handlers - Fixed: handler_session_index_read.py:64 - Replaced exists()+read_text() TOCTOU with try/except FileNotFoundError for consistent default-index behavior when file is deleted mid-read Review iteration: 2/10
…k, read consistency docs - Fixed: test_validator_defaults.py:111 - Updated hardcoded assertion to match INFRA_MAX_UNIONS=121 with threshold history entry - Fixed: handler_stale_run_gc.py:121 - Reordered to append run_id to deleted_ids before unlink, ensuring tracking even if post-delete operations fail unexpectedly - Fixed: handler_session_index_read.py - Added Note section documenting that standalone reads may return pre-transaction state during concurrent handle_read_modify_write() operations Review iteration: 3/10
- Fixed: handler_stale_run_gc.py:147 - Malformed file handler now tracks stem in deleted_ids before unlink, consistent with stale file path - Fixed: handler_session_index_write.py:129 - _read_modify_write_sync now uses try/except FileNotFoundError instead of exists() + read_text() TOCTOU pattern Review iteration: 4/10
…GC robustness Major fixes: - Add validate_run_id() at FSM boundary in with_run_created (defense in depth) - Remove dead failure_reason field from lifecycle state model - RMW read phase catches JSONDecodeError/ValueError, self-heals from corruption - Defensive dict copy in ModelRunContext._freeze_metadata for frozen invariant Minor fixes: - Catch pydantic.ValidationError in run_context_read and stale_run_gc handlers - Sort GC glob by mtime so oldest files are collected first under max_deletions - Pre-resolve runs_dir in run_context_read for consistency with GC handler - Add RUN_ID_PATTERN and validate_run_id to model_run_context __all__ - Fix docstring: "Ordered list" -> "Ordered tuple" in model_session_index - Parametrize all 12 invalid transition tests (was 4) - Document single-ID idempotency limitation in is_duplicate_event - Remove redundant is_terminal from contract.yaml run_ended state - Add flock serialization comment on concurrent RMW test
…ve consistency - Fixed: handler_stale_run_gc.py - max_deletions now counts files not IDs - Fixed: handler_stale_run_gc.py - deleted_ids appended only after unlink succeeds - Fixed: model_run_context.py - _freeze_metadata docstring clarifies shallow copy sufficiency - Fixed: handler_run_context_write.py - resolve() at construction matches read handler - Fixed: handler_session_index_read.py - explicit ValidationError in except tuple - Fixed: model_session_index.py - dedup run IDs on deserialization Review iteration: 1/10
…stead of overcounting - Fixed: handler_stale_run_gc.py - use unlink() + FileNotFoundError catch instead of unlink(missing_ok=True) to avoid incrementing files_deleted when another process concurrently removed the file Review iteration: 2/10
…w sensitivity - Fixed: handler_run_context_read.py - comment now describes structural comparison benefit, not incorrect TOCTOU claim - Fixed: model_run_context.py - is_stale() docstring documents clock-skew sensitivity between writer and GC processes Review iteration: 3/10
…handler docs, defensive-copy test - Fixed: handler_session_index_write.py:136 - Add ValidationError to except clause in RMW path (matching read handler) - Fixed: handler_run_context_write.py:139 - Document intentional catch-all pattern in crash-resilient handlers - Fixed: handler_session_index_write.py:204,290 - Document intentional catch-all pattern - Fixed: handler_stale_run_gc.py:141 - Document deleted_ids vs files_affected divergence in docstring - Fixed: test_models.py - Add test_metadata_defensive_copy verifying _freeze_metadata isolation Review iteration: 1/10
…sistency, empty-dict test - Fixed: model_run_context.py:120 - _freeze_metadata now copies unconditionally (empty-dict edge case) - Fixed: handler_run_context_read.py:138 - OSError handler sets files_affected=1 for consistency - Fixed: handler_run_context_write.py:138 - OSError handler sets files_affected=1 for consistency - Added: test_metadata_defensive_copy_empty_dict verifying empty-dict isolation Review iteration: 2/10
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In
`@src/omnibase_infra/nodes/node_session_state_effect/handlers/handler_session_index_write.py`:
- Around line 66-68: Replace the raw RuntimeError in the
HandlerSessionIndexWrite constructor with a raise that uses
ModelInfraErrorContext.with_correlation(...): call
ModelInfraErrorContext.with_correlation(transport_type=<appropriate transport>,
operation=<appropriate operation>, correlation_id=<generate new id if none>) to
build the context and pass it into the exception so the RuntimeError includes
the required context; specifically update the branch where fcntl is None to
raise using ModelInfraErrorContext.with_correlation and include transport_type
and operation values and an autogenerated correlation id when not provided.
In `@tests/unit/nodes/node_session_state_effect/test_handlers.py`:
- Around line 20-34: This test module is missing the pytest unit marker; add a
module-level pytestmark to classify the tests as unit tests by importing pytest
(if not already) and defining pytestmark = pytest.mark.unit near the top of
tests/unit/nodes/node_session_state_effect/test_handlers.py so the
handlers/models tests (HandlerRunContextRead, HandlerRunContextWrite,
HandlerSessionIndexRead, HandlerSessionIndexWrite, HandlerStaleRunGC,
ModelRunContext, ModelSessionIndex) are marked as unit tests.
In `@tests/unit/nodes/node_session_state_effect/test_models.py`:
- Around line 7-16: The test module is missing a unit test marker; add a
module-level pytest mark by defining pytestmark = pytest.mark.unit at the top of
the file (import pytest if not present) so this test file is classified as unit
tests—update tests/unit/nodes/node_session_state_effect/test_models.py to
include the pytest import and set pytestmark for the module.
🧹 Nitpick comments (1)
src/omnibase_infra/validation/infra_validators.py (1)
455-456: Documentation gap: missing history entry for 121st union.The comment on line 455 documents the 120th union (ModelRunContext.metadata), but
INFRA_MAX_UNIONSis set to 121. The test file (test_validator_defaults.py) documents both additions:
- 120:
ModelRunContext.metadata- 121:
Callable[[ModelSessionIndex], ModelSessionIndex]transform parameterConsider adding a second history note for completeness:
📝 Suggested documentation fix
# Note: OMN-2117 ModelRunContext.metadata uses StrictJsonPrimitive from omnibase_core (120th) +# Note: OMN-2117 SessionIndexRead/Write Callable transform parameter (121st) INFRA_MAX_UNIONS = 121
…tion, test markers, docstrings Address all PR #300 review feedback: - Add handler_type/handler_category properties to all 5 session state handlers - Sanitize error strings via sanitize_error_string() before returning - Use ModelInfraErrorContext.with_correlation() for error context in handler_session_index_write and model_session_lifecycle_state - Validate run_id against path traversal in handler_run_context_write - Guard missing run_id before activation/end transitions - Replace ValueError with RuntimeHostError for state transition errors - Add debug logging for non-stale files in handler_stale_run_gc - Add pytestmark and @pytest.mark.unit to all test files and classes - Add data integrity assertion to concurrent safety test - Add docstrings to key handler classes and methods - Document metadata immutability trade-off and defensive copy pattern - Note input model coverage gap in contract.yaml TODO - Add 121st union history entry in infra_validators.py
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@src/omnibase_infra/nodes/node_session_state_effect/contract.yaml`:
- Around line 31-40: The contract's input_model currently names
ModelSessionIndex but handlers accept varied input shapes (e.g., run_id: str,
ModelRunContext, RMW transform); update the contract to match actual handler
signatures by either (a) defining a union/envelope type (e.g.,
ModelSessionStateInput) that includes ModelSessionIndex, run_id,
ModelRunContext, and transform payloads and set input_model to that union, or
(b) add per-operation input_model entries mapping each operation to its correct
model and remove the TODO; reference the existing input_model declaration and
the handler types (ModelSessionIndex, ModelRunContext, run_id, RMW transform)
when making the change so validation and consumer docs align.
In
`@src/omnibase_infra/nodes/node_session_state_effect/handlers/handler_run_context_read.py`:
- Around line 137-191: The code currently treats a FileNotFoundError from
run_path.read_text() as a generic OSError and returns failure, but the contract
for run_context_read requires missing files to return success=True and
files_affected=0; modify the exception handling around the read/parse block in
handler_run_context_read so you explicitly catch FileNotFoundError (from
run_path.read_text) and return (None, ModelSessionStateResult(success=True,
operation="run_context_read", correlation_id=correlation_id, files_affected=0));
keep the existing JSON/ValidationError handling for parse errors and leave the
broader OSError handler for other I/O errors (ensure FileNotFoundError is
handled before the OSError except block).
…tion, test markers, docstrings - Sanitize error messages in handler_run_context_read/write (remove raw run_id from output) - Handle TOCTOU FileNotFoundError race in handler_run_context_read - Add null byte validation for run_id in handler_run_context_write - Guard can_activate_run/can_end_run against missing run_id in lifecycle state model - Reorder transition guards so run_id-missing error takes precedence - Add per-handler input_model declarations to contract.yaml - Correct 121st union history entry in infra_validators.py - Clean up redundant inline uuid4 imports in test_models.py
Summary
~/.claude/state/idle → run_created → run_active → run_ended → idleEnumSessionLifecycleStateenum and 4 Pydantic models:ModelSessionIndex,ModelRunContext,ModelSessionLifecycleState,ModelSessionStateResultKey Design Decisions
flockonsession.json, no lock needed for per-run documents (single-writer)run_idisstr, notUUID: Used in filesystem paths (runs/{run_id}.json) and for human readabilityTest plan
ruff checkcleanmypycleanCloses OMN-2117
Summary by CodeRabbit
New Features
Reliability
Tests
Chores