Skip to content

fix(OMN-19968): the llm-cost writer speaks the shared usage-source vocabulary on SQLite and Postgres - #3183

Merged
jonahgabriel merged 9 commits into
devfrom
lakshman/OMN-19968-usage-source-vocab
Oct 4, 2026
Merged

jonahgabriel merged 9 commits into
devfrom
lakshman/OMN-19968-usage-source-vocab

Conversation

@Patel230

@Patel230 Patel230 commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Summary

node_projection_llm_cost now writes the shared usage-source vocabulary (measured / estimated / unknown) on both stores. It used to write the retired API / ESTIMATED / MISSING, which every database omnibase_infra has migrated refuses. That is why this ticket's own lab step failed (OMN-19968, Amendment 2, AC8–AC11). Merges after OmniNode-ai/omnibase_infra#4420, which vendors this PR's migration.

What changed

  • src/omnimarket/nodes/node_projection_llm_cost/handlers/row_llm_call_metrics.py: _usage_source() maps provider-reported usage to measured, ESTIMATED to estimated, and MISSING / UNKNOWN / absent / unrecognised to unknown. The retired labels are folded in case-insensitively. usage_is_estimated derives from it. The docstring that still described the old mapping is corrected.
  • src/omnimarket/projection/sqlite_database.py: usage_source defaults to 'unknown', and existing SQLite rows carrying a retired label are moved onto the shared vocabulary in place.
  • src/omnimarket/nodes/node_projection_llm_cost/migrations/0003_usage_source_shared_vocabulary.sql (new): renames an omnimarket-created usage_source_type's labels in place (RENAME VALUE, no table rewrite) and sets the default to 'unknown'. The type is resolved through search_path (to_regtype), because 0001's typname guard matched a same-named type in any schema. That is how an infra-migrated database ended up rejecting this writer. On a database that already carries the shared vocabulary it changes nothing.
  • Tests and fixtures in 7 files now use the shared values. New tests/unit/test_omn19968_usage_source_vocabulary.py (17 cases) pins the mapping, the SQLite schema default and reconciliation, the migration's in-place renames and the migration number's uniqueness.

Grepped for every remaining use of the retired values (spec approach step 2). Left on purpose: the reconcile CASE in sqlite_database.py, the test inputs that feed API/ESTIMATED to prove the mapping, a comment in savings migration 087 that quotes a producer's wire label, and EnumUsageSource._missing_ (src/omnimarket/enums/enum_usage_source.py, from OMN-10382), whose legacy-alias table maps the same old labels to the same shared values this writer now uses. No reader treats 'API' as a meaning of its own.

How it was verified

Commit 6c26fd7ec (signed, G), on dev c603479d9.

  • RED then GREEN, same base: the 8 changed test files fail on unfixed dev (35 failed, 92 passed, including 17/17 of the new file) and pass on this branch (127 passed, re-run on 6c26fd7ec).

  • The tests can fail (AC11): two mutations in the worktree only, each restored byte-identical (git hash-object checked):

    • mapping api back to estimated → 5 tests fail;
    • the SQLite reconcile sending MISSING to estimated → 1 test fails.
  • Related suites, dev versus branch at the same sha: every test file that touches llm_call_metrics, usage_source, sqlite_database or node_projection_llm_cost (60 files) on dev ce2f9cfba and on the branch on ce2f9cfba: dev 1015 passed, branch 1032 passed (+17, the new file). The same single failure on both, tests/integration/test_cost_event_publisher_kafka.py::test_idempotency_key_is_stable_on_reprocess, is environmental: no Kafka at localhost:19092.

  • Lab, the ticket's own step (AC10): tests/test_omn19968_writer_store_parity.py on the lakshman lane's PostgreSQL 16 on .201, in a throwaway container on the lane network (2026-10-01T10:51:49Z): 8 passed, 0 failed. The same file with the same method on unfixed dev at 07:46Z: 6 passed, 2 failed (invalid input value for enum usage_source_type: "MISSING").

  • Migration 0003 on real PostgreSQL 16.15, lakshman lane (AC9), 10:08:06Z, throwaway databases dropped afterwards: 14 of 14 assertions in 4 starting states:

    • omnimarket-built with stored rows: renamed, rows kept, default 'unknown', 'API' refused afterwards, idempotent;
    • shared vocabulary already present: unchanged;
    • no type at all: clean no-op;
    • same-named type in another schema: untouched.

    Mutation control: removing the MISSING rename fails 3 assertions.

  • mypy src/omnimarket/ --strict: no issues in 3966 files. ruff check and ruff format --check: clean.

  • scripts/ci/check_wire_compatibility.py over the changed files: WIRE_COMPAT_NO_WIRE_CHANGE. Positive control: a wire model file gives WIRE_COMPAT_PASS.

  • scripts/check_release_identity.py --base origin/dev: 0.4.272 ahead of the published 0.4.271, so no bump is needed.

  • check_node_metadata_dependencies.py: PASS. node_runtime_sweep --import-check: exit 0.

