Repository navigation
feat(OMN-14058): thread ONEX_TENANT_ID through delegation FSM + projections [OPERATOR-ACCEPTED INTERIM] - #1648
Conversation
…ctions [OPERATOR-ACCEPTED INTERIM] Threads a real tenant identity through the delegation chain so delegation/savings projection rows stop silently landing under the shared 'omninode' column default: - omnimarket.config.settings.Settings: new onex_tenant_id field (ONEX_TENANT_ID env var), the single sanctioned config surface. - DelegationWorkflowState / TerminalEmissionInputs (handler_delegation_workflow.py): tenant_id resolved ONCE at request-acceptance (handle_delegation_request) -- request.tenant_id wins, falling back to ONEX_TENANT_ID -- and carried onto every terminal shape (completed / failed / agent-lifecycle) through to ModelDelegationResult. - LocalDelegationDispatchPort.dispatch (port_local_delegation_dispatch.py): same resolve-once-at-acceptance pattern for the bus-less `onex delegate` CLI path, mirroring the existing source_session_id forwarding. - Projection models (model_delegate_skill_terminal_projection.py) and handlers (handler_projection_delegation.py, handler_projection_savings.py): stamp row['tenant_id'] from the event ONLY when present -- omitting the key (never writing None) lets the NOT NULL DEFAULT 'omninode' column apply on INSERT and leaves an already-known tenant untouched on UPDATE. - Bug fix bundled with the interim stamp: materialize_budget_state's call site previously passed event.session_id as tenant_id (a session is not a tenant); now passes the real tenant_id, falling back to ModelDelegationBudgetStateEvent.resolved_tenant()'s DEFAULT_TENANT. - Two contract-conformance tests added (test_budget_state_surface_omn13235.py, test_golden_chain_projection_savings.py) to keep the pre-existing state-coverage-gate baseline entries for onex.snapshot.projection.delegation.budget-state.v1 and the two savings topics from regressing to a hard FAIL now that this diff touches those nodes (strict mode promotes baselined WARNs to FAIL for directly-modified nodes). OPERATOR-ACCEPTED INTERIM per OMN-14058 (beta-blocking tenant attribution). The durable per-tenant identity design is OMN-14107; this is the pragmatic beta fix, not the final design. Depends on the companion omnibase_core PR (adds tenant_id to ModelDelegationRequest/ModelDelegationResult) -- CI here will not go fully green until that PR merges, releases, and the omnibase-core pin in pyproject.toml/uv.lock is repinned.
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 51 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughAdds an ChangesTenant ID propagation
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant Request
participant HandlerDelegationWorkflow
participant Settings as get_settings
participant LocalDispatchPort
participant HandlerProjectionDelegation
participant HandlerProjectionSavings
Request->>HandlerDelegationWorkflow: delegation request accepted
HandlerDelegationWorkflow->>Settings: fallback onex_tenant_id if request.tenant_id absent
HandlerDelegationWorkflow->>HandlerDelegationWorkflow: store tenant_id on workflow state
HandlerDelegationWorkflow->>HandlerProjectionDelegation: terminal event with tenant_id
HandlerProjectionDelegation->>HandlerProjectionDelegation: conditionally stamp tenant_id in delegation_events/budget-state
HandlerProjectionDelegation->>HandlerProjectionSavings: task-delegated payload with tenant_id
HandlerProjectionSavings->>HandlerProjectionSavings: conditionally stamp tenant_id in savings_estimates
LocalDispatchPort->>Settings: resolve onex_tenant_id
LocalDispatchPort->>LocalDispatchPort: stamp tenant_id into evidence payload
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
Defect found (independent adversarial verification): missing
Failure mode: A companion fix is in flight under OMN-14058: a forward migration adding |
|
| 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 |
ARCH-DI-002: Direct Handler instantiation (warning)\n\n```\nsrc/omnimarket/nodes/node_delegate_skill_orchestrator/ports/port_local_delegation_dispatch.py:370: self._effect_handler = effect_handler or HandlerLlmDelegationCall()
src/omnimarket/nodes/node_delegate_skill_orchestrator/ports/port_local_delegation_dispatch.py:371: self._projection_handler = projection_handler or HandlerProjectionDelegation()
src/omnimarket/nodes/node_delegate_skill_orchestrator/ports/port_local_delegation_dispatch.py:391: self._judge = judge if judge is not None else HandlerJudgeAdequacy()
src/omnimarket/nodes/node_delegation_orchestrator/handlers/handler_delegation_workflow.py:493: loop = HandlerComplianceLoop()
src/omnimarket/nodes/node_delegation_orchestrator/handlers/handler_delegation_workflow.py:722: HandlerEscalationDecision()\n```\n\nHandler classes should be wired only in workflow_runner.py or adapter modules. Direct instantiation elsewhere bypasses the contract system.\n\n
Powered by node_pr_review_bot
✅ PR Review Bot — CLEANFindings: 0
Verdict semantics
Powered by node_pr_review_bot — FSM pipeline with multi-model review fan-out |
❓ Hostile Reviewer — UNKNOWNBlocking findings (critical/error): 0 Gate semantics (pilot phase)
Powered by omniintelligence.review_pairing.cli_review — node-based adversarial review via HandlerLlmCliSubprocess (OMN-8524) |
Summary
OMN-14058 (beta-blocking): delegation/savings projection rows land under
tenant_id='omninode'(the omnidash-side column default,omnidash/db/migrations/0001_tenant_rls.sql) because no tenant identity exists anywhere in the delegation chain today. OPERATOR-ACCEPTED INTERIM FIX — the operator explicitly accepted this pragmatic path for beta; the durable per-tenant identity design is tracked separately as OMN-14107 and is out of scope here.This is the omnimarket half — it threads a real tenant identity through the delegation FSM and stamps it onto the projection rows. Companion omnibase_core PR (OmniNode-ai/omnibase_core#1404) adds the
tenant_idfield to the two canonical wire DTOs this depends on.Changes
src/omnimarket/config/settings.py: newonex_tenant_id: str = Field(default="", ...)field — the sanctioned config surface, mapped bypydantic-settings(case_sensitive=False) to theONEX_TENANT_IDenv var. Regenerated.env.exampleviascripts/generate_env_example.py.handler_delegation_workflow.py(bus orchestrator FSM):DelegationWorkflowState.tenant_idresolved exactly ONCE inhandle_delegation_request(request-acceptance) —request.tenant_idwins if the caller set it, else falls back toONEX_TENANT_IDviaget_settings(). Carried onto everyTerminalEmissionInputsconstruction site (completed / pre-gate-failed / agent-lifecycle) and through_emit_terminalinto the canonicalModelDelegationResult.port_local_delegation_dispatch.py(bus-lessonex delegateCLI path): same resolve-once-at-dispatch()-acceptance pattern, mirroring the existingsource_session_idforwarding into the delegate-skill terminal payload — this is a genuinely separate production entry point (LocalDelegationDispatchPort) that also writesdelegation_eventsevidence rows viaHandlerProjectionDelegation, so it needed the same fix to actually close the gap for the CLI path (not just the bus path).model_delegate_skill_terminal_projection.py:tenant_idfield added toModelDelegateSkillTerminalProjection(validation_aliastenant_id/tenantId),ModelDelegationEventProjectionRow,ModelDelegateSkillSavingsProjection, andModelTaskDelegatedSavingsSource(+from_canonical_payloadmapping), so the value survives every projection conversion hop.handler_projection_delegation.py:ModelProjectionTaskDelegatedEvent.tenant_idfield;project()andproject_delegate_skill_terminal()stamprow["tenant_id"]only when the source event carries a non-blank value — the key is omitted (never written asNone) so theNOT NULL DEFAULT 'omninode'column applies cleanly on INSERT and an already-known tenant is left untouched on UPDATE (verified againstpostgres_sync_database.py'supsert(), which builds the INSERT column list directly fromrow.keys())._canonical_result_to_task_delegated_payloadnow carriestenant_idthrough the canonical-terminal→compat-shape conversion.handler_projection_savings.py: same stamp-when-present pattern inproject_delegate_skill_savings(). The plainModelSavingsEstimatedEvent.project()path (a separate upstream event,onex.evt.omnibase-infra.savings-estimated.v1, not part of the delegation chain) is intentionally untouched — no tenant identity is available on that source event.handler_projection_delegation.py'smaterialize_budget_statecall site previously passedtenant_id=event.session_id— a session identifier is not a tenant identifier. Now passes the realevent.tenant_id, falling back toModelDelegationBudgetStateEvent.resolved_tenant()'s existingDEFAULT_TENANTwhen none is resolved. This was a live latent bug in the same function I was already touching for OMN-14058; flagging it explicitly since it's not literally in scope but is directly adjacent and cheap to fix correctly rather than propagate.tests/test_budget_state_surface_omn13235.py,tests/test_golden_chain_projection_savings.py): thestate-coverage-gatepre-commit hook promotes pre-existing baselined declared-state gaps to a hard FAIL for any node directly touched by a diff (scripts/validation/state_coverage_baseline.txt—node_projection_delegation'sonex.snapshot.projection.delegation.budget-state.v1andnode_projection_savings's two-applied.v1/savings-overview.v1topics were pre-existing gaps unrelated to this change, verified viagit stash— the gate passes on the unmodified tree). Added real, non-vacuous assertions (contract.yamlprojection_api.exposures/terminal_eventconformance checks, not gaming asserts) rather than bypassing the gate.Verification
not kafkamarker):tests/unit/delegation/(535 passed, 2 skipped),tests/unit/nodes/node_delegate_skill_orchestrator/(99 passed), the full projection/delegation/golden-chain surface listed below (all green):test_golden_chain_projection_delegation.py,test_premium_counterfactual_omn13355.py,test_projection_delegation_tier_distribution_omn13662.py,test_projection_malformed_dlq_omn13548.py,test_savings_estimates_from_source_omn12494.py,test_escalation_metered_cost_reaches_projection_omn13535.py,test_budget_state_surface_omn13235.py,test_delegation_dto_omnibase_compat_imports.py,delegation_golden/test_layer1_contract_unit.py,integration/golden_chain/test_sea_acceptance_chain.py,integration/node_projection_delegation_inference_response/,test_actual_cost_recompute_omn13355.py,test_delegation_converge_one_schema_omn12811.py,test_generation_*(corpus/events proof/failed/semantic),test_golden_chain_projection_savings.py,test_traces_projection_omn13083.py,unit/cost/test_generation_cost_projection_omn13621.py,unit/delegation/test_canonical_failed_terminal_cost_projection_omn13408.py,test_ceiling_quality_gate_wiring_omn13335.py,test_context_pack_hash_plumb_omn13644.py,test_failed_terminal_cost_usd_omn13408.py,test_judge_verdict_events_omn13367.py,test_golden_chain_node_delegate_skill_orchestrator.py,test_roi_overlay_routing_omn14001.py.pre-commit runon all changed files — every hook passes, including the previously-failingstate-coverage-gate(verified before/after withgit stash).ruff format --check/ruff check/mypy(non-strict, matching CI) on all changed files — clean.pytest tests/(unconditional, dev-boundary style) in progress in background at push time; will report final tail if requested.omnibase-coreresolves from PyPI in this repo'spyproject.toml(no[tool.uv.sources]local override — OMN-13878). To exercise thetenant_idfield before the companion omnibase_core PR is merged/released, local test runs usedpip install -e <local omnibase_core worktree> --no-depsinto this venv (NOT committed topyproject.toml/uv.lock). CI here will see the old pinnedomnibase-coreand MUST NOT be expected to go green until the companion PR merges + releases + the pin is repinned.Dependency / sequencing
Depends on OmniNode-ai/omnibase_core#1404 merging + releasing + a follow-up
repin omnibase-core X -> Ycommit here (same pattern as recent PR history, e.g.fix(OMN-14138): repin omnibase-core 0.46.4 -> 0.46.5). Until then this PR's CI is expected to fail on the missingtenant_idfield.Non-goals / explicitly out of scope
node_delegate_skill_orchestrator's delegate-skill-terminal topic path and the plainsavings-estimated.v1path were scoped in/out as documented above — not a blanket tenant-id sweep across every omnimarket node.OCC / DoD
Per lane instructions: NOT self-authoring an OCC Evidence-Source companion (a parallel lane is fixing OCC-companion autogen; see memory
feedback_no_self_authored_evidence). This PR needs an independent OCC companion + dod_evidence on OMN-14058 before it can merge — that dependency is flagged, not resolved, here.Closes OMN-14058 (omnimarket half; see companion omnibase_core#1404 for the wire-DTO half).
No merge, no deploy — this PR is queued for independent adversarial review per the dispatching lane's instructions.
Evidence-Source: OCC#3733
Evidence-Ticket: OMN-14058
Summary by CodeRabbit
New Features
Bug Fixes
Tests