Skip to content

feat(OMN-14589): canonicalize node_coding_agent_invoke_effect to def-B - #2302

Merged
jonahgabriel merged 3 commits into
devfrom
jonah/omn-14589-canonicalize-node_coding_agent_invoke_effect
Jul 14, 2026
Merged

jonahgabriel merged 3 commits into
devfrom
jonah/omn-14589-canonicalize-node_coding_agent_invoke_effect

Conversation

@jonahgabriel

@jonahgabriel jonahgabriel commented Jul 13, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

RSD mechanical-wave canary (path (b)): flips HandlerCodingAgentInvoke.handle()
from the def-A envelope-wrapping shape
(handle(envelope: ModelEventEnvelope[T]) -> ModelHandlerOutput[None]) to the
canonical def-B shape (handle(command: ModelCodingAgentInvokeCommand) -> ModelCodingAgentResult), per the OMN-14355 canonical handler-shape ratchet.

  • The contract already declares the typed event_model
    (ModelCodingAgentInvokeCommand); the runtime's _make_dispatch_callback
    (omnibase_infra/runtime/auto_wiring/handler_wiring.py) already validates and
    passes the typed payload directly once the handler no longer accepts an
    envelope-shaped parameter (_handler_accepts_event_envelope inspects the
    signature).
  • DispatchResultApplier._publish_payload_for_output_event already unwraps an
    envelope-shaped output event to its inner payload before publish — so
    returning the bare ModelCodingAgentResult is byte-for-byte
    behavior-identical to the prior inner-envelope-wrapping form on the real
    dispatch path. Confirmed by the existing
    test_effect_result_publishes_to_invoke_completed_with_result_payload,
    unmodified, still green.

Proof (canon-shape ratchet 3-part flip gate: canonical_handler_shape.py::verify_flip_receipt)

  • RED -> GREEN: classify_node() on the pre-change file =
    envelope_in_core (non-canonical); on the post-change file = canonical
    (magic:command).
  • Adequacy receipt (scripts/ci/adequacy_receipts/omnibase_infra.nodes.node_coding_agent_invoke_effect.json):
    85.45% branch coverage of the handler module, measured from the node's
    existing 37-test suite (golden chain + subprocess/git probes + real dispatch
    multitopic).
  • Equivalence: 7 scenarios replayed through both the pre-flip
    envelope-wrapping handle() body and the post-flip handle(command),
    confirmed byte-equivalent (duration_ms masked as volatile); recorded as
    goldens under tests/fixtures/golden/node_coding_agent_invoke_effect/ with a
    durable replay regression test
    (test_invoke_effect_golden_equivalence.py) and the paired
    .equivalence.json artifact (same selected_input_hashes as the receipt).
    verify_flip_receipt() returns True end-to-end against these artifacts.

Vendors scripts/ci/adequacy_receipt.py + compute_golden.py (per the
omnimarket OMN-14371 canary's per-repo convention: the recorder/comparator
libraries are vendored locally per-repo; the classifier script itself stays
centralized in omnibase_core).

Scope note — node_coding_agent_fsm_reducer NOT converted here

The ticket's second target (node_coding_agent_fsm_reducer) is intentionally
left unconverted. Its handle() currently returns two reducer projections
(FSM state + trace projection) via ModelHandlerOutput.for_reducer(...).
Tracing the live runtime wiring (_make_dispatch_callback /
_normalize_handler_result in runtime/auto_wiring/handler_wiring.py) shows
no code path reads ModelHandlerOutput.projections or constructs a
ModelProjectionIntent from a bare typed return — so the ticket's assumed
"adapter wraps a plain return into for_reducer(projections=(...))" does not
exist in the current runtime. Converting to a single-model return per the
ticket's literal target snippet would either silently drop the trace
projection or require inventing new adapter behavior outside this ticket's
declared mechanical scope. Flagged to the team lead rather than forced through
— see OMN-14589 for follow-up.