Update 2026-10-03, the pin (commit 0e413dfbe, signed G): the Sibling Release Compatibility Gate (OMN-20381) refused migration 0003 because the pin, 0.38.61, is a release without it. The floor is now omnibase-infra>=0.38.63,<0.39.0 in both dependencies and override-dependencies; omnibase_infra#4420 (98620a57) is first released in v0.38.63. uv.lock was refreshed for omnibase-infra only with uv 0.12.22, the version CI installs: revision stays 5, 10 lines change, no other package moves; uv lock --check passes.

  • The gate's own comparison run locally against omnibase_infra tags: v0.38.61 lacks docker/migrations/forward/nodes/node_projection_llm_cost/0003_usage_source_shared_vocabulary.sql; v0.38.63 carries it byte-identical.
  • Related suites (-k "llm_cost or llm_call or usage_source or metering or parity", -m "not kafka and not integration") on dev 76e1673d0 and on this branch merged with it: dev 795 passed, 5 skipped, 0 failed; branch 816 passed, 1 skipped, 0 failed.
  • AC11's mutation re-run on the new head: removing the "api" synonym fails 3 of the 17 vocabulary cases; restored, 17 pass.
  • Two merge commits on this branch, c47936adc and 94eb9c2a1, were made by GitHub's update-branch and are signed by GitHub (verified: true on the commits API); the local check cannot verify GitHub's key.

Overlap-Reviewed: #3172 — both edit tests/test_projection_handlers.py in separate hunks: #3172 adds model_cloud_baseline to two delegate-skill fixtures (near lines 488 and 847); this changes one expected usage_source value in TestLlmCostHandler (near line 208) from 'MISSING' to 'unknown'. No shared lines; each is needed for its own change and they merge in either order.

Overlap-Reviewed: #3309 — it raises the same omnibase-infra floor to 0.38.63 for the repo (OMN-17427). This PR needs that floor for its own migration and cannot wait on #3309, which is red on other checks; the two produce the same pin, so whichever merges second takes either side of the pyproject and uv.lock hunks.
Overlap-Reviewed: #3308 — it bumps omnimarket's own version line in pyproject.toml and uv.lock (0.4.284 to 0.4.285); this PR changes the omnibase-infra dependency lines. Different lines, both needed.
Overlap-Reviewed: #3245 — it bumps omnimarket's own version line (0.4.282 to 0.4.283); same reasoning as #3308.
Overlap-Reviewed: #3293 — it adds nullable finish_reason and truncated to delegation_events in sqlite_database.py; this PR changes only llm_call_metrics there. Different tables, both needed.

Failure paths

n/a: this change writes no receipt, verdict or report.

Open defects

none

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.

The pre-commit dependency health gate first failed because it ran under the system Python 3.9 (datetime.UTC missing). It passed once the worktree's own venv was first on PATH, as the workflow rules require; no hook was skipped.

Not in this change

  • node_projection_tenant_credentials's SQLite path. Per the ticket's Amendment 1 it is proposed to move to OMN-19985, pending Jonah. AC7's reply on the ticket waits for Lakshman's YES.
  • savings_estimates.usage_source and any other table's vocabulary (out of scope in the plan).
  • Merging; that is Jonah's.

