Repository navigation
fix(OMN-19833): projection writers report rows_upserted and route no pure fold, and a gate keeps it so - #3100
Conversation
…es the way the runtime expects Measured on the .201 dev lane 2026-09-30T03:27Z-03:39Z when the landing orchestrator's events first reached the bus (OMN-20127 hot-patch): the runtime went DEGRADED on projection_apply_divergence, failing every compose-dev lab pass, although the writer had landed 9 state rows and 34 transition rows in omnidash_analytics.omninode_internal. Three causes, each pinned by a test that fails on the previous code: 1. The writer reported its count as rows_written. The runtime reads only rows_upserted (omnibase_infra _extract_rows_upserted) and reads any other key as 0, so every real write counted as zero. The count now sits under rows_upserted, and a stale-write refusal under the runtime's refusal key, so a redelivery logs as a refusal, not a silent zero. 2. The pure fold was routed beside the writer. It was handed the raw event, failed nine extra_forbidden errors per message, and its zero upserts also counted toward the divergence. Only the writer is routed now, as OMN-19721 did for its siblings; the writer calls the fold in process. 3. One writer instance is routed on four topics and the runtime runs it in worker threads, so a transitioned and an agent-needed event of the same transition overlapped on the one adapter's pool (PoolConnectionHolder.wait_until_released). Each message now opens its own adapter, on its own loop, with the DSN the runtime bound. The tables were never absent: database_ref application resolves to omnidash_analytics on the dev lane, which the first diagnosis did not read. Onex-Lane: landing-projection-0510 Onex-Session: 5634e1aad3db43d594e27f74df90c7c9
…pure fold, and a gate keeps it so The runtime counts a projection write only under rows_upserted. The ci attempt outcome and worktree reconcile writers reported rows_written, so every real write counted as zero (omnimarket#3098 fixed the same for pr landing). Nine contracts also routed a pure fold beside the in-process writer; the runtime hands the fold the raw event, so it can never report a write. Each now routes the writer only, which calls the fold in process. New gate omnimarket.validators.projection_writer_contract (pre-commit hook and a pytest over the real tree plus positive controls) refuses a writer that never reports rows_upserted and a routed fold beside an in-process writer.
|
| 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_projection_ci_attempt_outcome/handlers/handler_ci_attempt_outcome_writer.py:123: self._derive = HandlerProjectionCiAttemptOutcome()
src/omnimarket/nodes/node_projection_metering_summary/handlers/handler_metering_summary_writer.py:96: result = HandlerProjectionMeteringSummary().handle(request)
src/omnimarket/nodes/node_projection_metering_summary/handlers/handler_projection_metering_summary.py:54: summary = HandlerMeteringSummary().handle(
src/omnimarket/nodes/node_projection_pr_landing/handlers/handler_pr_landing_writer.py:223: self._derive = HandlerProjectionPrLanding()
src/omnimarket/nodes/node_projection_worktree_reconcile/handlers/handler_worktree_reconcile_writer.py:49: self._derive = HandlerProjectionWorktreeReconcile()
src/omnimarket/projection/sqlite_metering_summary.py:42: result = HandlerProjectionMeteringSummary().handle(\n```\n\nHandler classes should be wired only in workflow_runner.py or adapter modules. Direct instantiation elsewhere bypasses the contract system.\n\n
Static architectural lint — no model inference (OMN-14176).
…mniNode-ai/omnimarket#3100 OCC companion by node_pr_lifecycle_fix_effect (OMN-13317 F1 / OMN-13990 / OMN-14285). Product PR head f0b55b603032b6e3ff160a91760cda734272cafd.
…nimarket#3100 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/36681848018
✅ Hostile Reviewer — PASSEDBlocking findings (critical/error): 0 Gate semantics
Powered by omniintelligence.review_pairing.cli_review — node-based adversarial review via HandlerLlmCliSubprocess (OMN-8524) |
| def _dispatch(event: dict[str, Any]) -> None: | ||
| try: | ||
| results.append(writer.handle(dict(event))) | ||
| except BaseException as exc: |
| pytestmark = pytest.mark.unit | ||
|
|
||
| _NODES_ROOT = Path(gate.__file__).resolve().parent.parent / "nodes" | ||
| _TOPIC = "onex.evt.test.thing-happened.v1" |
…e Evidence-Source on f0b55b6 Local readback on lab host h202: 82 passed, 11 skipped over the nine test files this PR touches. No code change.
…nimarket#3100 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/36684098258
Summary
rows_upserted. The ci attempt outcome and worktree reconcile writers reportedrows_written, so every real write counted as zero: a zero-row error per message, no terminal event, and theprojection_apply_divergencehealth dimension degraded. Both now reportrows_upserted. (Same defect omnimarket#3098 fixed for pr landing.)omnimarket.validators.projection_writer_contract: refuses an in-process writer that never reportsrows_upsertedand a routed fold beside an in-process writer. Wired as a pre-commit hook and astests/validators/test_projection_writer_contract_omn19833.py, which runs in CI over the real tree, with synthetic positive controls and a check that the key and routing agree with the runtime's own_extract_rows_upsertedand_topics_for_handler_entry. A vacuity floor fails a scan that sees fewer than 12 writers (15 today).Stacking
This branch sits on the head of omnimarket#3098 (its commit is the first in this PR). Once that merges, this rebases to the two-file-class delta above.
Audit
All 15 in-process projection writers were read. The other 13 already report
rows_upserted. Writers that are standalone runners (no in-process declaration) are out of scope by design.Test plan
_extract_rows_upsertedreads their count.Evidence-Ticket: OMN-19833
Evidence-Source: OCC#11871
Dev lane readback (hot-patched into omninode-runtime, warm restart, 2026-09-30)
rows_upserted: 1, projected: true. Before, that count read as zero.ci_attempt_outcomerow stored, applied event carriedrows_upserted: 1. No zero-row error line for either handler after the restart. Probe rows deleted afterwards.projection_apply_divergenceHEALTHY, all ten dimensions HEALTHY.