Skip to content

fix(OMN-19654): the delegate-skill claim store lives under the runtime's declared state root, not ~/.omninode - #2917

Merged
jonahgabriel merged 2 commits into
devfrom
jonah/omn-19654-claim-store-state-root
Sep 25, 2026
Merged

jonahgabriel merged 2 commits into
devfrom
jonah/omn-19654-claim-store-state-root

Conversation

@jonahgabriel

@jonahgabriel jonahgabriel commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Summary

This pull request resolves defect OMN-19654, which gates OMN-18307 AC4. The defect caused the deployed node_delegate_skill_orchestrator on the onex-lab k3s lane (host .201, namespace onex-dev) to terminalize every bus delegation with OSError [Errno 30] Read-only file system: '/.omninode' (ONEX_CORE_095_HANDLER_EXECUTION_ERROR). Correlation IDs 73d3d9b9-9f2e-418a-8309-de304d892b27 and 406dbd74-6c5b-4ed4-bd90-22ab30dc5a94 are associated with the failure in pod omninode-runtime-effects.

Root cause

The OMN-18887 claim port (ports/port_delegation_claim.py, resolve_delegation_claim_store) falls back to SqliteDatabaseAdapter(default_claim_db_path()) when no projection binding overlay is set. The default_claim_db_path is derived from default_evidence_db_path(), which resolves to Path.home()/.omninode/delegation/delegation.sqlite (projection/sqlite_database.py:37-39). Because SqliteDatabaseAdapter._connect creates the parent directory, a read-only root filesystem with HOME=/ results in EROFS on every delivery.

Change

A new module src/omnimarket/config/state_root.py introduces resolve_onex_state_root(purpose=...), which reads ONEX_STATE_DIR and then ONEX_STATE_ROOT. It raises OnexStateRootUnconfiguredError naming both keys if neither is set, with no home, cwd, or temp fallback. The default_claim_db_path now returns /delegation/delegation_claims.sqlite. DelegationClaimPort gains a keyword resolve_database factory, ensuring the sqlite fallback is resolved at the first claim rather than at handler construction. This allows the bus-less CLI, which builds the same handler but never claims, to operate without a state root. The attested-write type check was moved into _require_attested_write and continues to run on both paths. The Postgres path, used when the binding overlay is set, remains unchanged. No manifest change is required for onex-lab, as the onex-dev runtime base already declares ONEX_STATE_DIR and ONEX_STATE_ROOT over the /app/data emptyDir, and the prod runtime ConfigMap declares ONEX_STATE_ROOT.

Every writer with the same default

Other writers using the same Path.home()/.omninode default were not changed in this PR because they hold user data (evidence rows, minted tenant identity, BYOK credentials) on the bus-less CLI path, and moving them would relocate existing user data. These include projection/sqlite_database.py:37 default_evidence_db_path (with callers at nodes/node_delegate_skill_orchestrator/ports/evidence_db_resolution.py:101, local_deployment/tenant_identity.py:239, inference/local_byok_credential_adapter.py, projection/sqlite_metering_reader.py, and cli/cli_local.py) and omniclaude src/omniclaude/delegation/sqlite_adapter.py:25. Read-only overlay lookups that do not write include routing/delegation_backend_resolution.py:70, adapters/llm/bifrost/config_loader_bifrost_delegation.py:59, adapters/adk/adapter_adk_invoke.py:73, and delegation/swe_discriminator/model_client.py:43.

Tests

The test file tests/unit/nodes/node_delegate_skill_orchestrator/test_claim_store_state_root_omn19654.py contains 6 cases selected by -k omninode_readonly_rootfs. These cases verify that claims land under ONEX_STATE_DIR with an unwritable HOME, that ONEX_STATE_ROOT is used when ONEX_STATE_DIR is unset, that ONEX_STATE_DIR takes precedence, that unset or blank state roots refuse at claim with OnexStateRootUnconfiguredError while construction succeeds, and that a second port instance loses a redelivered claim. Before the fix, all 6 cases failed (5 with PermissionError creating /.omninode and 1 on the missing module). After the fix, 6 cases passed. The entire tests/unit/nodes/node_delegate_skill_orchestrator directory shows 257 passed and 3 skipped. Golden chain, structured terminal truth, claims migration, and evidence-db resolution tests show 74 passed. ruff, mypy --strict, and pre-commit checks are clean.