Evidence-Ticket: OMN-19968
Evidence-Source: OCC#12273

…cabulary on SQLite and Postgres

node_projection_llm_cost wrote the retired labels API/ESTIMATED/MISSING, while
omnibase_infra migration 077 (OMN-10382) moved the shared usage_source_type to
measured/estimated/unknown (EnumUsageSource). On any database infra has migrated,
0001's typname guard matched the shared type and created nothing, so every write
of 'MISSING' or 'API' was refused; the ticket's own lab step failed 6 passed,
2 failed on the lakshman lane at 07:46Z.

- row_llm_call_metrics maps provider-reported usage to measured, ESTIMATED to
  estimated, and MISSING/UNKNOWN/absent/unrecognised to unknown (retired labels
  folded in, case-insensitive).
- The SQLite schema defaults to 'unknown', and existing SQLite rows are moved
  onto the shared vocabulary in place.
- Migration 0003 renames an omnimarket-created usage_source_type's labels in
  place (RENAME VALUE, no table rewrite), resolving the type through
  search_path; a no-op where the shared vocabulary is already present.
- Tests and fixtures use the shared values; a new test file pins the mapping,
  the SQLite reconciliation and the migration's uniqueness.
@github-actions

github-actions Bot commented Oct 1, 2026

Copy link
Copy Markdown

✅ Architectural Review — PASSED

Errors: 0
Warnings: 0

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

No architectural violations found.

Static architectural lint — no model inference (OMN-14176).

@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

✅ Hostile Reviewer — PASSED

Blocking findings (critical/error): 0
Total findings: 12
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)

onexbot-occ-writer Bot pushed a commit to OmniNode-ai/onex_change_control that referenced this pull request Oct 1, 2026
…3183

OCC companion by node_pr_lifecycle_fix_effect (OMN-13317 F1 / OMN-13990 / OMN-14285). Product PR head 6c26fd7eced89036122cc17842bd43a7947a119f.
onexbot-occ-writer Bot added a commit to OmniNode-ai/onex_change_control that referenced this pull request Oct 1, 2026
…nimarket#3183

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

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

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

* evidence(OMN-19968): self-bind OCC#12139 + rebind contract_sha256

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

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

---------

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

Copy link
Copy Markdown
Contributor

OCC autobind rebound this PR's evidence-source stamp line to OCC#12273.

The receipt gate's own eligibility validator, run against the change-control tree the gate pins for OCC#12273, returned eligible for head 94eb9c2a1f5eff16adb8ffb5fa0fa203eed09f58. The body now carries exactly one stamp line. Displaced: OCC#12139.

Reported by occ_companion_emitter (OMN-18853).

…the parity tests

The repo-evidence gate (OMN-20070) requires a PR that cites OMN-19968 to
carry contracts/OMN-19968.yaml, with every acceptance criterion bound by
binds_ac to a test_passes item naming an existing test file.

