Repository navigation
feat(OMN-20006): usage-by-model-day keeps measured cost apart from estimates - #3378
Conversation
…timates The Usage page must count runs whose cost was not measured and leave them out of the cost it shows (AC3), and must never show 0 for a cost nobody measured (AC4). The projection stored every call's cost as a number and had no idea how that number was obtained, so no page could do either. Each call now carries its usage_source (unknown when the event does not say, so an estimate is never taken for a measurement). Each day-and-model row serves measured_cost_usd, the sum over measured calls and NULL when there were none, and unmeasured_call_count. cost_usd and call_count keep their meaning. Migration 0002 is additive; old rows read unknown/NULL/0. The exposure now declares tenant_column, so the server scopes a read to the reading tenant instead of the page filtering rows in the browser. The local onex delegate path writes the usage rows too, keyed by the run's correlation id and dated by the terminal's emitted_at; a failure there is logged and swallowed like the call-metrics write. A local store also lacked projection_cursor, so a served read of this exposure answered 503 projection_column_missing on every local install. The store now adds the missing columns on connect and stamps the cursor on each insert and recount from a one-row sequence table, as Postgres's BIGSERIAL does from its sequence. The consumers opt-out is deleted: the omnidash Usage page is the reader. Version 0.4.294: packaged source changed (0.4.293 is published). Onex-Lane: omn20006-usage-page Onex-Session: d99c149d-35af-4f1e-b791-28a28187bfc4
|
| 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:995: self._effect_handler = effect_handler or HandlerLlmDelegationCall()
src/omnimarket/nodes/node_delegate_skill_orchestrator/ports/port_local_delegation_dispatch.py:996: self._projection_handler = projection_handler or HandlerProjectionDelegation(
src/omnimarket/nodes/node_delegate_skill_orchestrator/ports/port_local_delegation_dispatch.py:3421: HandlerProjectionLlmCost().project(call_event, self._evidence_db)
src/omnimarket/nodes/node_delegate_skill_orchestrator/ports/port_local_delegation_dispatch.py:3513: HandlerProjectionUsageByModelDay().handle(usage_event),
src/omnimarket/nodes/node_projection_usage_by_model_day/handlers/handler_usage_by_model_day_writer.py:95: self._derive = HandlerProjectionUsageByModelDay()\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) |
…imit The ticket_contract model caps dod_evidence ids at 50 characters. The AC1 id was 51, so contract-validation failed on the PR before any check ran. The id is a label only; nothing else references it. Onex-Lane: omn20006-ci Onex-Session: d99c149d-35af-4f1e-b791-28a28187bfc4
…tuple target CodeQL flagged the single-target tuple unpack of the loader's result as a length mismatch (alert on tests/test_omn19978_usage_by_model_day.py:386) and failed the PR's CodeQL check. The sibling test already asserts the length and indexes, so this one does the same and still fails if the loader returns two exposures. Onex-Lane: omn20006-ci Onex-Session: d99c149d-35af-4f1e-b791-28a28187bfc4
…he dispatch port stops reaching into a sibling node The local delegation dispatch port imported ModelUsageCallEvent from node_projection_usage_by_model_day.models, tripping test_no_new_cross_node_model_reach_ins. The model now lives in omnimarket.models.model_usage_call_event; the node's module re-exports it. Onex-Lane: omn20006-reachin Onex-Session: d99c149d-35af-4f1e-b791-28a28187bfc4
|
OCC autobind did not rebind this PR. This PR's evidence-source stamp line names To clear this: Reported by |
…3378 OCC companion by node_pr_lifecycle_fix_effect (OMN-13317 F1 / OMN-13990 / OMN-14285). Product PR head 7f9f4ab7df46c9768e2c40c31f16f7e63e68db52.
…de-ai/omnimarket#3378 (head 7f9f4ab7df46c9768e2c40c31f16f7e63e68db52) Executed declared pytest check in omnimarket checkout: 38 passed, 12 skipped, exit 0, captured with check_receipt_hardening --capture-probe Fixes occ-preflight / eligibility and verify / verify on OCC#12830 (reason nonpass_receipt)
…market (#12830) * evidence(OMN-19432, OMN-20006): author OCC companion for OmniNode-ai/omnimarket#3382 OCC companion by node_pr_lifecycle_fix_effect (OMN-13317 F1 / OMN-13990 / OMN-14285). Product PR head cf731a4ec36fdc2c4740d45b934314cb0d3d4e66. * evidence(OMN-19432, OMN-20006): self-bind OCC#12830 + rebind contract_sha256 * evidence(OMN-16859): executed test_passes receipts for OmniNode-ai/omnimarket#3382 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/37227641030 * evidence(OMN-20006): supersede PENDING test_passes receipt for OmniNode-ai/omnimarket#3378 (head 7f9f4ab7df46c9768e2c40c31f16f7e63e68db52) Executed declared pytest check in omnimarket checkout: 38 passed, 12 skipped, exit 0, captured with check_receipt_hardening --capture-probe Fixes occ-preflight / eligibility and verify / verify on OCC#12830 (reason nonpass_receipt) --------- Co-authored-by: omnimarket-bot <bot@omninode.ai> Co-authored-by: onexbot-occ-writer[bot] <307849072+onexbot-occ-writer[bot]@users.noreply.github.com> Co-authored-by: jonahgabriel <jonah.gabriel@gmail.com>
The PR read DIRTY against dev. The only conflict was the import block of port_local_delegation_dispatch.py: this branch imports ModelUsageCallEvent from omnimarket.models, and dev (#3399/#3404 line) imports dispatch_progress from the same node. They are independent, so both are kept in isort order. sqlite_database.py and the writer/store parity test merged without conflict. Dev's delegation_events reconcile (#3319) and this branch's usage-by-model-day reconcile run one after the other in _connect, each in its own BEGIN IMMEDIATE transaction. Onex-Lane: omn20006-usage-page Onex-Session: d99c149d-35af-4f1e-b791-28a28187bfc4
After merging dev, v0.4.299 is reachable from this branch and dev itself reads 0.4.299, so the release-identity gate (OMN-16344) refuses a src/ change at that version: check_release_identity.py --base origin/dev exited 1 at the merge commit and 0 after this bump. 0.4.300 is the number the other open omnimarket PRs and the post-release bump PR already use. Onex-Lane: omn20006-usage-page Onex-Session: d99c149d-35af-4f1e-b791-28a28187bfc4
Dev gained #3405 (OCC receipt runner, scripts/ci and its test only) after the previous merge. No conflict and no src/ change, so the 0.4.300 bump still holds.
Dev's llm_call_metrics relabel (#3183) runs an UPDATE on every connect once that table has usage_source, which every store written by the current release does. The sqlite3 module opens a transaction before that UPDATE and leaves it open until _connect commits at the end. The usage-by-model-day reconcile sits between the two and issues BEGIN IMMEDIATE, so on such a store it raised "cannot start a transaction within a transaction": the first open after upgrading failed, and concurrent first opens (dev's #3319 test) failed 4 of 5 runs. The reconcile now commits that open transaction before taking the write lock. The relabel is idempotent and _connect committed it anyway, so nothing is lost; the new test asserts the relabel is kept, and a rollback in place of the commit fails it. Onex-Lane: omn20006-usage-page Onex-Session: d99c149d-35af-4f1e-b791-28a28187bfc4
…-page dev opened v0.4.301 after v0.4.300 was published, so the 0.4.300 version lines on this branch conflicted. Both version lines now take dev's 0.4.301, which is ahead of the published v0.4.300; nothing else conflicted. Onex-Lane: omn20006-usage-page Onex-Session: d99c149d-35af-4f1e-b791-28a28187bfc4
dev moved while this branch was being brought current; the second merge is clean, so no file needed a resolution.
Onex-Lane: omn20006-usage-page Onex-Session: d99c149d-35af-4f1e-b791-28a28187bfc4
…ore step #3370 (OMN-19968) turned the llm_call_metrics relabel into a one-time store step: it runs in its own write transaction that commits it with its row in omnimarket_sqlite_store_steps, a store that has recorded it opens with reads only, a read-only store is read as it is with a warning, and a busy store raises. This PR's usage reconcile still ran as its own connect-time write, outside that mechanism, so a read-only store written before this change raised "attempt to write a readonly database" on every read of every table. The reconcile is now the store step omn20006_usage_by_model_day_measured_cost (the SQLite counterpart of usage_by_model_day migration 0002). _connect runs every pending step through one _apply_store_steps: each step's columns or relabel and its record commit together, the columns are read again under the step's write lock so racing first opens add each once, a read-only store gets one warning naming every step it lacks, and any other error raises. The schema-probe check and the in_transaction commit guard are gone; the guard was dead once #3370 moved the relabel out of the connect-time DDL, and its comment described code that no longer exists. Tests: tests/unit/projection/test_sqlite_usage_by_model_day_store_step_omn20006.py (five fail on the previous head). The OMN-19968 vocabulary tests read only their own step's record, since the store now records a second step. Onex-Lane: omn20006-usage-page Onex-Session: d99c149d-35af-4f1e-b791-28a28187bfc4
The local path wrote one usage call per run: the final model, the final attempt's tokens and the run's cumulative cost, always as measured. On an escalated run that put every rejected attempt's spend on the model that answered, so the Usage page's cost per model was wrong for that run even though the total was right, and an attempt whose cost was not measured was counted as measured because the last one was. The dispatch loop now records what each banked call consumed (model, tokens, cost, usage source) at the same two points it adds to the run's cumulative cost, so the two cannot drift. Each call that reached a provider becomes its own usage call with its own usage source; the per-model sum still equals the delegation row's cost_usd. A call that never reached a provider carries no tokens and no cost and is not a usage call. A single-call run keeps the correlation id as its key; several calls take <correlation id>:<n> in attempt order. Onex-Lane: omn20006-usage-page Onex-Session: d99c149d-35af-4f1e-b791-28a28187bfc4
AC1 binds the Usage page's token totals to onex metering --json, which reads delegation_events and so counts only the deciding attempt's tokens. Booking every attempt's tokens made an escalated run's usage tokens larger than metering's for the same window, which is AC1's falsifier. Cost stays split per attempt and per model; tokens now go on the deciding attempt's call only, and earlier attempts' calls carry 0. The token columns are NOT NULL on both stores, so 0 cannot be told apart from a measured zero without a schema change. Counting escalated-from attempts' tokens belongs in the metering summary, so the CLI and the page move together; that is a follow-up, not this change. Onex-Lane: omn20006-usage-page Onex-Session: d99c149d-35af-4f1e-b791-28a28187bfc4
# Conflicts: # src/omnimarket/nodes/node_delegate_skill_orchestrator/ports/port_local_delegation_dispatch.py
…mniNode-ai/omnimarket#3378 OCC companion by node_pr_lifecycle_fix_effect (OMN-13317 F1 / OMN-13990 / OMN-14285). Product PR head cb28b12d38199ae9d3d680d95d192b2a4bf89ca3.
…ring usage_by_model_day 0002 The Sibling Release Compatibility Gate (OMN-20381) refuses a node migration until the pinned omnibase_infra release carries it byte-identical. 0.38.67 (tag 2bbaaeb) vendors 0002_usage_by_model_day_measured_cost.sql via omnibase_infra#4559; 0.38.66 does not. The lock moves omnibase-infra only. Onex-Lane: omn20006-usage-page Onex-Session: d99c149d-35af-4f1e-b791-28a28187bfc4
…or --json omnibase_infra#4609 (OMN-20124, in 0.38.67) prints the plain answer when stdout is redirected; JSON now comes only with --json. The probe parsed stdout as JSON without asking for it, so on 0.38.67 every field read None and the Customer Clean-Install and Sibling Release delegate gates failed on dev and on every PR. --json exists in 0.38.66 and 0.38.67, so the probe now works on both: local runs FAIL unchanged on 0.38.67, PASS with --json on 0.38.67 and on 0.38.66. Onex-Lane: omn20006-usage-page Onex-Session: d99c149d-35af-4f1e-b791-28a28187bfc4
…the lock The 0.38.67 bump moved pyproject.toml and uv.lock but left seven workflow and test references on v0.38.66, so test_infra_checkout_pins_match_lock failed (Tests split 2/20). The same files the 0.38.66 bump touched (omnimarket#3417) now name v0.38.67; tests/ci/test_sibling_pins_omn20001.py and test_ci_bus_overlay_parity.py pass (29). Onex-Lane: omn20006-usage-page Onex-Session: d99c149d-35af-4f1e-b791-28a28187bfc4
…ge-page # Conflicts: # pyproject.toml
…e read-only one CodeQL flagged the store step's except block (alert 1545), and our merge rule needs every review thread resolved. It caught BaseException to roll back, then re-raised everything except a read-only store. It now catches sqlite3.OperationalError for the read-only case, and keeps a bare "except BaseException: rollback; raise" so any other failure, an interrupt included, still releases the write lock before it propagates. The behaviour is unchanged. That rollback was never pinned: removing it passed every test. A new test holds the error and checks another writer can take the lock at once; it fails with "database is locked" when the rollback is removed. The thread-safety test now records Exception, not BaseException (alert 1546). Onex-Lane: omn20006-usage-page Onex-Session: d99c149d-35af-4f1e-b791-28a28187bfc4
|
Close-out for OMN-20006: merged 2026-10-07 08:29Z as
Thank you! |
|
AC3 and AC4 lab readback for OMN-20006, on the merged code: a fresh install on the .201 lab of omnimarket How the unmeasured run was made. On the local delegate path every answered call is recorded as measured, so a real local run cannot be unmeasured; unmeasured usage comes from the hosted path. After two real local delegations, one call with
With the 2026-10-06 readback (AC1, AC2), all four criteria now have a lab readback on merged code. 1 sources read: the two served exposures before and after, the CLI metering JSON, both page captures · 2 live at 09:48Z · 3 weakest claim checked: that the page reads the new row and not a stale one: the Unmeasured column moved none → 1 while measured cost stayed $0.00. Thank you! |
Update 2026-10-05 08:20Z (head
55bdc6066)55bdc6066(was24ff8612a). The two commits after24ff8612aare merges of dev by headline:a15a50f6f(v0.4.301 open, chore(OMN-18010): post-release dev version bump to 0.4.301 #3409) and55bdc6066(91b8bb1, fix(OMN-20558): burn down the action-pin, transport-mock and runtime_profiles ratchets to plain checks #3408). The verification below covers24ff8612aand was not re-run at55bdc6066.pyproject.tomlat55bdc6066, 2026-10-05 08:16-08:17Z), not the 0.4.300 stated below.node_projection_usage_by_model_day0002 (the latest release read, v0.38.65 of 2026-10-04T04:30:05Z, does not); then this PR, which also bumps its omnibase-infra pin to that release; and omnidash#358 after omnidash#350. The Sibling Release Compatibility Gate requires the release before this PR: its failure at this head reads that0002_usage_by_model_day_measured_cost.sql"is not in the pinned omnibase_infra release v0.38.65 ... and no omnibase_infra release vendors it yet" (job 111658862982).f32f25d82), omnidash#350 is OPEN. Merge state BLOCKED; failing contexts: Sibling Release Compatibility Gate, CI Summary, Coverage Sweep Gate, Repo Evidence Dependency (4), occ-preflight eligibility, repo-evidence / dod-verify, OCC Attestation Observe (report-only); only the Sibling gate's cause was read.Summary
This is the data half of OMN-20006.
usage-by-model-day.v1now keeps measured cost apart from cost that was estimated or not recorded, the read is scoped to the reading tenant, and the localonex delegatepath writes the usage rows the Usage page reads. Before this, an unmeasured run's cost was stored as a number, so no page could count it or leave it out (AC3, AC4).What changed
usage_source.ModelUsageCallEventdefaults it tounknown, so an event that never says how its cost was obtained is not taken for a measurement. The legacy aliasAPIstill reads as measured. The fold carries the source intoModelUsageCallDelta, and the store writes it on the call row.measured_cost_usd: the sum over measured calls, and NULL when there were none, never 0.unmeasured_call_count: the count of the other calls.cost_usdandcall_countkeep their meaning (every call).handler_usage_by_model_day_store.py. The Postgres recount inhandler_usage_by_model_day_writer.pyusesSUM(cost_usd) FILTER (WHERE usage_source = 'measured')andCOUNT(*) FILTER (WHERE usage_source <> 'measured').0002_usage_by_model_day_measured_cost.sqlis additive only. Old rows readunknown, NULL and 0 until their key is recounted.tenant_column: tenant_id.consumers: noneopt-out is deleted, because the omnidash Usage page is now the declared reader, in omnidash's component registry, which is where omnibase_infra's exposure-reader-coverage gate resolves readers.projection_cursor, so a served read of this exposure from any local store answered503 projection_column_missing. Measured at omnimarket dev8ee3d6103with the read node, before this change. Now:BEGIN IMMEDIATE;port_local_delegation_dispatch.pyfolds the run's calls into the usage tables, dated by the terminal'semitted_at. Each attempt that reached a provider is its own call, on its own model, with its own cost and usage source; tokens go on the deciding attempt's call only, asonex meteringcounts them (see the 2026-10-06 08:59Z update). A single-call run is keyed by its correlation id; several calls take<correlation id>:<n>. Each write is in its own guard, so a failed usage write is logged and never breaks the response or the call-metrics write.contracts/OMN-20006.yamlbinds AC1 (data half), AC3 and AC4 touv run pytestchecks.llm-call-completedon deployed lanes, andcost_usdandcall_countsemantics are unchanged.How it was verified
Update 2026-10-06 08:59Z: each attempt's cost is booked to its own model (head
21ef659)The independent review (2026-10-06 08:13Z) found that the local path booked an escalated run's whole cost to the final model as one measured call. Two signed commits on
f0e1f37fix it:8307a16: the dispatch loop records what each call consumed at the same two places it adds to the run's cumulative cost, so the two cannot drift. Each attempt that reached a provider becomes its own usage call on its own model, with its owncost_usdand its ownusage_source(from the effect result, no longer hardcoded measured). A call that reached no provider carries no tokens and no cost and is not booked, so the per-model sum still equals the delegation row'scost_usd(AC2).21ef659: tokens follow the one definition AC1 binds to.onex metering --jsonreadsdelegation_events, which records only the deciding attempt's tokens, so only the deciding attempt's call carries tokens and earlier attempts' calls carry 0. The token columns areNOT NULLon Postgres (migration 0000) and SQLite, and the row has no token usage source, so this 0 cannot be marked as "not counted" without a schema change. It is not a measured zero.tests/unit/nodes/node_delegate_skill_orchestrator/test_local_dispatch_usage_by_model_day.py): an escalated run (local, then cheap_cloud) gets per-model cost 0.001 and 0.010, the sum equals the delegation row's cost, and the window's usage tokens equal metering's (33, 44); a failed run across three models books each model's own cost and the tokens equal metering's (55, 66); an attempt whose cost was not measured readsmeasured_cost_usdNULL with 1 unmeasured call; a call that reached no provider is not a usage call.f0e1f37: 3 failed, 4 passed (the no-provider test passes there too and guards against regression). RED at8307a16for the token rule: 2 failed, 5 passed. GREEN at21ef659: 7 passed.21ef659: book everything to the final model again, 3 failed; book every attempt's tokens, 3 failed. At8307a16: always mark the call measured, 1 failed; count calls that reached no provider, 1 failed (not re-run at21ef659).tests/unit/nodes/node_delegate_skill_orchestrator, the usage-by-model-day tests, and every test that calls_project_evidencedirectly;-m "not kafka and not integration"):f0e1f37553 passed, 3 skipped;21ef659557 passed, 3 skipped, 0 failed.-m "not kafka and not integration"suite. The run sat idle at 0% CPU with no output for 16 minutes and was stopped.ruff check,ruff format --check,mypy --stricton the changed src, andpre-commit run --files(54 passed, 0 failed) each exit 0.HandlerX(in a changed file; no factory or registry exists to use instead, and the same warning already fires at lines that predate this PR.Update 2026-10-06 04:48Z: dev brought in, usage reconcile moved into #3370's store-step mechanism (head
f0e1f37)The counts below supersede those for
55bdc6066.0614c3a58(no conflicts) as63850c3f8. The pins equal dev's (omnibase-infra floor>=0.38.66).f0e1f37moves the usage-by-model-day reconcile into its recorded one-time store steps.omn20006_usage_by_model_day_measured_cost, the SQLite counterpart of migration 0002._connectcalls one_apply_store_steps, which runs each pending step in its ownBEGIN IMMEDIATEtransaction and commits it together with its record row.attempt to write a readonly databaseon every read. That is the case fix(OMN-19968): a read through the SQLite adapter no longer takes a write lock #3370 made readable.tests/unit/projection/test_sqlite_usage_by_model_day_store_step_omn20006.py(6 tests). RED on63850c3f8: 5 failed, 1 passed (the passing one is a locked-store guard). GREEN: 6/6, with fix(OMN-19968): a read through the SQLite adapter no longer takes a write lock #3370's tests 35 passed._step_rowshelper is scoped to its own step, because the store now records two steps. No assertion was loosened.OperationalErroras read-only (the locked-store test fails); a deferredBEGIN(the racing-first-opens test fails 5 of 5 runs).-m "not kafka and not integration"): 1289 passed, 5 skipped, 0 failed.mypy --stricton the changed src andpre-commit run --fileseach exit 0.At head
24ff8612a, deve51774cd7(signed merges of dev at76a5ce6e2and0201ce57a, the 0.4.300 bump2262ee8f0, and the fix24ff8612a):_connectcommits. This PR's usage reconcile issuedBEGIN IMMEDIATEinside it, so a store written by the current release failed to open ("cannot start a transaction within a transaction"), and dev's concurrent-first-open test (fix(OMN-19976): the local store carries every column the delegation exposures declare #3319) failed 4 of 5 runs. The reconcile now commits that transaction first.test_a_store_written_by_the_previous_release_opens_and_keeps_its_relabelwas red before the fix and is green after; the concurrent test passes 20 of 20; removing the commit fails both (concurrent 7 of 10 red); a rollback in its place fails the relabel assertion.tests/projection,tests/unit/projectionandtests/unit/nodes/node_delegate_skill_orchestrator;-m "not kafka and not integration", the 7 files that start a local Postgres or container left out): dev 2226 passed, 5 skipped; branch 2245 passed, 5 skipped.scripts/check_release_identity.py --base origin/dev: exit 1 at the merge commit, exit 0 after the bump.ruff,ruff format --checkand strictmypyare clean on the changed files, and the full pre-commit run passed on each commit.Earlier, at head
2c2ed032c:Head
2c2ed032c(signed), rebased on deva1ae08f1f; the focused tests were re-run there (41 passed). The contract checks were re-run on basee71ca5378: 5, 15 and 3 failed there, and 5, 33 and 3 passed at the head. The suite counts below were measured on dev7cb86dbe8, before dev's release commit, which changed no source.Red, then green. At dev
8ee3d6103with the new tests overlaid: 19 failed and 18 passed. On the branch, the focused files pass (38).The contract's three checks fail at the merge base and pass at the head. Base is source
7cb86dbe8with the branch tests overlaid, the way the repo-evidence gate re-controls added evidence:Mutations: 9 of 9 caught. Each undoes one behaviour, and a test goes red:
usage_source;tenant_column;measured_cost_usdis not served.Related suites (every test file naming usage-by-model-day, the SQLite store, the local dispatch port, the topic map or
bus_backed, plustests/unit/projection,tests/unit/nodes/node_delegate_skill_orchestratorandtests/projection):7cb86dbe8: 2082 passed, 52 skipped;Real Postgres, on the lakshman lane on .201 (port 45436), in throwaway schemas, 2026-10-04 about 14:28Z:
usage_by_model_day(with every usage source) andmetering_summary.delegation_eventsandllm_call_metrics, withinvalid input value for enum usage_source_type: "MISSING". They fail the same way at dev8ee3d6103against the same database, so they are not caused by this change.Repo gates:
ruff check,ruff format --checkandmypy src/omnimarket/ --strictare clean (4111 files);tests/ci/test_repo_evidence_caller_workflow.py: 2 passed;scripts/check_release_identity.py --base origin/dev: exit 0;The reader gate, run locally. omnibase_infra's exposure-reader-coverage gate over this branch's contracts plus omnimarket#3368's metering-summary contract:
OK: 22 served bus_backed exposure(s), 0 without a reader;NULL parity. An independent review noted that the parity helper drops None values, so a NULL measured cost was never compared. The parity test now gives one day and model no measured call and compares
(measured_cost_usd, unmeasured_call_count)per key explicitly. It passes on the lakshman lane at 16:12Z. With the "no measured call reads 0" mutant it fails:(Decimal('0'), 3) == (None, 3).Not run: the lab readback (AC1's window sum against
onex metering --json, and AC2's per-model sum against the Overview spend on a fresh HOME). It needs this head installed on .201 and is the next step.Failure paths
try, and a raise is logged. Tested bytest_a_failing_usage_write_never_breaks_the_delegation: the delegation and call-metrics rows still land and no usage row is written.test_unmeasured_only_aggregate_has_typed_null_cost). A call that does not say how its cost was obtained is unmeasured.Open defects
none. Risks from the independent review, not fixed here and named so the merger sees them:
onex meteringcounts only the deciding attempt's tokens, so tokens spent by failed or escalated-from attempts are not counted anywhere. The fix belongs in the metering summary (one definition), so the CLI and the page move together. It is a follow-up and is not done here.llm_call_metricswrite from omnimarket#3069 still books an escalated run's whole cost to the final model. Out of this PR.llm-call-completedevent carries nousage_source, so it folds as unknown (Not measured) whilellm_costdefaults it to measured. No deployed emitter exists yet.unmeasured_call_count0 until recounted. 0 such rows on the lab and on dev today.projection_cursor.Local gates
--no-verify, noSKIP=, no--no-gpg-sign, nocore.hooksPathoverride. Every pre-commit and pre-push hook ran.MAX(...) FROM usage_by_model_dayas a registry read, so the cursor now comes from its own sequence table, as on Postgres.Not in this change
The omnidash Usage page, which reads these columns and is the declared reader, and the omnibase_infra vendoring of migration 0002 are separate PRs on this ticket.
Merge order:
omnidash goes before this one so the registry reader is in place before the opt-out deletion reaches omnibase_infra's pinned omnimarket.
Evidence for AC1, AC3 and AC4 is in onex_change_control#12823: test_passes checks that fail at the merge base and pass at this head.
Overlap-Reviewed: #3371 — omnimarket#3371 (OMN-19976) moves
_ensure_columnsunder the write lock; this PR adds the usage tables' own reconcile in_connectand does not touch_ensure_columns. Both also set the version to 0.4.300 inpyproject.tomlanduv.lock, the same value. Both are needed; whichever merges second rebases.Overlap-Reviewed: #3370 — omnimarket#3370 (OMN-19968) makes a read stop taking a write lock and runs one-time data steps, including the llm_call_metrics relabel, only on a write. This PR's usage reconcile commits the transaction that relabel leaves open in today's
_connect; once #3370 moves the relabel, that commit finds no open transaction and does nothing. #3370 merged first, so this PR moved the usage reconcile into #3370's store-step mechanism (commitf0e1f37, see the 2026-10-06 update). Both set the version to 0.4.300.Overlap-Reviewed: #3403 — omnimarket#3403 is the post-release bot bump of
pyproject.tomlanduv.lockto 0.4.300. This PR sets the same value because its src change needs it before #3403 lands; whichever merges second has no version change left.Overlap-Reviewed: #3402 — omnimarket#3402 is the earlier post-release bot bump, whose head also reads 0.4.300 in
pyproject.tomlanduv.lock. Same value as this PR, so whichever merges second has no version change left.Overlap-Reviewed: #3406 — omnimarket#3406 (OMN-20009): both PRs extend
tests/test_omn19968_writer_store_parity.pyto apply a follow-up migration (this PR usage-by-model-day0002, #3406 metering-summary0002) and refactor the same lines differently, sogit merge-treeconflicts there (2026-10-05 06:59Z). Whichever merges second keeps both, with each table applying its own follow-up migrations.sqlite_database.pymerges cleanly.Overlap-Reviewed: #3407 — omnimarket#3407 (OMN-19977) also edits
_project_evidenceinport_local_delegation_dispatch.py: it records the terminal time inside the evidence try and refreshes metering after the except; this PR adds the usage fold inside the try.git merge-treeis clean (06:59Z).Evidence-Ticket: OMN-20006
Evidence-Source: OCC#12823