Lab readback

A pre-PR probe was run on the onex-lab lane (.201 k3s, namespace onex-dev, pod omninode-runtime-effects, image onex-lab/omninode-runtime:20260925T175853Z-a6afd007, omnimarket 0.4.219) as a separate python process via kubectl exec, without changing the running consumer or image. The negative control using the installed unfixed port reproduced the lane defect with OSError errno=30 (EROFS) at '/.omninode'. Candidate modules loaded from this branch's source in the same pod with ONEX_STATE_DIR=/tmp/omn19654-probe resulted in the first claim winning true, redelivery winning false, and the db file being created under the state root, with /.omninode not existing. When ONEX_STATE_DIR was removed, the candidate resolved ONEX_STATE_ROOT=/app/data/.onex_state and wrote there; the probe file was subsequently removed and /app/data was restored to its prior listing.

Runtime-affecting

Yes. This change alters the deployed delegate-skill orchestrator's claim store location.

Tickets

OMN-19654 is the primary ticket, serving as the gate for OMN-18307 AC4. An end-to-end delegation completing on the onex-lab lane is not yet proven; OMN-19654 AC1 and OMN-18307 AC4 will be read back after this merge and the lab lane is rebuilt via the deploy agent. A follow-up issue, not included in this PR, notes that while the node contract declares delegate_skill_command_claims in the application Postgres database (db_io, omninode_internal schema) and the table exists in the lab omnidash_analytics database, no runtime pod sets the projection binding overlay, causing deployed claims to use the per-pod sqlite fallback rather than the contract-declared Postgres table.


Body drafted by onex delegate run 65609ce9-ff36-46a7-ba21-015b30d2b073 (Qwen3.8-27B, receipt status success) and checked against the facts above by the authoring lane (omn19654-rofs-fix-83).

Evidence-Ticket: OMN-19654
Evidence-Source: OCC#11329

…e's declared state root, not ~/.omninode

On the onex-lab k3s lane the deployed node_delegate_skill_orchestrator runs
with readOnlyRootFilesystem and HOME=/. With no projection binding overlay on
the pod, the OMN-18887 claim port fell back to a SQLite file derived from
Path.home(), so every bus delivery tried to create /.omninode and terminalized
OSError [Errno 30] Read-only file system. No delegation completed on the lane.

The fallback claim store now resolves from ONEX_STATE_DIR, then
ONEX_STATE_ROOT (both declared by the runtime manifests over a writable
volume), through a new omnimarket.config.state_root resolver that raises a
named OnexStateRootUnconfiguredError when neither is set. Resolution is
deferred to the first claim, which only a bus delivery makes, so the bus-less
CLI that builds the same handler and never claims is unaffected.
@github-actions

Copy link
Copy Markdown

✅ Architectural Review — PASSED

Errors: 0
Warnings: 0

What this checks