AC1 binds to the rows_equal parity tests in
tests/test_omn19968_writer_store_parity.py (the four writers, real SQLite
adapter against real Postgres; the Postgres half skips without a Postgres).
AC2 binds to the postgres_only_sql tests (a recording SQLite connection
refuses $n placeholders, ::jsonb and enum casts on every writer's path).

node_dod_verify --execution-audience hosted on this head: PASS, 2 of 2
verified, both behavior-proving.
…fails at the merge base

The repo-evidence gate overlays a PR's tests on the merge-base source and
requires every bound test to fail there. The parity tests already pass on
dev (they were delivered by #3067 and #3148), so binding them read as
always-pass. AC1 now binds to tests/unit/test_omn19968_usage_source_vocabulary.py,
the test this change adds: against the merge-base source (2c6dabe) it
fails 17 of 17, on this head it passes. AC2 is not changed by this PR and
is no longer claimed here.
@Patel230
Patel230 requested a review from jonahgabriel October 3, 2026 05:07
…ndors migration 0003

The Sibling Release Compatibility Gate (OMN-20381) refused
node_projection_llm_cost/migrations/0003_usage_source_shared_vocabulary.sql:
it reads the omnibase-infra pin from uv.lock and requires every node
migration a change adds to be byte-identical in that release. The pin was
0.38.61, which lacks it; omnibase_infra#4420 (98620a57) vendored it and is
first released in v0.38.63.

Raise the floor to >=0.38.63,<0.39.0 in dependencies and
override-dependencies, and refresh uv.lock for omnibase-infra only (uv
0.12.22, the version CI installs: lock revision stays 5, 10 lines change,
no other package moves). The gate's comparison run locally: v0.38.61 lacks
0003, v0.38.63 is byte-identical.

Onex-Lane: omn19981-evidence
Onex-Session: bf37a08791cf43ccab41e737a008f469
…v0.38.63

tests/ci/test_sibling_pins_omn20001.py::test_infra_checkout_pins_match_lock
failed on this PR's CI (Tests split 2/20, run 37138844983): six workflows
still checked out omnibase_infra at v0.38.61 after the lock moved to
0.38.63. Move every such checkout, and the messages that name the version,
to v0.38.63, and the one test that pins the overlay-parity checkout ref.
These are the same edits omnimarket#3309 makes for the same pin.

tests/ci, tests/seam_goldens and the pricing tests on this branch and on
dev at the merge base 76e1673 fail the same 23 tests locally (release
staleness and others; identical sets, environmental here) and nothing else.
The pin test passes; restoring one v0.38.61 ref fails it.

Onex-Lane: omn19981-evidence
Onex-Session: bf37a08791cf43ccab41e737a008f469
@Patel230

Patel230 commented Oct 3, 2026

Copy link
Copy Markdown
Contributor Author

Answers to the two hostile-review notes on this head (job 111263134881, both info, from gpt-oss-review):

  1. _usage_source and EnumUsageSource inputs (row_llm_call_metrics.py): not a defect. EnumUsageSource is a StrEnum (enum_usage_source.py:12), so str(member) is its plain value, and an enum or a string maps to the same member at row_llm_call_metrics.py:64. The projection handler also passes event.model_dump(mode="json") (handler_projection_llm_cost.py:126), so the value arriving there is already a string. No change.
  2. SQLite default not migrated for existing databases (sqlite_database.py): the rows are migrated. _reconcile_legacy_llm_call_metrics runs on every open (sqlite_database.py:288) and rewrites API, ESTIMATED and MISSING to measured, estimated and unknown (:309-318), and running it again changes nothing. Only the column default of a table created before this change stays 'MISSING': SQLite cannot alter a column default, and :211 applies to new tables. Nothing relies on that default, because build_llm_call_metrics_row always sets usage_source (row_llm_call_metrics.py:198) and the writer stores the whole row (handler_projection_llm_cost.py:129-140). No change.

Thank you!

…ge-source-vocab

dev raised the omnibase-infra floor to 0.38.64 (omnimarket#3293), past the
0.38.63 this branch pinned for migration 0003. Nine files conflicted on that
one value (pyproject.toml, uv.lock, six workflows and one CI test); each
takes dev's version, so this branch no longer changes any of them.

Onex-Lane: omn19968-usage-vocab
Onex-Session: b4a9c00fa6f045f4b54fca18b2cdd0e7
@jonahgabriel

Copy link
Copy Markdown
Contributor

Thanks, @Patel230 (Lakshman), for omnimarket#3183 on ticket OMN-19968, which was squash-merged as 5de6e9d on 2026-10-04T23:13:51Z by the landing lane under the operator ruling of 2026-10-04T23:07:50Z that overrode the report-only landing policy for the collaborator pull requests listed in the standups ask. We really appreciate your contribution!

Patel230 added a commit that referenced this pull request Oct 5, 2026
… the merge base's contract does not carry

repo-evidence / dod-verify refused 0ebc5ef: after #3183 merged, contracts/OMN-19968.yaml on dev binds only the usage-source vocabulary check, so every check this PR bound was "carried unchanged from the merge base" and the PR bound no evidence of its own (run 37257169127). Add a check on the read-lock tests; the selected six fail on origin/dev cce192a and pass here.

Onex-Lane: omn19981-evidence
Onex-Session: bf37a08791cf43ccab41e737a008f469
Patel230 added a commit that referenced this pull request Oct 5, 2026
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
Patel230 added a commit that referenced this pull request Oct 5, 2026
…rite lock (#3370)

* fix(OMN-19968): the llm-cost writer speaks the shared usage-source vocabulary on SQLite and Postgres

node_projection_llm_cost wrote the retired labels API/ESTIMATED/MISSING, while
omnibase_infra migration 077 (OMN-10382) moved the shared usage_source_type to
measured/estimated/unknown (EnumUsageSource). On any database infra has migrated,
0001's typname guard matched the shared type and created nothing, so every write
of 'MISSING' or 'API' was refused; the ticket's own lab step failed 6 passed,
2 failed on the lakshman lane at 07:46Z.

- row_llm_call_metrics maps provider-reported usage to measured, ESTIMATED to
  estimated, and MISSING/UNKNOWN/absent/unrecognised to unknown (retired labels
  folded in, case-insensitive).
- The SQLite schema defaults to 'unknown', and existing SQLite rows are moved
  onto the shared vocabulary in place.
- Migration 0003 renames an omnimarket-created usage_source_type's labels in
  place (RENAME VALUE, no table rewrite), resolving the type through
  search_path; a no-op where the shared vocabulary is already present.
- Tests and fixtures use the shared values; a new test file pins the mapping,
  the SQLite reconciliation and the migration's uniqueness.

* test(OMN-19968): repo-owned evidence contract binding AC1 and AC2 to the parity tests

The repo-evidence gate (OMN-20070) requires a PR that cites OMN-19968 to
carry contracts/OMN-19968.yaml, with every acceptance criterion bound by
binds_ac to a test_passes item naming an existing test file.

AC1 binds to the rows_equal parity tests in
tests/test_omn19968_writer_store_parity.py (the four writers, real SQLite
adapter against real Postgres; the Postgres half skips without a Postgres).
AC2 binds to the postgres_only_sql tests (a recording SQLite connection
refuses $n placeholders, ::jsonb and enum casts on every writer's path).

node_dod_verify --execution-audience hosted on this head: PASS, 2 of 2
verified, both behavior-proving.

* test(OMN-19968): bind AC1 to the usage-source vocabulary test, which fails at the merge base

The repo-evidence gate overlays a PR's tests on the merge-base source and
requires every bound test to fail there. The parity tests already pass on
dev (they were delivered by #3067 and #3148), so binding them read as
always-pass. AC1 now binds to tests/unit/test_omn19968_usage_source_vocabulary.py,
the test this change adds: against the merge-base source (2c6dabe) it
fails 17 of 17, on this head it passes. AC2 is not changed by this PR and
is no longer claimed here.

* fix(OMN-19968): pin omnibase-infra 0.38.63, the first release that vendors migration 0003

The Sibling Release Compatibility Gate (OMN-20381) refused
node_projection_llm_cost/migrations/0003_usage_source_shared_vocabulary.sql:
it reads the omnibase-infra pin from uv.lock and requires every node
migration a change adds to be byte-identical in that release. The pin was
0.38.61, which lacks it; omnibase_infra#4420 (98620a57) vendored it and is
first released in v0.38.63.

Raise the floor to >=0.38.63,<0.39.0 in dependencies and
override-dependencies, and refresh uv.lock for omnibase-infra only (uv
0.12.22, the version CI installs: lock revision stays 5, 10 lines change,
no other package moves). The gate's comparison run locally: v0.38.61 lacks
0003, v0.38.63 is byte-identical.

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

* fix(OMN-19968): the workflows check out omnibase_infra at the locked v0.38.63

tests/ci/test_sibling_pins_omn20001.py::test_infra_checkout_pins_match_lock
failed on this PR's CI (Tests split 2/20, run 37138844983): six workflows
still checked out omnibase_infra at v0.38.61 after the lock moved to
0.38.63. Move every such checkout, and the messages that name the version,
to v0.38.63, and the one test that pins the overlay-parity checkout ref.
These are the same edits omnimarket#3309 makes for the same pin.

tests/ci, tests/seam_goldens and the pricing tests on this branch and on
dev at the merge base 76e1673 fail the same 23 tests locally (release
staleness and others; identical sets, environmental here) and nothing else.
The pin test passes; restoring one v0.38.61 ref fails it.

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

* fix(OMN-19968): a read through the SQLite adapter no longer takes a write lock

#3183 moved old usage_source labels onto the shared vocabulary with an
UPDATE run on every connection. An UPDATE takes a write lock even when it
matches nothing, so every read waited behind any writer (or failed with
'database is locked') and a read-only store raised.

The relabel is now a one-time store step, like migration 0003 on
Postgres: it runs once, in one transaction with its record in
omnimarket_sqlite_store_steps, and a store that has recorded it opens
with reads only. A read-only store is read as it is, with one warning;
any other error propagates.

The decision reads the adapter's own step record, not llm_call_metrics.
A look-first read of that table was refused by the OMN-17446 registry
hook: this module is shared by nodes that do not own the table, and a
shared module deciding from another node's time-retained relation is the
shape that hook guards against.

Onex-Lane: omn19968-readlock-followup
Onex-Session: d99c149d-35af-4f1e-b791-28a28187bfc4

* chore(OMN-19968): bump project.version to 0.4.293, past published v0.4.292

The release identity gate (OMN-16344) refuses packaged source changes on a version that is already published. v0.4.292 was released 2026-10-04T15:18Z; this matches the open bot bump omnimarket#3375 (pyproject.toml and uv.lock, identical lines).

Onex-Lane: ci-fix2-OMN-19968
Onex-Session: d99c149d-35af-4f1e-b791-28a28187bfc4

* fix(OMN-19968): drop the connect-time relabel that the dev merge kept beside the recorded step

omnimarket#3183 landed on dev with the relabel inside _reconcile_legacy_llm_call_metrics, which runs on every connect and so takes a write lock on every read. This branch moves it into the once-per-store step; git merged both, so the old UPDATE came back and six of the read-lock tests failed on the merged tree. Keep only the step.

Dev is at 0.4.299, which is already published, and this change touches packaged source, so project.version moves to 0.4.300 (release-identity gate, OMN-16344).

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

* test(OMN-19968): bind the read-lock tests in the repo contract, which the merge base's contract does not carry

repo-evidence / dod-verify refused 0ebc5ef: after #3183 merged, contracts/OMN-19968.yaml on dev binds only the usage-source vocabulary check, so every check this PR bound was "carried unchanged from the merge base" and the PR bound no evidence of its own (run 37257169127). Add a check on the read-lock tests; the selected six fail on origin/dev cce192a and pass here.

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

---------

Co-authored-by: onexbot-occ-writer[bot] <307849072+onexbot-occ-writer[bot]@users.noreply.github.com>
Patel230 added a commit that referenced this pull request Oct 7, 2026
…timates (#3378)

* feat(OMN-20006): usage-by-model-day keeps measured cost apart from estimates

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

* fix(OMN-20006): shorten the AC1 dod_evidence id to the 50-character limit

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

* test(OMN-20006): unpack the tenant-scoped exposure by length, not by 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

* fix(OMN-20006): promote ModelUsageCallEvent to omnimarket.models so the 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

* chore(OMN-20006): bump to 0.4.300, past the published 0.4.299

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

* fix(OMN-20006): an existing local store opens again after the dev merge

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

* fix(OMN-20006): run the usage-by-model-day reconcile as a one-time store 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

* fix(OMN-20006): book each delegation attempt's usage to its own model

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

* fix(OMN-20006): usage tokens follow metering's one definition

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

* build(OMN-20006): pin omnibase-infra 0.38.67, the first release vendoring 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

* fix(OMN-20006): the clean-install delegate probe asks onex delegate for --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

* ci(OMN-20006): every omnibase_infra checkout pins v0.38.67, matching 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

* fix(OMN-20006): store step rolls back any failure but catches only the 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

---------

Co-authored-by: onexbot-occ-writer[bot] <307849072+onexbot-occ-writer[bot]@users.noreply.github.com>
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