Skip to content

feat(OMN-19977): onex metering renders the stored row, and the delegation refreshes it - #3407

Merged
Patel230 merged 22 commits into
devfrom
lakshman/OMN-19977-metering-reads-the-row
Oct 6, 2026
Merged

Patel230 merged 22 commits into
devfrom
lakshman/OMN-19977-metering-reads-the-row

Conversation

@Patel230

@Patel230 Patel230 commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Update 2026-10-06 04:42Z (head 44d85b0ed)

  • 44d85b0ed is a signed, conflict-free merge of current origin/dev through 4f999c0ee (omnimarket#3466). The two dev advances since the prior PR head add the OMN-20578 routing-feedback migration and move OMN-18887 redelivery coverage into the golden-chain suite; neither changes this PR's implementation paths.
  • Post-merge verification through the required lab-first pytest wrapper: 45 tests passed in 26.35 seconds, covering the 33 OMN-19977 focused tests plus the golden-chain file changed by current dev.
  • The signed merge hooks passed. The repository pre-push stage passed end-of-file, large-file, and strict mypy checks. This head requires fresh GitHub CI before merge.

Update 2026-10-06 04:22Z (head fc2f75e0a)

  • fc2f75e0a is a verified ED25519-signed, conflict-free merge of current origin/dev through 0f808a44f (omnimarket#3470).
  • Focused post-merge verification: 33 tests passed in 26.52 seconds across the tenant-identity read-only proof, stored metering-row renderer, and end-of-delegation metering refresh suites.
  • The previous head 602b36af6 reached 145 passed checks, 4 skipped checks, and one still-pending shadow coverage check before dev advanced. This new head requires fresh GitHub CI before merge.

Update 2026-10-06 04:02Z (head 602b36af6)

  • 602b36af6 is a verified ED25519-signed, conflict-free merge of current origin/dev through 0614c3a58. The current-base mandatory PR self-check is clean, including the pre-push stage and the 31-open-PR overlap scan.
  • Focused post-merge verification: 33 tests passed in 22.02 seconds across the tenant-identity read-only proof, stored metering-row renderer, and end-of-delegation metering refresh suites.
  • The previous head 4ed6fd477 had every GitHub check completed successfully or skipped, but dev then advanced by four commits. This new head requires a fresh GitHub run before merge.

Update 2026-10-06 03:29Z (head 4ed6fd477)

  • 4ed6fd477 is a verified ED25519-signed merge of current origin/dev through ff604489d; the merge was conflict-free. It carries the merged OMN-20558 characterization fix from #3468. The unrelated local experiment 5da3e3129 is not in this branch.
  • Focused post-merge verification: 33 tests passed across the tenant-identity read-only proof, stored metering-row renderer, and end-of-delegation metering refresh suites. The mandatory PR self-check's pre-push stage passed. The current-head CI run starts after this head is pushed.
  • The live open-PR overlap scan added #3471: it changes the watchdog timeout in _run_single_attempt, while this PR changes the post-terminal metering refresh in _project_evidence; both are required and the hunks are disjoint.

Earlier update 2026-10-06 (head d5569be3f)

  • The stacked predecessor omnimarket#3368 (OMN-20007) merged from reviewed head d7153cf4b as squash commit ba146f348. This branch now carries a signed merge of that origin/dev at d5569be3f; the merge is conflict-free and its tree is byte-identical to the previous head 3e330cfb9 (a3a32d59023993c755b920d2a5efbc2e30f57ae2). The predecessor order is satisfied.
  • bbb7e15da adds a fifth read-only proof to tests/test_omn19977_tenant_identity_read_is_read_only.py. It intercepts every SQLite connection used by read_local_tenant_identity, attempts a schema write through it, and requires SQLite's read-only refusal. On the real source all 5 tests pass; under the ?mode=ro to ?mode=rwc mutation this test fails with tenant identity reader opened a writable SQLite connection.
  • Post-merge verification at d5569be3f: 23 focused and related tenant-identity/local-deployment tests passed; Ruff check and format-check passed on the changed test and source; mypy --strict passed on tenant_identity.py. The repository commit hooks passed and the merge commit has a verified ED25519 signature.

Earlier update 2026-10-05 (head d36c97996)

  • d36c97996 (signed) makes the tenant identity read open the store mode=ro. read_local_tenant_identity used to go through SqliteDatabaseAdapter, whose connect applies additive schema DDL, so onex metering added tables and columns to an older store just by running. It now reads the identity row and the mirror row over a read-only connection. Outcomes are unchanged: no identity table reads as no identity (IDENTITY_ABSENT from the requirer, None from the reader), a table with no row is the same, and a malformed value still refuses IDENTITY_MALFORMED. Mint is untouched and still creates its own tables before it reads. Source change: src/omnimarket/local_deployment/tenant_identity.py.
  • Test first, red then green: tests/test_omn19977_tenant_identity_read_is_read_only.py (4 tests) builds an older-schema store (a narrow delegation_events table and nothing else), and compares the file's sha256, mtime and sqlite_master before and after the read. On 20fe7434b's source all 4 fail (the three reader tests change the file's bytes and schema; the onex metering --db test does too). On d36c97996 all 4 pass.
  • Tests at this head: 1252 passed, 12 skipped across the 28 OMN-19977 tests, the new 4, the CLI metering tests, the metering node and compute, tests/projection, tests/unit/projection, tests/unit/local_deployment, tests/unit/cli, the whole tests/unit/nodes/node_delegate_skill_orchestrator, and the local-port and tenant-identity suites. ruff check, ruff format --check and mypy --strict on the touched source are clean. tests/ci was also run on this branch: 1472 passed, 23 failed, all in test_check_wire_compatibility_omn18868.py and test_release_staleness_gate_omn18010.py; the first failure read is a git tag subprocess exiting 128 in the test's own scratch repo, and neither file touches the tenant identity code. Not re-run on the previous head, so whether they fail there too is unverified.
  • The paging item is no longer listed as an open defect: it is the limit omnimarket#3368 already records as accepted (decision D-B), so it moved to "Known, accepted limit" below.

Earlier update 2026-10-05 (head 20fe7434b)

  • Signed merge of dev 130d734d5 (2d7e784b1); pyproject.toml and uv.lock merged with no conflict and equal dev's 0.4.301, so the merge adds no version change of its own. Dev's version is not bumped again for this PR: the release-identity gate (scripts/check_release_identity.py) requires project.version strictly greater than the latest published tag, which is v0.4.300, and 0.4.301 is. The pre-commit "No code merged onto a published version without a bump" hook passed on the commit. If a peer PR merges and dev moves past 0.4.301 first, this PR takes that value on its next dev merge.
  • Both CodeQL findings are fixed in 20fe7434b: the no-op ... after the ProtocolMeteringRefreshPublisher.publish docstring is removed (py/ineffectual-statement, alert 1508), and the lock test's worker thread catches Exception instead of BaseException (alert 1509). The test still sees the TimeoutError or any other failure the refresh raises. Neither change alters behaviour.
  • Tests at this head: 577 passed, 5 skipped (the 28 OMN-19977 tests, the metering node, CLI metering, tests/projection, tests/unit/projection); ruff and mypy --strict on the touched source are clean.
  • Stacked on omnimarket#3368 (OMN-20007), still open, so this lands after test(OMN-20007): metering-summary day rows sum exactly to the all-time row #3368. The diff carries test(OMN-20007): metering-summary day rows sum exactly to the all-time row #3368's files (including contracts/OMN-20007.yaml and test_omn20007_day_rows_sum_to_all_window.py) until it merges; the closer's missing-file failure on that test comes from running against dev, where test(OMN-20007): metering-summary day rows sum exactly to the all-time row #3368 has not landed.

Earlier update 2026-10-05 06:41Z (head ee4f014fd)

  • a435c6916 adds contracts/OMN-19977.yaml: AC1 bound to tests/unit/nodes/node_delegate_skill_orchestrator/test_local_dispatch_metering_refresh_omn19977.py (16 tests) and AC2 to tests/test_omn19977_metering_renders_the_row.py (12). Both pass at the head and fail at the gate's control (dev 43700e208 plus the branch's tests and contract: 16 failed; 11 failed, 1 passed). The text below that says the contract "comes separately" is superseded.
  • Signed merges of dev 43700e208 and 005aa89f4; version now 0.4.301 (17c370e6f), because v0.4.300 was published at 06:18Z (same lines as merged chore(OMN-18010): post-release dev version bump to 0.4.301 #3409). The base and 0.4.300 notes below describe the earlier head.
  • Related suites: dev 995 passed vs merged 1038 passed (15 skipped, 4 errors on both: the laptop guard stopping the parity test's local Postgres); on the lakshman lane, parity plus the OMN-19977 real-Postgres tests 24 passed on both.

Summary

For OMN-19977, omnimarket half: AC2, AC4, AC5 (ruling D-A), and the CLI halves of AC6 and AC7, plus one mutation per AC1 clause. The end of each mode 1 onex delegate now refreshes the metering rows itself, and onex metering prints the stored metering-summary.v1 row instead of folding on every read. Before this, the CLI refreshed through the fold each time it ran, picked its baseline from a default frozen at import, and could write fixture-inclusive numbers under the real row's key.

Originally stacked on omnimarket#3368 (OMN-20007), which declares and serves metering-summary.v1. #3368 has now merged as ba146f348, and this branch carries that origin/dev merge at d5569be3f; the predecessor order is satisfied. The implementation diff below remains described against #3368's reviewed head d7153cf4b.

What changed

  • End-of-delegate refresh (AC4). The seam is LocalDelegationDispatchPort._project_evidence in src/omnimarket/nodes/node_delegate_skill_orchestrator/ports/port_local_delegation_dispatch.py. Once project_delegate_skill_terminal reports the terminal row written (rows_upserted > 0), the new _refresh_metering_summary calls refresh_metering_after_terminal. That issues the metering node's own fold request on the node's own command topic, read from its contract (onex.cmd.omnimarket.refresh-metering-summary.v1), for the run's UTC day plus the all row. In mode 1 the publisher delivers it in-process to HandlerProjectionMeteringSummary and the store-neutral store_rows, on the local SQLite store. It never goes on a bus, because the laptop store backs no lane's table. The refresh does not run when the terminal row did not land, or when the evidence store is not the local SQLite file (an overlay-bound shared store has its own lane writer). If the refresh fails, the delegation still returns its result, the failure is logged as a warning, and one line on stderr names the run and says onex metering will show the row as stale.
  • as_of is one microsecond after the newest recorded run, never the wall clock, because the fold's windows are half-open. So the run is inside its own row, a second identical terminal delivery changes no byte, and a late refresh for an earlier run stores the same snapshot as the latest one rather than an older one. A per-store flock covers read, fold and write, so two delegations finishing together cannot interleave. The wait for it is bounded at 30 s, so a stuck holder cannot hang a delegation.
  • Baseline or manifest change (AC5, D-A). When the tenant's all row under the resolved baseline is missing, or was priced against another manifest version, the same command goes out with days=None for the whole tenant. That recomputes every recorded day and the all row. Rows folded under a previous baseline are a different key and stay as they were.
  • onex metering renders the row (AC2). src/omnimarket/cli/cli_metering.py reads the row through HandlerProjectionRead over the read-only store. That is the same read onex dashboard and the pages make, with the exposure loaded from the node contract. The command prints the row's columns with summary_json spread at the top level, so every printed key is a field of the served row. It never folds, writes or prices. An absent row prints a typed METERING_SUMMARY_MISSING, and a row older than a recorded run, or priced against another manifest version, prints METERING_SUMMARY_STALE. Both name the repair, exit 1, and print no figures (src/omnimarket/models/model_metering_row_refusal.py). JSON gains window_end, the one column it used to drop. --include-fixtures is now refused, because stored rows count real runs only.
  • Same row as the page (AC6/AC7, CLI half). The default baseline comes from resolve_metering_baseline_model(), which is resolve_baseline_model(overlay={}, store={}), the same resolution compute_baseline_savings makes for each run. --baseline now only names another stored row, and the window still defaults to all-time. An unpriced baseline prints BASELINE_UNRESOLVED (stderr in JSON mode, where the row carries baseline_state: unresolved with null savings) and never a number. The text output says unmeasured runs are counted and in no sum.
  • Tests whose premise was the old behaviour: in tests/test_cli_metering_omn18697.py and three CLI tests in tests/nodes/node_projection_metering_summary/test_projection.py, the setup now stores the rows the refresh writes before the CLI reads them. test_cli_manifest_change_refreshes_all_days became test_cli_manifest_change_is_reported_stale_not_refreshed. No file under src/omnimarket/nodes/node_projection_metering_summary/ changed.

How it was verified

Base d7153cf4b (#3368 head, unchanged at 04:26Z); dev at b181b79a6 was read at 04:26Z.

  • New tests, red then green. There are 28 new tests: 12 in tests/test_omn19977_metering_renders_the_row.py and 16 in tests/unit/nodes/node_delegate_skill_orchestrator/test_local_dispatch_metering_refresh_omn19977.py. Against the base source (exported with git archive, with pytest's pythonpath pointed at it), 27 fail. A probe test in the same run confirmed it imported the base module. The one that passes on base is test_another_tenants_row_is_never_printed, existing behaviour that had no test. With the base source, the two rewritten test_projection.py CLI tests fail and the reworked existing tests pass. On the branch, all 60 tests across those four files pass. The AC4 tests run HandlerDelegateSkill with the default local port on a fresh HOME. Only the provider is stubbed, at the transport. They cover runs at 23:59:59Z and 00:00:00Z, and a missing-key failure.
  • Mutations, 18 of 18 failed at least one test, and each file was restored with its sha256 checked:
    • AC1, one per clause, against tests/nodes/node_projection_metering_summary. Fold as_of from the wall clock: 10 failed. A tokenless run entering the sums: 2 failed. Refresh folding only the all row: 3 failed.
    • AC2. The CLI refreshing before it reads: 4 failed. A column dropped: 2. The stale-run check off: 1. The manifest check off: 1.
    • AC6, the default frozen at import: 2. AC7, the label not named: 1.
    • AC4. All row not stored: 7. Wall-clock as_of: 2. Run day taken in UTC-5: 2. Failure swallowed: 2. Refresh without a durable terminal row: 2. Non-SQLite store folded: 1. Lock removed: 2. Unbounded lock wait: 1.
    • AC5, no whole-tenant refresh on change: 2.
  • Related suites, same set on base and branch. The set: metering node and compute, the CLI and reader tests, tests/projection, tests/unit/projection, the projection read node, the golden-chain read effect, writer-store parity, the dev seed, and the whole tests/unit/nodes/node_delegate_skill_orchestrator. Base: 956 passed, 16 skipped. Branch (3f371f3e5): 972 passed, 16 skipped. The 16 extra are the new AC4/AC5 tests, and the 16 skips are the real-Postgres legs, not run on a laptop.
  • Lint, types and gates. ruff check and ruff format --check are clean on the 8 changed files. mypy --strict src/omnimarket reports no issues in 4115 files. check_node_metadata_dependencies.py and node_runtime_sweep --import-check exit 0. The repo pre-commit at the signed commit 3f371f3e5, run with the worktree venv first on PATH and Homebrew bash 5.3: 63 passed, 51 skipped (no matching files), 0 failed. uv lock --check passes.
  • Cost. On a synthetic store of 25,000 runs over 226 days, the first whole-tenant refresh took 0.74 s, a steady day-plus-all refresh 0.20 s, and the CLI's currency check 0.18 s.

Failure paths

This writes no receipt or verdict, but it does write rows and print a figure, so the three shapes are covered as they apply:

  • Size: n/a. Nothing crosses a transport with a cap. The fold request is delivered in-process, and the stderr notice is one line.
  • No answer. If another refresh of the same store holds the lock, the wait is bounded (30 s). After that the refresh raises TimeoutError, the delegation still returns, and stderr names the run. This is tested with the lock held and the bound at 0.5 s. A refresh that raises for any other reason is surfaced the same way, also tested.
  • Incomplete verdict. A terminal row that did not land (zero rows, or the projection raised) gets no refresh attempt, tested both ways. A store that is not the local SQLite file gets none either. On the read side, a row that is absent, older than a recorded run, or priced against another manifest version prints a typed state, prints no figures and exits 1; it is never read as a current figure. A served row always carries every declared column, because the read node answers 503 projection_column_missing otherwise, and the CLI turns that into an error, not a number.

Open defects

Known, accepted limit

  • Paging across baseline models. read_served_row walks the exposure's pages, up to 100. metering_summary has no column unique per tenant, so the cursor window_start is unique only per tenant and baseline model, and a walk across two baseline models can skip a row at a page boundary. This is the limit omnimarket#3368 (OMN-20007) pins with a test and records under "Known, accepted limit": decision D-B of 2026-10-05, the 500-row limit is accepted and there is no cursor migration. This PR adds no paging of its own; it reads through that exposure. A tenant needs more than 500 rows before it can surface, and it would surface as METERING_SUMMARY_MISSING.

Decisions

  • In JSON mode BASELINE_UNRESOLVED goes to stderr, not into the JSON. The JSON carries only the row's own fields (baseline_state: "unresolved", savings_usd: null), so that every field it prints equals the served row's (AC2). The text output prints BASELINE_UNRESOLVED in place.

Local gates

  • No gate was bypassed — no --no-verify, no SKIP=, no --no-gpg-sign, no core.hooksPath override. Every pre-commit and pre-push hook ran.
  • Any hook that failed was fixed at the input, not worked around.

Two hooks failed during the work and were fixed at the input:

  • canonical-file-shape refused a # type: ignore in a test, and later a new module under projection/. The test now uses a plain stub, the reader moved into the CLI, and the refusal model went to models/.
  • registry-projection-reconcile fired when a module the delegate port imports SQL-read metering_summary. The port's module now reads only through the store adapter, and the read-only read is the CLI's, through the read node.

Overlapping open PRs

Read 2026-10-05 04:34Z. Since then (re-read 17:47Z): #3371 merged at 16:59Z, dev and this branch are at 0.4.301, so the version-line overlaps below with #3403, #3402, #3371 and #3370 no longer apply; the code overlaps with #3378, #3406, #3368 and #3361 stand as written.

Read live at 04:34Z. Stacked on #3368, six open PRs share a path with this change, and five of those share only the version lines. Against dev, #3368 and #3361 share paths as well, through #3368's own files.

Overlap-Reviewed: #3378 — both edit port_local_delegation_dispatch.py inside _project_evidence, plus the version lines. #3378 adds the usage-by-model-day fold inside the evidence try, and this change captures the terminal time there and refreshes metering after the except. Both are needed: they are different projections fed by the same terminal. git merge-tree of this commit with #3378's head 24ff8612a merges clean.
Overlap-Reviewed: #3406 — shares only pyproject.toml and uv.lock, the same 0.4.300 line. It adds the savings_per_measured_run_usd column to metering_summary, which is why the CLI prints the served row's columns as served rather than a fixed list. Merged with #3406's head 3f360db02 (merge-tree clean), the new and reworked metering tests plus #3368's served-row test give 88 passed, including served-row field equality with the new column.
Overlap-Reviewed: #3368 — this branch was stacked on #3368's head d7153cf4b, so it carried #3368's six files unchanged: none of them is edited here. #3368 serves metering-summary.v1, and this change reads that exposure through the read node. #3368 has now merged as ba146f348, and the signed origin/dev merge at d5569be3f has the same tree as the previous branch head, so this PR's remaining diff is this change alone.
Overlap-Reviewed: #3361 — tests/unit/projection/test_projection_discovery.py comes in only through #3368 (its live-topic count goes to 70). #3361 adds two columns in a different test of the same file. The changes are disjoint, and #3368's body records that this merge is clean.
Overlap-Reviewed: #3403 — the release train's 0.4.300 bump. The lines are identical, and the second to merge takes them unchanged.
Overlap-Reviewed: #3402 — the release train's bump (its branch now reads 0.4.300). The lines are identical.
Overlap-Reviewed: #3371 — its own 0.4.300 bump for an unrelated src/ change. Both need a bump past the published 0.4.299, and the lines are identical.
Overlap-Reviewed: #3370 — its own 0.4.300 bump for an unrelated src/ change. Both need a bump past the published 0.4.299, and the lines are identical.
Overlap-Reviewed: #3447 — preserves gate failure reasons in rejected-attempt error_message; #3407 refreshes metering after a durable terminal projection. Both are needed, their hunks are disjoint, and merge-tree is clean at faa7609 and 3e330cf.
Overlap-Reviewed: #3437 — preserves raw leading-reasoning evidence before the quality gate; #3407 refreshes metering after terminal projection. Both are needed, their hunks are disjoint, and merge-tree is clean at da94523 and 3e330cf.
Overlap-Reviewed: #3435 — stamps caller, ticket, and lineage attribution onto the evidence terminal before projection; #3407 refreshes metering only after that projection reports rows_upserted. Both are needed and merge-tree is clean at 74832a5 and 3e330cf.

Overlap-Reviewed: #3471 — adds stage=inference to the watchdog timeout at _run_single_attempt; #3407 adds the post-terminal metering refresh in _project_evidence. Both changes are needed, their hunks are disjoint in the shared delegate port, and current origin/dev is already merged into #3407.

Not in this change

  • The OmniDash rebinding of Overview to metering-summary.v1 (AC3, AC6 and AC7, page half; AC9), the OCC companion that binds AC1 and AC2 to their pytest, and the lab readback (TI5, AC3, AC10). Each comes separately.
  • A cursor column (D-B: the 500-row limit is accepted).
  • Whether a run that consumed tokens and then failed its quality gate should stay in the sums. The fold counts its real spend and counterfactual today; that definition belongs to the metering node.
  • An explicit refresh command. Today the repair a stale or missing row names is the next onex delegate.
  • Any change to the metering node's fold, writer, contract or migrations (OMN-20009 is changing them).

Evidence-Ticket: OMN-19977
Evidence-Source: OCC#12865

…e row

The pages read the daily savings series from metering-summary.v1's day
rows (ruling 2026-10-04: option (a); savings-series.v1 stays listed as
not served). This pins what that relies on: for one fold of recorded
runs, the day rows' spend, counterfactual and savings add up exactly
(Decimal) to the all-time row, unknown money stays null, run and token
counts add up the same way, an unmeasured run counts in runs_total and
in no money sum, every run lands in its UTC day, a run at as_of lands in
none, and folding the same records again in any order gives the same
rows.

The records cover three UTC days with an idle day between two of them,
two models, a run with no tokens, a run with no spend, runs either side
of a UTC midnight (one written with a -04:00 offset) and a run at as_of.
Each day row is checked against a hand-computed oracle.

On dev acd8a5b the six tests pass. Five wrong folds each fail at least
one: a day window one hour short (3 failed), a day window that includes
its end (4), a run at as_of counted (3), local calendar days instead of
UTC days (1), one day dropped (4). The related metering suites: 81
passed on dev, 87 with this file.

contracts/OMN-20007.yaml binds AC3 and AC4 to this file; the pinned
onex_change_control validator (802b5f710) accepts it. The lab reads of
the served day rows wait on a served metering-summary.v1 exposure.

Onex-Lane: omn20007-day-rows-sum
Onex-Session: d99c149d-35af-4f1e-b791-28a28187bfc4
Keep the branch on the latest base before adding the exposure.

Onex-Lane: omn20007-metering-summary-exposure
Onex-Session: d99c149d-35af-4f1e-b791-28a28187bfc4
…dable per tenant

The pages read the daily savings series from metering-summary.v1's day
rows (ruling 2026-10-04, option (a)), but node_projection_metering_summary
declared no projection_api block, so every read of that topic answered
404 unknown_topic and the series could not reach a page at all. Pinning
the fold's arithmetic alone could not show that: those tests already
passed on dev, which is why the evidence check was refused as always-pass.

This declares the exposure: bus-backed, tenant-scoped on tenant_id, every
metering_summary column, key (tenant_id, window_kind, window_start,
baseline_model) with a mutable grain, as_of as freshness. It uses the
nested exposures form because the pinned exposure-drift gate reads only
that form; a flat block would never have been reconciled against the
migration DDL.

The new local-mode test folds day and all rows for two tenants into a
SQLite store and reads them through the projection read node with the
exposures built from the contracts: the reading tenant's rows only, every
column, day rows summing exactly to the served all row, the same rows
after a replayed fold, a key equal to the store's unique index, and the
catalogue listing metering-summary.v1 ok while savings-series.v1 stays
degraded not_yet_bus_backed and still answers 503.

window_start is the cursor because no column of metering_summary is
unique per tenant. It is unique per tenant and baseline model, so a page
boundary between two baselines' rows for the same window skips one; a
test measures and pins that gap (4 of 9 rows at limit 1, none at the
contract limit) rather than leaving it in a comment. An exact walk needs
a cursor column in the table, which belongs to the fold's migration.

The exposure count (69 to 70) and the cursor-declared set (70 to 71)
are pinned populations that every new exposure updates; both move by
this one exposure. contracts/OMN-20007.yaml binds AC1's code half, AC3
and AC4 to the new test plus the existing sum and replay test, so the
check fails at the merge base, where the exposure does not exist.

Onex-Lane: omn20007-metering-summary-exposure
Onex-Session: d99c149d-35af-4f1e-b791-28a28187bfc4
…of an assert

v0.4.291 was published from dev head 8ee3d61 at 12:18Z, so dev's own
version now equals a release it descends from. This PR changes packaged
source (the metering-summary node contract), and the release-identity
gate refuses that onto a published version: Release Identity Gate job
111439457514 failed with "0.4.291 is NOT ahead of latest published
0.4.291". The gate's own message asks for the next patch, and the change
here is byte-identical to the release train's pending bump (#3372 head
7525cd3, pyproject.toml and uv.lock), so whichever lands second merges
clean.

CodeQL alert 1499 (py/side-effect-in-assert): the served-rows test popped
summary_json inside the assert, so running under -O would skip the pop
and the row comparison after it would see an extra key. The pop now runs
first and the assert checks the popped value; replacing it with None
still fails the test.

Onex-Lane: ci-fix-OMN-20007
Onex-Session: d99c149d-35af-4f1e-b791-28a28187bfc4
Takes dev's 0.4.293 version line (#3375); the branch's 0.4.292 is already published.

Onex-Lane: claude-d99c149d
Onex-Session: d99c149d-35af-4f1e-b791-28a28187bfc4
Takes dev's version line; 0.4.293 is published.

Onex-Lane: claude-d99c149d
Onex-Session: d99c149d-35af-4f1e-b791-28a28187bfc4
…tion refreshes it

`onex metering` folded the evidence store on every read and picked its
baseline from a default frozen at import, so it was a second computation of
the savings figure, and could select a different row from the one Overview
reads. `--include-fixtures` even stored fixture-inclusive numbers under the
real row's key.

Now the end of each mode 1 `onex delegate` refreshes the rows. Once the
terminal row is durable, LocalDelegationDispatchPort._project_evidence issues
the metering node's own fold request on its contract topic for the run's UTC
day plus the all row, delivered in-process on the local SQLite store. A
missing all row, or one priced against another manifest version, sends the
same command for the whole tenant instead (ruling D-A). as_of follows the
newest recorded run, so replays change no byte and a late refresh cannot go
backwards; a bounded per-store lock keeps two refreshes from interleaving. A
refresh failure is logged and said on stderr, never failing the delegation.

The CLI reads the row through HandlerProjectionRead and prints the served
row's fields, with no fold, write or price arithmetic. An absent or stale row
is a typed state that names the repair. The baseline is resolved the way each
run's saving is, and an unpriced baseline prints BASELINE_UNRESOLVED, never a
number.

Version 0.4.300: the release-identity gate refuses packaged source on a
published version; v0.4.299 is published, and the sibling PRs use 0.4.300.

Onex-Lane: omn19981-evidence
Onex-Session: bf37a08791cf43ccab41e737a008f469
onexbot-occ-writer Bot added a commit to OmniNode-ai/onex_change_control that referenced this pull request Oct 5, 2026
…nimarket#3407

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/37266836556
jonahgabriel pushed a commit to OmniNode-ai/onex_change_control that referenced this pull request Oct 5, 2026
)

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

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

* evidence(OMN-19977): self-bind OCC#12865 + rebind contract_sha256

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

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/37266836556

---------

Co-authored-by: omnimarket-bot <bot@omninode.ai>
Co-authored-by: onexbot-occ-writer[bot] <307849072+onexbot-occ-writer[bot]@users.noreply.github.com>
… and render tests

The repo-evidence dod-verify job refuses a pull request that cites a
ticket and carries no contracts/<ticket>.yaml, so #3407 never reached
its checks (run 37266837348). This adds the contract.

AC1 binds to the end-of-delegate refresh tests: a second identical
terminal delivery leaves the rows byte-identical, a failed run counts and
is in no sum, and a changed baseline or manifest version refolds every
row. AC2 binds to the render tests, which compare each printed field with
the served row and fail if rendering folds or writes. The ticket's own
AC1 falsifier, the whole node_projection_metering_summary module, passes
at the merge base because #3079 delivered the fold, so the gate's
must-fail control would refuse it; it is not bound. AC3 needs the
OmniDash binding and the lab readback and is not claimed here.

Checked as the gate does: at this head the two bound files give 16 and
12 passed; at dev 43700e2 with this branch's test files laid over, 16
failed and 11 of 12 failed.

Onex-Lane: omn19981-evidence
Onex-Session: bf37a08791cf43ccab41e737a008f469
…-row

The branch sat on #3368's head d7153cf, 21 commits behind dev. Dev
changed port_local_delegation_dispatch.py (#3390, #3382) and the
projection read handler (#3364, #3363), both of which this change
uses; the merge is textually clean, and the metering, dispatch and
projection-read suites were re-run on the merged tree. #3368 is still
unmerged, so its six files stay on this branch unchanged.
v0.4.300 was tagged on dev 43700e2 at 06:18:02Z, so the Release
Identity Gate now refuses this branch's packaged source at 0.4.300:
scripts/check_release_identity.py --base origin/dev fails at 0.4.300
and passes at 0.4.301. pyproject.toml and uv.lock are byte-identical
to the release train's pending bump, omnimarket#3409, so whichever
merges second takes identical text.

Onex-Lane: omn19981-evidence
Onex-Session: bf37a08791cf43ccab41e737a008f469
…-row

Dev's only new commit is the v0.4.301 opening (#3409), whose
pyproject.toml and uv.lock lines the previous commit already carries
byte-identically. The merge changes no file on this branch.
@github-actions

github-actions Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

⚠️ Architectural Review — WARNINGS

Errors: 0
Warnings: 1

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

ARCH-DI-002: Direct Handler instantiation (warning)\n\n```\nsrc/omnimarket/cli/cli_metering.py:191: handler = HandlerProjectionRead(

src/omnimarket/nodes/node_delegate_skill_orchestrator/ports/port_local_delegation_dispatch.py:987: self._effect_handler = effect_handler or HandlerLlmDelegationCall()
src/omnimarket/nodes/node_delegate_skill_orchestrator/ports/port_local_delegation_dispatch.py:988: self._projection_handler = projection_handler or HandlerProjectionDelegation(
src/omnimarket/nodes/node_delegate_skill_orchestrator/ports/port_local_delegation_dispatch.py:3390: HandlerProjectionLlmCost().project(call_event, self._evidence_db)
src/omnimarket/projection/sqlite_metering_summary.py:82: result = HandlerProjectionMeteringSummary().handle(
src/omnimarket/projection/sqlite_metering_summary.py:156: result = HandlerProjectionMeteringSummary().handle(request)\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).

@github-actions

github-actions Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

✅ Hostile Reviewer — PASSED

Blocking findings (critical/error): 0
Total findings: 10
Models succeeded: qwen3-review,gpt-oss-review


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)

Comment thread src/omnimarket/projection/sqlite_metering_summary.py Fixed
Drop the no-op Ellipsis after the Protocol method docstring (py/ineffectual-statement)
and narrow the lock test's worker catch from BaseException to Exception.

Onex-Lane: omn19977-metering-row
Onex-Session: e72e9157-677b-4b13-bd77-daa2be198c16
The metering read is mode=ro, but the tenant identity read opened the store
through SqliteDatabaseAdapter, whose connect applies additive schema DDL, so
`onex metering` changed an older store just by running. read_local_tenant_identity
now reads the identity and mirror rows over a mode=ro connection. An absent
identity table still reads as no identity (IDENTITY_ABSENT from the requirer),
a table without a row is still absent, and a malformed value still refuses.
Mint is unchanged and still creates its tables before it reads.

Onex-Lane: omn19977-metering-row
Onex-Session: e72e9157-677b-4b13-bd77-daa2be198c16
Onex-Lane: codex-01a10cd4
Onex-Session: 8e0e3d7486864991af4e16364639c7df
Onex-Lane: codex-01a10cd4
Onex-Session: 8e0e3d7486864991af4e16364639c7df
Onex-Lane: codex-01a10cd4
Onex-Session: e7efbde5fb2449db91c6f200e1965d6d
@Patel230
Patel230 merged commit 14c88f4 into dev Oct 6, 2026
159 of 163 checks passed
@Patel230
Patel230 deleted the lakshman/OMN-19977-metering-reads-the-row branch October 6, 2026 05:07
@Patel230

Patel230 commented Oct 6, 2026

Copy link
Copy Markdown
Contributor Author

Merge close-out

  • omnimarket#3407 merged from reviewed head 44d85b0ede26ff45a7947ffb7195dc8728e4e983 as squash commit 14c88f49726b1a9123a57f9d145605e2b5f61c2f at 2026-10-06T05:07:05Z.
  • Current-head CI completed with 154 successful checks, 4 skipped, 1 neutral, 4 superseded cancellations, no failures, and no pending checks. CI Summary passed.
  • The two earlier CodeQL findings are resolved and outdated; current-head CodeQL passed. The evidence companion is OCC#12865.
  • OMN-19977 remains In Progress: the product PR proves the omnimarket/CLI half, while AC3's Overview-to-CLI equality and lab readback remain open.

Thank you!

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.

2 participants