Repository navigation
feat(OMN-19977): onex metering renders the stored row, and the delegation refreshes it - #3407
Conversation
…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
…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
) * 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.
|
| 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).
✅ Hostile Reviewer — PASSEDBlocking findings (critical/error): 0 Gate semantics
Powered by omniintelligence.review_pairing.cli_review — node-based adversarial review via HandlerLlmCliSubprocess (OMN-8524) |
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
Merge close-out
Thank you! |
Update 2026-10-06 04:42Z (head
44d85b0ed)44d85b0edis a signed, conflict-free merge of currentorigin/devthrough4f999c0ee(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.Update 2026-10-06 04:22Z (head
fc2f75e0a)fc2f75e0ais a verified ED25519-signed, conflict-free merge of currentorigin/devthrough0f808a44f(omnimarket#3470).602b36af6reached 145 passed checks, 4 skipped checks, and one still-pending shadow coverage check beforedevadvanced. This new head requires fresh GitHub CI before merge.Update 2026-10-06 04:02Z (head
602b36af6)602b36af6is a verified ED25519-signed, conflict-free merge of currentorigin/devthrough0614c3a58. The current-base mandatory PR self-check is clean, including the pre-push stage and the 31-open-PR overlap scan.4ed6fd477had every GitHub check completed successfully or skipped, butdevthen advanced by four commits. This new head requires a fresh GitHub run before merge.Update 2026-10-06 03:29Z (head
4ed6fd477)4ed6fd477is a verified ED25519-signed merge of currentorigin/devthroughff604489d; the merge was conflict-free. It carries the merged OMN-20558 characterization fix from #3468. The unrelated local experiment5da3e3129is not in this branch._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)d7153cf4bas squash commitba146f348. This branch now carries a signed merge of thatorigin/devatd5569be3f; the merge is conflict-free and its tree is byte-identical to the previous head3e330cfb9(a3a32d59023993c755b920d2a5efbc2e30f57ae2). The predecessor order is satisfied.bbb7e15daadds a fifth read-only proof totests/test_omn19977_tenant_identity_read_is_read_only.py. It intercepts every SQLite connection used byread_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=roto?mode=rwcmutation this test fails withtenant identity reader opened a writable SQLite connection.d5569be3f: 23 focused and related tenant-identity/local-deployment tests passed; Ruff check and format-check passed on the changed test and source;mypy --strictpassed ontenant_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 storemode=ro.read_local_tenant_identityused to go throughSqliteDatabaseAdapter, whose connect applies additive schema DDL, soonex meteringadded 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_ABSENTfrom the requirer,Nonefrom the reader), a table with no row is the same, and a malformed value still refusesIDENTITY_MALFORMED. Mint is untouched and still creates its own tables before it reads. Source change:src/omnimarket/local_deployment/tenant_identity.py.tests/test_omn19977_tenant_identity_read_is_read_only.py(4 tests) builds an older-schema store (a narrowdelegation_eventstable and nothing else), and compares the file's sha256, mtime andsqlite_masterbefore and after the read. On20fe7434b's source all 4 fail (the three reader tests change the file's bytes and schema; theonex metering --dbtest does too). Ond36c97996all 4 pass.tests/projection,tests/unit/projection,tests/unit/local_deployment,tests/unit/cli, the wholetests/unit/nodes/node_delegate_skill_orchestrator, and the local-port and tenant-identity suites.ruff check,ruff format --checkandmypy --stricton the touched source are clean.tests/ciwas also run on this branch: 1472 passed, 23 failed, all intest_check_wire_compatibility_omn18868.pyandtest_release_staleness_gate_omn18010.py; the first failure read is agit tagsubprocess 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.Earlier update 2026-10-05 (head
20fe7434b)130d734d5(2d7e784b1);pyproject.tomlanduv.lockmerged 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) requiresproject.versionstrictly 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.20fe7434b: the no-op...after theProtocolMeteringRefreshPublisher.publishdocstring is removed (py/ineffectual-statement, alert 1508), and the lock test's worker thread catchesExceptioninstead ofBaseException(alert 1509). The test still sees theTimeoutErroror any other failure the refresh raises. Neither change alters behaviour.tests/projection,tests/unit/projection); ruff andmypy --stricton the touched source are clean.contracts/OMN-20007.yamlandtest_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)a435c6916addscontracts/OMN-19977.yaml: AC1 bound totests/unit/nodes/node_delegate_skill_orchestrator/test_local_dispatch_metering_refresh_omn19977.py(16 tests) and AC2 totests/test_omn19977_metering_renders_the_row.py(12). Both pass at the head and fail at the gate's control (dev43700e208plus the branch's tests and contract: 16 failed; 11 failed, 1 passed). The text below that says the contract "comes separately" is superseded.43700e208and005aa89f4; 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.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 delegatenow refreshes the metering rows itself, andonex meteringprints the storedmetering-summary.v1row 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 asba146f348, and this branch carries thatorigin/devmerge atd5569be3f; the predecessor order is satisfied. The implementation diff below remains described against #3368's reviewed headd7153cf4b.What changed
LocalDelegationDispatchPort._project_evidenceinsrc/omnimarket/nodes/node_delegate_skill_orchestrator/ports/port_local_delegation_dispatch.py. Onceproject_delegate_skill_terminalreports the terminal row written (rows_upserted > 0), the new_refresh_metering_summarycallsrefresh_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 toHandlerProjectionMeteringSummaryand the store-neutralstore_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 saysonex meteringwill show the row as stale.as_ofis 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-storeflockcovers 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.days=Nonefor 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 meteringrenders the row (AC2).src/omnimarket/cli/cli_metering.pyreads the row throughHandlerProjectionReadover the read-only store. That is the same readonex dashboardand the pages make, with the exposure loaded from the node contract. The command prints the row's columns withsummary_jsonspread 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 typedMETERING_SUMMARY_MISSING, and a row older than a recorded run, or priced against another manifest version, printsMETERING_SUMMARY_STALE. Both name the repair, exit 1, and print no figures (src/omnimarket/models/model_metering_row_refusal.py). JSON gainswindow_end, the one column it used to drop.--include-fixturesis now refused, because stored rows count real runs only.resolve_metering_baseline_model(), which isresolve_baseline_model(overlay={}, store={}), the same resolutioncompute_baseline_savingsmakes for each run.--baselinenow only names another stored row, and the window still defaults to all-time. An unpriced baseline printsBASELINE_UNRESOLVED(stderr in JSON mode, where the row carriesbaseline_state: unresolvedwith null savings) and never a number. The text output says unmeasured runs are counted and in no sum.tests/test_cli_metering_omn18697.pyand three CLI tests intests/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_daysbecametest_cli_manifest_change_is_reported_stale_not_refreshed. No file undersrc/omnimarket/nodes/node_projection_metering_summary/changed.How it was verified
Base
d7153cf4b(#3368 head, unchanged at 04:26Z); dev atb181b79a6was read at 04:26Z.tests/test_omn19977_metering_renders_the_row.pyand 16 intests/unit/nodes/node_delegate_skill_orchestrator/test_local_dispatch_metering_refresh_omn19977.py. Against the base source (exported withgit archive, with pytest'spythonpathpointed at it), 27 fail. A probe test in the same run confirmed it imported the base module. The one that passes on base istest_another_tenants_row_is_never_printed, existing behaviour that had no test. With the base source, the two rewrittentest_projection.pyCLI tests fail and the reworked existing tests pass. On the branch, all 60 tests across those four files pass. The AC4 tests runHandlerDelegateSkillwith 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.tests/nodes/node_projection_metering_summary. Foldas_offrom the wall clock: 10 failed. A tokenless run entering the sums: 2 failed. Refresh folding only the all row: 3 failed.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.tests/projection,tests/unit/projection, the projection read node, the golden-chain read effect, writer-store parity, the dev seed, and the wholetests/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.ruff checkandruff format --checkare clean on the 8 changed files.mypy --strict src/omnimarketreports no issues in 4115 files.check_node_metadata_dependencies.pyandnode_runtime_sweep --import-checkexit 0. The repo pre-commit at the signed commit3f371f3e5, run with the worktree venv first onPATHand Homebrew bash 5.3: 63 passed, 51 skipped (no matching files), 0 failed.uv lock --checkpasses.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:
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.projection_column_missingotherwise, and the CLI turns that into an error, not a number.Open defects
pyproject.tomlanduv.lockare at 0.4.301, equal to dev130d734d5and above the latest published tag v0.4.300. The sibling PRs that also bump (OMN-20006 feat(OMN-20006): usage-by-model-day keeps measured cost apart from estimates #3378, OMN-20009 feat(OMN-20009): model-routing serves the run-locally share and metering-summary the saving per measured run #3406, OMN-20008 feat(OMN-20008): enforce measured savings provenance #3361, OMN-19980 test(OMN-19980): a CI guard holds savings to one definition in SQL #3373) take a fresh bump if dev moves first.d36c97996: it ismode=roand the store's bytes, mtime and schema are unchanged on an older store (see the top update). Other callers ofread_local_tenant_identity(the delegate handler,onex local init) get the same read-only read, with identical outcomes.Known, accepted limit
read_served_rowwalks the exposure's pages, up to 100.metering_summaryhas no column unique per tenant, so the cursorwindow_startis 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 asMETERING_SUMMARY_MISSING.Decisions
BASELINE_UNRESOLVEDgoes 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 printsBASELINE_UNRESOLVEDin place.Local gates
--no-verify, noSKIP=, no--no-gpg-sign, nocore.hooksPathoverride. Every pre-commit and pre-push hook ran.Two hooks failed during the work and were fixed at the input:
canonical-file-shaperefused a# type: ignorein a test, and later a new module underprojection/. The test now uses a plain stub, the reader moved into the CLI, and the refusal model went tomodels/.registry-projection-reconcilefired when a module the delegate port imports SQL-readmetering_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.pyinside_project_evidence, plus the version lines. #3378 adds the usage-by-model-day fold inside the evidencetry, and this change captures the terminal time there and refreshes metering after theexcept. Both are needed: they are different projections fed by the same terminal.git merge-treeof this commit with #3378's head24ff8612amerges clean.Overlap-Reviewed: #3406 — shares only
pyproject.tomlanduv.lock, the same 0.4.300 line. It adds thesavings_per_measured_run_usdcolumn tometering_summary, which is why the CLI prints the served row's columns as served rather than a fixed list. Merged with #3406's head3f360db02(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 servesmetering-summary.v1, and this change reads that exposure through the read node. #3368 has now merged asba146f348, and the signedorigin/devmerge atd5569be3fhas 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.pycomes 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=inferenceto 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 currentorigin/devis already merged into #3407.Not in this change
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.onex delegate.Evidence-Ticket: OMN-19977
Evidence-Source: OCC#12865