Rule Description
ARCH-TOPIC-001 No hardcoded Kafka topic strings in handler code
ARCH-DI-001 No event_bus=None bypass
ARCH-DI-002 No direct Handler instantiation outside workflow_runner/adapters
ARCH-DI-003 No reinvented DI containers
ARCH-TOPIC-002 contract.yaml topics follow `onex.{cmd

No architectural violations found.

Static architectural lint — no model inference (OMN-14176).

onexbot-occ-writer Bot added a commit to OmniNode-ai/onex_change_control that referenced this pull request Sep 25, 2026
…nimarket#2917

The declared checks were executed for real in the OmniNode-ai/omnimarket checkout at the PR head by the product-repo OCC receipt runner, and the results written append-only into this companion. Producer-minted PENDING receipts are superseded by a net-new record; no existing receipt was edited.

Ticket: OMN-16859 (AC3b)
Onex-Workflow: OCC Receipt Runner
Onex-Run: https://github.com/OmniNode-ai/omnimarket/actions/runs/36177065633
@github-actions

github-actions Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

✅ Hostile Reviewer — PASSED

Blocking findings (critical/error): 0
Total findings: 11
Models succeeded: qwen3-review,qwen3-review-b


Gate semantics

Verdict Meaning Blocks merge?
passed >=2 models succeeded, no critical/error findings No
blocked CRITICAL or ERROR findings found Yes
degraded Fewer than 2 models succeeded (infra unavailable/timeout) Yes (OMN-15110)

Powered by omniintelligence.review_pairing.cli_review — node-based adversarial review via HandlerLlmCliSubprocess (OMN-8524)

…ore once

The deferred sqlite store was resolved without synchronization, so the
first claims on a fresh port, arriving together from in-flight records,
each built their own adapter. Resolution now happens under a lock with
a double check, and the missing-resolver case raises instead of relying
on an assert. New test: eight concurrent first claims of one delivery
resolve the store once and exactly one wins (RED 8 resolutions, GREEN 1).
onexbot-occ-writer Bot added a commit to OmniNode-ai/onex_change_control that referenced this pull request Sep 25, 2026
…nimarket#2917

The declared checks were executed for real in the OmniNode-ai/omnimarket checkout at the PR head by the product-repo OCC receipt runner, and the results written append-only into this companion. Producer-minted PENDING receipts are superseded by a net-new record; no existing receipt was edited.

Ticket: OMN-16859 (AC3b)
Onex-Workflow: OCC Receipt Runner
Onex-Run: https://github.com/OmniNode-ai/omnimarket/actions/runs/36195123053
jonahgabriel pushed a commit to OmniNode-ai/onex_change_control that referenced this pull request Sep 25, 2026
…imarket#2917 (#11329)

* evidence(OMN-19654): author OCC companion for OmniNode-ai/omnimarket#2917

OCC companion by node_pr_lifecycle_fix_effect (OMN-13317 F1 / OMN-13990 / OMN-14285). Product PR head 8718cdd3ffe4da07d89250e69a9ee07df4f207d4.

* evidence(OMN-19654): self-bind OCC#11329 + rebind contract_sha256

* evidence(OMN-16859): executed test_passes receipts for OmniNode-ai/omnimarket#2917

The declared checks were executed for real in the OmniNode-ai/omnimarket checkout at the PR head by the product-repo OCC receipt runner, and the results written append-only into this companion. Producer-minted PENDING receipts are superseded by a net-new record; no existing receipt was edited.

Ticket: OMN-16859 (AC3b)
Onex-Workflow: OCC Receipt Runner
Onex-Run: https://github.com/OmniNode-ai/omnimarket/actions/runs/36177065633

* evidence(OMN-16859): executed test_passes receipts for OmniNode-ai/omnimarket#2917

The declared checks were executed for real in the OmniNode-ai/omnimarket checkout at the PR head by the product-repo OCC receipt runner, and the results written append-only into this companion. Producer-minted PENDING receipts are superseded by a net-new record; no existing receipt was edited.

Ticket: OMN-16859 (AC3b)
Onex-Workflow: OCC Receipt Runner
Onex-Run: https://github.com/OmniNode-ai/omnimarket/actions/runs/36195123053

---------

Co-authored-by: omnimarket-bot <bot@omninode.ai>
Co-authored-by: onexbot-occ-writer[bot] <307849072+onexbot-occ-writer[bot]@users.noreply.github.com>
@jonahgabriel
jonahgabriel merged commit 42f502d into dev Sep 25, 2026
178 of 186 checks passed
@jonahgabriel
jonahgabriel deleted the jonah/omn-19654-claim-store-state-root branch September 25, 2026 23:36
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