Depends on / should be reconciled with OMN-14587 (#2300), which freezes the
omnibase_infra canon-shape baseline including this node as non-canonical
pre-flip; once both land, the baseline's NON_CANONICAL tuple should drop this
node's entry (via --update or a one-line removal) to record the proven flip.

Test plan

  • uv run pytest tests/unit/nodes/node_coding_agent/ -v -m unit — 57 passed
  • uv run ruff format --check / ruff check / mypy — clean
  • pre-commit run --all-files (targeted) — all hooks passed
  • verify_flip_receipt() (canonical_handler_shape.py) returns
    (True, "recomputed-meets-target+equivalence-pass") against the
    committed receipt + equivalence artifact
  • Golden-equivalence replay test proves handle() output is unchanged
    across 7 scenarios (success x2, timeout, subprocess error, empty
    response, unavailable x2)

Evidence-Source: OCC#4132
Evidence-Ticket: OMN-14589
Evidence-Commit: b5963486f45fe6287bdb633b25e2a44b1955a58a

Summary by CodeRabbit

  • New Features

    • Added golden-output recording and comparison for coding-agent invocations.
    • Added coverage-guided adequacy receipts with deterministic input selection and waiver tracking.
    • Added documented canonical handler behavior and updated contract metadata.
  • Bug Fixes

    • Simplified coding-agent invocation handling to return typed results directly.
    • Improved validation of success, timeout, subprocess-error, and unavailable-agent outcomes.
  • Tests

    • Added golden fixtures and equivalence coverage for common invocation scenarios.

Evidence-Superseded-Product-Head: 1011fcb
Evidence-Current-Product-Head: 6477d6a

Evidence-Superseded-Product-Head: cc51e48

RSD mechanical-wave canary (path (b), OMN-14355/14371 pattern): flip
HandlerCodingAgentInvoke.handle() from the def-A envelope-wrapping shape
(handle(envelope: ModelEventEnvelope[T]) -> ModelHandlerOutput[None]) to the
canonical def-B shape (handle(command: ModelCodingAgentInvokeCommand) ->
ModelCodingAgentResult). The contract already declares the typed event_model;
the runtime's _make_dispatch_callback already validates and passes the typed
payload directly once the handler no longer accepts an envelope-shaped
parameter, and DispatchResultApplier already unwraps an envelope-shaped output
event to its inner payload before publish — so this is byte-for-byte
behavior-identical on the real dispatch path (confirmed by the existing
test_effect_result_publishes_to_invoke_completed_with_result_payload,
unmodified, still green).

Proof (per the canon-shape ratchet's 3-part flip gate,
scripts/ci/canonical_handler_shape.py::verify_flip_receipt):
- RED->GREEN: classify_node() on the pre-change file = envelope_in_core
  (non-canonical); on the post-change file = canonical (magic:command).
- Adequacy receipt (scripts/ci/adequacy_receipts/*.json): 85.45% branch
  coverage of the handler module, measured from the node's existing 37-test
  suite (golden chain + subprocess/git probes + real dispatch multitopic).
- Equivalence: 7 scenarios replayed through both the pre-flip envelope-wrapping
  handle() body and the post-flip handle(command), confirmed byte-equivalent
  (duration_ms masked); recorded as goldens under
  tests/fixtures/golden/node_coding_agent_invoke_effect/ with a durable
  replay regression test (test_invoke_effect_golden_equivalence.py) and the
  paired .equivalence.json artifact (same selected_input_hashes as the
  receipt). verify_flip_receipt() returns True end-to-end against these.

Vendors scripts/ci/adequacy_receipt.py + compute_golden.py (per the omnimarket
OMN-14371 canary's per-repo convention — the recorder/comparator libraries are
vendored locally, the classifier script itself stays centralized in
omnibase_core).

Scope note: node_coding_agent_fsm_reducer (the ticket's second target) is
NOT converted here. Its handle() currently returns TWO reducer projections
(FSM state + trace projection) via ModelHandlerOutput.for_reducer(...); tracing
the live runtime wiring (_make_dispatch_callback/_normalize_handler_result in
auto_wiring/handler_wiring.py) shows no code path reads
ModelHandlerOutput.projections or constructs ModelProjectionIntent from a
bare typed return, so the ticket's assumed adapter-side reconstruction of
for_reducer(projections=(...)) from a single typed return does not exist yet.
Converting to a single-model return would either silently drop the trace
projection or require inventing new adapter behavior outside this ticket's
mechanical scope. Flagged to the team lead rather than forced.

Evidence: OMN-14589.
@coderabbitai

coderabbitai Bot commented Jul 13, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds CI utilities for deterministic golden comparisons and coverage adequacy receipts, updates the coding-agent handler to the canonical typed contract, records adequacy and equivalence artifacts, and adds scenario fixtures with handler equivalence tests.

Changes

Canonical compute-node validation

Layer / File(s) Summary
Golden recording and comparison utilities
scripts/ci/compute_golden.py
Adds canonical JSON serialization, volatile-field masking, structural diffing, golden recording, and output-equivalence helpers.
Coverage adequacy receipt generation
scripts/ci/adequacy_receipt.py, scripts/ci/adequacy_receipts/*, scripts/ci/__init__.py
Adds validated coverage receipts, deterministic branch-covering input selection, provenance hashing, redaction, waiver handling, and recorded node receipts.
Canonical typed handler contract
src/omnibase_infra/nodes/node_coding_agent_invoke_effect/contract.yaml, src/omnibase_infra/nodes/node_coding_agent_invoke_effect/handlers/handler_coding_agent_invoke.py
Documents the DEF-B handler shape and changes handle to accept ModelCodingAgentInvokeCommand and return ModelCodingAgentResult.
Golden fixtures and handler equivalence tests
tests/fixtures/golden/node_coding_agent_invoke_effect/*, tests/unit/nodes/node_coding_agent/test_invoke_effect_golden_equivalence.py, tests/unit/nodes/node_coding_agent/test_golden_chain_coding_agent.py
Adds seven recorded scenarios and validates that mocked handler results reproduce their golden outputs under the volatile-field mask.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GoldenFixture
  participant HandlerCodingAgentInvoke
  participant ModelCodingAgentResult
  participant compare_output
  GoldenFixture->>HandlerCodingAgentInvoke: build command and call handle(command)
  HandlerCodingAgentInvoke->>ModelCodingAgentResult: return typed result
  GoldenFixture->>compare_output: compare recorded output with fresh result
  compare_output-->>GoldenFixture: masked structural differences
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.45% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: canonicalizing node_coding_agent_invoke_effect to the def-B handler shape.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jonah/omn-14589-canonicalize-node_coding_agent_invoke_effect

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

@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: 1

🧹 Nitpick comments (1)
scripts/ci/compute_golden.py (1)

35-45: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Deep copy via JSON round-trip works but is indirect.

json.loads(json.dumps(payload)) is a valid deep-copy for JSON-safe dicts, but copy.deepcopy(payload) is more direct, avoids re-serialization cost, and doesn't risk surprises with non-JSON-native types if apply_mask is ever reused on non-model_dump(mode="json") payloads.

♻️ Optional simplification
+import copy
+
 def apply_mask(payload: dict[str, Any], volatile_mask: list[str]) -> dict[str, Any]:
     """Return a deep copy of ``payload`` with each dotted-path volatile field removed.
     ...
     """
-    masked: dict[str, Any] = json.loads(json.dumps(payload))  # deep copy via round-trip
+    masked: dict[str, Any] = copy.deepcopy(payload)
     for dotted in volatile_mask:
         _remove_path(masked, dotted.split("."))
     return masked
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/ci/compute_golden.py` around lines 35 - 45, The deep-copy
implementation in apply_mask should use copy.deepcopy(payload) instead of the
JSON serialization round-trip. Add or reuse the standard copy module import,
preserve the existing dict shape and _remove_path processing, and avoid changing
masking behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/ci/adequacy_receipt.py`:
- Around line 259-270: Update the ModelUncoveredWaiver construction in the
coverage-miss path to set uncovered_branch_count from the actual missing-branch
total reported by the coverage data, replacing the hardcoded 0. Preserve the
existing waiver reason and behavior for coverage targets that are met.

---

Nitpick comments:
In `@scripts/ci/compute_golden.py`:
- Around line 35-45: The deep-copy implementation in apply_mask should use
copy.deepcopy(payload) instead of the JSON serialization round-trip. Add or
reuse the standard copy module import, preserve the existing dict shape and
_remove_path processing, and avoid changing masking behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9810cc4d-4f26-49ef-8961-1ac046f5c0ba

📥 Commits

Reviewing files that changed from the base of the PR and between 8fc42a9 and cc51e48.

📒 Files selected for processing (16)
  • scripts/ci/__init__.py
  • scripts/ci/adequacy_receipt.py
  • scripts/ci/adequacy_receipts/omnibase_infra.nodes.node_coding_agent_invoke_effect.equivalence.json
  • scripts/ci/adequacy_receipts/omnibase_infra.nodes.node_coding_agent_invoke_effect.json
  • scripts/ci/compute_golden.py
  • src/omnibase_infra/nodes/node_coding_agent_invoke_effect/contract.yaml
  • src/omnibase_infra/nodes/node_coding_agent_invoke_effect/handlers/handler_coding_agent_invoke.py
  • tests/fixtures/golden/node_coding_agent_invoke_effect/empty_response.json
  • tests/fixtures/golden/node_coding_agent_invoke_effect/subprocess_error.json
  • tests/fixtures/golden/node_coding_agent_invoke_effect/success_claude_read_only.json
  • tests/fixtures/golden/node_coding_agent_invoke_effect/success_codex_workspace_write.json
  • tests/fixtures/golden/node_coding_agent_invoke_effect/timeout.json
  • tests/fixtures/golden/node_coding_agent_invoke_effect/unavailable_binary_missing.json
  • tests/fixtures/golden/node_coding_agent_invoke_effect/unavailable_cred_home_unset.json
  • tests/unit/nodes/node_coding_agent/test_golden_chain_coding_agent.py
  • tests/unit/nodes/node_coding_agent/test_invoke_effect_golden_equivalence.py

Comment thread scripts/ci/adequacy_receipt.py
@jonahgabriel
jonahgabriel merged commit a638cf7 into dev Jul 14, 2026
159 of 170 checks passed
@jonahgabriel
jonahgabriel deleted the jonah/omn-14589-canonicalize-node_coding_agent_invoke_effect branch July 14, 2026 03:47
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