From 6c26fd7eced89036122cc17842bd43a7947a119f Mon Sep 17 00:00:00 2001 From: Lakshman Patel Date: Thu, 1 Oct 2026 16:45:38 +0530 Subject: [PATCH 1/5] 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. --- .../handlers/row_llm_call_metrics.py | 39 +++-- .../0003_usage_source_shared_vocabulary.sql | 39 +++++ .../test_row_and_backfill_llm_call_metrics.py | 38 ++--- src/omnimarket/projection/sqlite_database.py | 12 +- .../test_dispatch_path_projection_llm_cost.py | 2 +- .../test_golden_chain_projection_llm_cost.py | 6 +- tests/test_omn19968_writer_store_parity.py | 28 ++++ tests/test_projection_handlers.py | 2 +- .../test_local_dispatch_llm_call_metrics.py | 2 +- tests/unit/test_cli_delegation_cost_demo.py | 4 +- .../test_omn19968_usage_source_vocabulary.py | 141 ++++++++++++++++++ 11 files changed, 271 insertions(+), 42 deletions(-) create mode 100644 src/omnimarket/nodes/node_projection_llm_cost/migrations/0003_usage_source_shared_vocabulary.sql create mode 100644 tests/unit/test_omn19968_usage_source_vocabulary.py diff --git a/src/omnimarket/nodes/node_projection_llm_cost/handlers/row_llm_call_metrics.py b/src/omnimarket/nodes/node_projection_llm_cost/handlers/row_llm_call_metrics.py index 9b7760e3f5..5e0245ce8a 100644 --- a/src/omnimarket/nodes/node_projection_llm_cost/handlers/row_llm_call_metrics.py +++ b/src/omnimarket/nodes/node_projection_llm_cost/handlers/row_llm_call_metrics.py @@ -18,6 +18,7 @@ import uuid from typing import Any +from omnimarket.enums.enum_usage_source import EnumUsageSource from omnimarket.projection.runner import safe_parse_date # Ordered tuple of llm_call_metrics columns this node writes. Asserted equal to @@ -43,18 +44,26 @@ "created_at", ) -# DB enum usage_source_type accepts only these values. The upstream event uses -# MEASURED (token usage measured via the provider's usage response) which maps to -# the DB's API provenance; UNKNOWN/absent maps to MISSING. -_USAGE_SOURCE_MAP: dict[str, str] = { - "MEASURED": "API", - "API": "API", - "ESTIMATED": "ESTIMATED", - "MISSING": "MISSING", - "UNKNOWN": "MISSING", +# OMN-19968: rows carry the shared usage-source vocabulary (EnumUsageSource: +# measured / estimated / unknown), the values omnibase_infra migration 077 +# (OMN-10382) gave the shared ``usage_source_type``. The retired provenance +# labels an event may still send (API, MISSING, upper-case spellings) are +# folded onto it here; anything unrecognised is ``unknown``. +_USAGE_SOURCE_SYNONYMS: dict[str, EnumUsageSource] = { + "measured": EnumUsageSource.MEASURED, + "api": EnumUsageSource.MEASURED, + "estimated": EnumUsageSource.ESTIMATED, + "unknown": EnumUsageSource.UNKNOWN, + "missing": EnumUsageSource.UNKNOWN, } +def _usage_source(raw: object) -> EnumUsageSource: + if not raw: + return EnumUsageSource.UNKNOWN + return _USAGE_SOURCE_SYNONYMS.get(str(raw).strip().lower(), EnumUsageSource.UNKNOWN) + + def _safe_int(value: Any, default: int = 0) -> int: if value is None: return default @@ -102,8 +111,9 @@ def build_llm_call_metrics_row(data: dict[str, Any]) -> dict[str, Any]: """Map an inbound llm-call-completed event to an llm_call_metrics row. The returned dict has exactly the keys in ``LLM_CALL_METRICS_COLUMNS``. - ``usage_source`` is honest: MEASURED→API, anything else→ESTIMATED/MISSING, - with ``usage_is_estimated`` derived from it. + ``usage_source`` is honest and uses the shared vocabulary: ``measured`` + when the provider reported the usage, else ``estimated`` or ``unknown`` + (see ``_usage_source``), with ``usage_is_estimated`` derived from it. """ model_id = str(data.get("model_id") or data.get("model_name") or "unknown") session_id = data.get("session_id") or data.get("sessionId") @@ -149,15 +159,14 @@ def build_llm_call_metrics_row(data: dict[str, Any]) -> dict[str, Any]: # ContractLlmCallMetrics wire format nests it as usage_normalized.source # (values: "api" / "estimated" / "missing") — no top-level usage_source # field. Fall back to that nested path when the flat field is absent so - # exp0/generation-consumer rows are not silently written as MISSING. + # exp0/generation-consumer rows are not silently written as unknown. _top_level = data.get("usage_source") or data.get("usageSource") if not _top_level: _normalized = data.get("usage_normalized") if isinstance(_normalized, dict): _top_level = _normalized.get("source") - usage_source_raw = str(_top_level or "MISSING").upper() - usage_source = _USAGE_SOURCE_MAP.get(usage_source_raw, "MISSING") - usage_is_estimated = usage_source != "API" + usage_source = _usage_source(_top_level).value + usage_is_estimated = usage_source != EnumUsageSource.MEASURED.value input_hash = _compute_input_hash( reporting_source=str(reporting_source) if reporting_source else None, diff --git a/src/omnimarket/nodes/node_projection_llm_cost/migrations/0003_usage_source_shared_vocabulary.sql b/src/omnimarket/nodes/node_projection_llm_cost/migrations/0003_usage_source_shared_vocabulary.sql new file mode 100644 index 0000000000..d9d14e1d77 --- /dev/null +++ b/src/omnimarket/nodes/node_projection_llm_cost/migrations/0003_usage_source_shared_vocabulary.sql @@ -0,0 +1,39 @@ +-- SPDX-FileCopyrightText: 2026 OmniNode.ai Inc. +-- SPDX-License-Identifier: MIT +-- +-- OMN-19968: llm_call_metrics.usage_source speaks the shared vocabulary. +-- +-- 0001 created usage_source_type as ('API', 'ESTIMATED', 'MISSING'). omnibase_infra +-- migration 077 (OMN-10382) moved the shared type to ('measured', 'estimated', +-- 'unknown'), the EnumUsageSource values, and the writer now produces those. A +-- database built from omnimarket's migrations alone still carries the old labels; +-- this renames them in place. RENAME VALUE keeps every stored row and needs no +-- table rewrite. +-- +-- The type is resolved through search_path (to_regtype), never by name across +-- every schema: 0001's typname guard matched a same-named type in any schema, +-- which is how a database migrated by infra ended up rejecting this writer. +-- +-- Idempotent: each label is renamed only while it is still present, so on a +-- database that already carries the shared vocabulary (every infra-migrated one) +-- the block changes nothing. Forward-only: applied migration bytes are immutable. + +DO $$ +DECLARE + usage_type regtype := to_regtype('usage_source_type'); +BEGIN + IF usage_type IS NULL THEN + RETURN; + END IF; + IF EXISTS (SELECT 1 FROM pg_enum WHERE enumtypid = usage_type AND enumlabel = 'API') THEN + ALTER TYPE usage_source_type RENAME VALUE 'API' TO 'measured'; + END IF; + IF EXISTS (SELECT 1 FROM pg_enum WHERE enumtypid = usage_type AND enumlabel = 'ESTIMATED') THEN + ALTER TYPE usage_source_type RENAME VALUE 'ESTIMATED' TO 'estimated'; + END IF; + IF EXISTS (SELECT 1 FROM pg_enum WHERE enumtypid = usage_type AND enumlabel = 'MISSING') THEN + ALTER TYPE usage_source_type RENAME VALUE 'MISSING' TO 'unknown'; + END IF; +END$$; + +ALTER TABLE llm_call_metrics ALTER COLUMN usage_source SET DEFAULT 'unknown'; diff --git a/src/omnimarket/nodes/node_projection_llm_cost/tests/test_row_and_backfill_llm_call_metrics.py b/src/omnimarket/nodes/node_projection_llm_cost/tests/test_row_and_backfill_llm_call_metrics.py index 39001cdd31..8c565d670b 100644 --- a/src/omnimarket/nodes/node_projection_llm_cost/tests/test_row_and_backfill_llm_call_metrics.py +++ b/src/omnimarket/nodes/node_projection_llm_cost/tests/test_row_and_backfill_llm_call_metrics.py @@ -156,29 +156,29 @@ def test_builds_complete_row_shape(self) -> None: } assert expected_keys.issubset(row.keys()) - def test_measured_maps_to_api(self) -> None: + def test_measured_maps_to_measured(self) -> None: row = _build_row(_minimal_event(usage_source="MEASURED")) - assert row["usage_source"] == "API" + assert row["usage_source"] == "measured" assert row["usage_is_estimated"] is False def test_api_usage_source_preserved(self) -> None: row = _build_row(_minimal_event(usage_source="API")) - assert row["usage_source"] == "API" + assert row["usage_source"] == "measured" assert row["usage_is_estimated"] is False def test_estimated_usage_source(self) -> None: row = _build_row(_minimal_event(usage_source="ESTIMATED")) - assert row["usage_source"] == "ESTIMATED" + assert row["usage_source"] == "estimated" assert row["usage_is_estimated"] is True - def test_unknown_usage_source_maps_to_missing(self) -> None: + def test_unknown_usage_source_maps_to_unknown(self) -> None: row = _build_row(_minimal_event(usage_source="UNKNOWN")) - assert row["usage_source"] == "MISSING" + assert row["usage_source"] == "unknown" assert row["usage_is_estimated"] is True - def test_garbage_usage_source_maps_to_missing(self) -> None: + def test_garbage_usage_source_maps_to_unknown(self) -> None: row = _build_row(_minimal_event(usage_source="GARBAGE")) - assert row["usage_source"] == "MISSING" + assert row["usage_source"] == "unknown" def test_source_from_reporting_source(self) -> None: row = _build_row(_minimal_event(reporting_source="ab-compare")) @@ -309,7 +309,9 @@ def test_prompt_token_aliases_accepted(self) -> None: # --- OMN-12994: usage_normalized.source fallback --- - def test_usage_normalized_source_api_maps_to_api_when_no_top_level(self) -> None: + def test_usage_normalized_source_api_maps_to_measured_when_no_top_level( + self, + ) -> None: """ContractLlmCallMetrics wire shape: usage_normalized.source='api', no top-level usage_source.""" data: dict[str, Any] = { "model_id": "Qwen3.6-35B-A3B", @@ -319,8 +321,8 @@ def test_usage_normalized_source_api_maps_to_api_when_no_top_level(self) -> None "usage_normalized": {"source": "api"}, } row = _build_row(data) - assert row["usage_source"] == "API", ( - "usage_normalized.source='api' must resolve to API (MEASURED), not MISSING" + assert row["usage_source"] == "measured", ( + "usage_normalized.source='api' must resolve to measured, not unknown" ) assert row["usage_is_estimated"] is False @@ -335,10 +337,10 @@ def test_usage_normalized_source_estimated_maps_to_estimated_when_no_top_level( "usage_normalized": {"source": "estimated"}, } row = _build_row(data) - assert row["usage_source"] == "ESTIMATED" + assert row["usage_source"] == "estimated" assert row["usage_is_estimated"] is True - def test_usage_normalized_source_missing_maps_to_missing_when_no_top_level( + def test_usage_normalized_source_missing_maps_to_unknown_when_no_top_level( self, ) -> None: data: dict[str, Any] = { @@ -347,7 +349,7 @@ def test_usage_normalized_source_missing_maps_to_missing_when_no_top_level( "usage_normalized": {"source": "missing"}, } row = _build_row(data) - assert row["usage_source"] == "MISSING" + assert row["usage_source"] == "unknown" def test_top_level_usage_source_takes_priority_over_usage_normalized(self) -> None: """Top-level usage_source wins over usage_normalized.source.""" @@ -360,9 +362,9 @@ def test_top_level_usage_source_takes_priority_over_usage_normalized(self) -> No "usage_normalized": {"source": "missing"}, } row = _build_row(data) - assert row["usage_source"] == "API" # MEASURED → API from top-level + assert row["usage_source"] == "measured" # MEASURED -> measured from top-level - def test_usage_normalized_absent_falls_back_to_missing(self) -> None: + def test_usage_normalized_absent_falls_back_to_unknown(self) -> None: """No top-level usage_source and no usage_normalized → MISSING (unchanged).""" data: dict[str, Any] = { "model_id": "some-model", @@ -371,7 +373,7 @@ def test_usage_normalized_absent_falls_back_to_missing(self) -> None: "total_tokens": 150, } row = _build_row(data) - assert row["usage_source"] == "MISSING" + assert row["usage_source"] == "unknown" # --------------------------------------------------------------------------- @@ -519,7 +521,7 @@ def test_canonical_event_maps_correctly(self) -> None: } row = _build_row(event) - assert row["usage_source"] == "API" + assert row["usage_source"] == "measured" assert row["usage_is_estimated"] is False assert row["latency_ms"] == pytest.approx(19820.0) assert row["source"] == "ab-compare" diff --git a/src/omnimarket/projection/sqlite_database.py b/src/omnimarket/projection/sqlite_database.py index 96cd7adc1b..693c317183 100644 --- a/src/omnimarket/projection/sqlite_database.py +++ b/src/omnimarket/projection/sqlite_database.py @@ -172,7 +172,7 @@ total_tokens INTEGER, estimated_cost_usd REAL, latency_ms REAL, - usage_source TEXT NOT NULL DEFAULT 'MISSING', + usage_source TEXT NOT NULL DEFAULT 'unknown', usage_is_estimated INTEGER NOT NULL DEFAULT 0, usage_raw TEXT, input_hash TEXT, @@ -248,6 +248,16 @@ def _reconcile_legacy_llm_call_metrics(cls, conn: sqlite3.Connection) -> None: conn.execute( f"ALTER TABLE llm_call_metrics RENAME TO {_LEGACY_LLM_CALL_METRICS_TABLE}" ) + elif "usage_source" in columns: + # OMN-19968: rows written before the shared vocabulary move onto it + # (EnumUsageSource; omnibase_infra migration 077). Idempotent: it + # touches only rows still holding a retired label. + conn.execute( + "UPDATE llm_call_metrics SET usage_source = CASE usage_source " + "WHEN 'API' THEN 'measured' WHEN 'ESTIMATED' THEN 'estimated' " + "WHEN 'MISSING' THEN 'unknown' ELSE usage_source END " + "WHERE usage_source IN ('API', 'ESTIMATED', 'MISSING')" + ) @staticmethod def _existing_columns(conn: sqlite3.Connection, table: str) -> set[str]: diff --git a/tests/test_dispatch_path_projection_llm_cost.py b/tests/test_dispatch_path_projection_llm_cost.py index 9218fee53a..039553c297 100644 --- a/tests/test_dispatch_path_projection_llm_cost.py +++ b/tests/test_dispatch_path_projection_llm_cost.py @@ -106,7 +106,7 @@ async def test_envelope_through_real_dispatch_writes_llm_call_metrics_row() -> N assert total_tokens == 28471 assert estimated_cost_usd == pytest.approx(0.17897125) assert latency_ms == pytest.approx(19820.0) - assert usage_source == "API" # MEASURED -> API + assert usage_source == "measured" # MEASURED -> measured assert usage_is_estimated is False assert session_id == "b45a01ea-0000-0000-0000-000000000001" assert run_id is None diff --git a/tests/test_golden_chain_projection_llm_cost.py b/tests/test_golden_chain_projection_llm_cost.py index 9cd1e9b662..dd29552ced 100644 --- a/tests/test_golden_chain_projection_llm_cost.py +++ b/tests/test_golden_chain_projection_llm_cost.py @@ -76,7 +76,7 @@ def test_project_batch(self) -> None: assert result.rows_upserted == 3 assert len(db.query(TABLE)) == 3 - def test_usage_source_measured_maps_to_api(self) -> None: + def test_usage_source_measured_maps_to_measured(self) -> None: db = InmemoryDatabaseAdapter() HANDLER.project( ModelLlmCallCompletedEvent( @@ -85,7 +85,7 @@ def test_usage_source_measured_maps_to_api(self) -> None: db, ) rows = db.query(TABLE) - assert rows[0]["usage_source"] == "API" + assert rows[0]["usage_source"] == "measured" assert rows[0]["usage_is_estimated"] is False def test_usage_source_estimated(self) -> None: @@ -100,7 +100,7 @@ def test_usage_source_estimated(self) -> None: db, ) rows = db.query(TABLE) - assert rows[0]["usage_source"] == "ESTIMATED" + assert rows[0]["usage_source"] == "estimated" assert rows[0]["usage_is_estimated"] is True def test_compute_cost_folds_into_estimated_cost(self, tmp_path: Path) -> None: diff --git a/tests/test_omn19968_writer_store_parity.py b/tests/test_omn19968_writer_store_parity.py index 7be2c34f44..54abe0ac8d 100644 --- a/tests/test_omn19968_writer_store_parity.py +++ b/tests/test_omn19968_writer_store_parity.py @@ -225,6 +225,33 @@ def _dsn(pg: _Postgres, schema: str) -> str: return pg.dsn(schema) +async def _mirror_infra_usage_source_state(admin: Any, schema: str) -> None: + """Start the throwaway schema in the state a lab database is in. + + omnibase_infra migration 031 creates ``usage_source_type`` together with + ``llm_call_metrics``, and migration 077 (OMN-10382) moves the type to the shared + vocabulary, all before omnimarket's node migrations run. 0001's typname guard + matches a type in any schema, so on such a server it skips creating its own + type. On a real lab database that is harmless because the table already exists; + in an empty throwaway schema it is not, so mirror the type and the table here. + On a server infra never migrated this is a no-op and 0001 creates everything. + """ + shared = await admin.fetchval( + "SELECT EXISTS (SELECT 1 FROM pg_type t JOIN pg_enum e ON e.enumtypid = t.oid " + "WHERE t.typname = 'usage_source_type' AND e.enumlabel = 'measured')" + ) + if not shared: + return + await admin.execute( + f"CREATE TYPE {schema}.usage_source_type AS ENUM ('measured', 'estimated', 'unknown')" + ) + await admin.execute( + f"CREATE TABLE {schema}.llm_call_metrics (id BIGSERIAL PRIMARY KEY, " + f"model_id TEXT NOT NULL, usage_source {schema}.usage_source_type NOT NULL " + "DEFAULT 'unknown')" + ) + + _WRITERS = { "delegation_events": _write_delegation, "llm_call_metrics": _write_llm, @@ -243,6 +270,7 @@ async def test_writer_rows_equal_on_sqlite_and_postgres( sqlite_rows = _normalize(sqlite.query(table)) async with _provisioned(pg) as (admin, schema): + await _mirror_infra_usage_source_state(admin, schema) for migration in _LLM_COST_MIGRATIONS: await admin.execute(migration.read_text(encoding="utf-8")) postgres = PostgresSyncProjectionAdapter(_dsn(pg, schema)) diff --git a/tests/test_projection_handlers.py b/tests/test_projection_handlers.py index 2b54c5b636..5633828dec 100644 --- a/tests/test_projection_handlers.py +++ b/tests/test_projection_handlers.py @@ -208,7 +208,7 @@ async def test_usage_source_normalization(self, mock_db: AsyncMock) -> None: # to the DB enum's MISSING value. call_args[0] is SQL; the 10th INSERT # column (usage_source) is the 10th positional bind -> index 10. call_args = mock_db.execute.call_args[0] - assert call_args[10] == "MISSING" + assert call_args[10] == "unknown" class TestDelegationHandler: diff --git a/tests/unit/nodes/node_delegate_skill_orchestrator/test_local_dispatch_llm_call_metrics.py b/tests/unit/nodes/node_delegate_skill_orchestrator/test_local_dispatch_llm_call_metrics.py index 8960a3e423..680561d061 100644 --- a/tests/unit/nodes/node_delegate_skill_orchestrator/test_local_dispatch_llm_call_metrics.py +++ b/tests/unit/nodes/node_delegate_skill_orchestrator/test_local_dispatch_llm_call_metrics.py @@ -134,7 +134,7 @@ def test_local_dispatch_materializes_llm_call_metrics_row( assert row["prompt_tokens"] == delegation[0]["tokens_input"] == 11 assert row["completion_tokens"] == delegation[0]["tokens_output"] == 22 assert row["total_tokens"] == 33 - assert row["usage_source"] == "API" + assert row["usage_source"] == "measured" def test_two_local_runs_with_equal_tokens_are_two_rows( diff --git a/tests/unit/test_cli_delegation_cost_demo.py b/tests/unit/test_cli_delegation_cost_demo.py index 676c569df6..ef7fe7cd8f 100644 --- a/tests/unit/test_cli_delegation_cost_demo.py +++ b/tests/unit/test_cli_delegation_cost_demo.py @@ -54,7 +54,7 @@ def test_cli_default_profile_prints_joined_projection_table( assert "savings_estimates:1" in result.output # OMN-13001: usage_source is now persisted in the DB enum's vocabulary # (MEASURED -> API) by the per-call llm_call_metrics writer. - assert "usage_source=API" in result.output + assert "usage_source=measured" in result.output @pytest.mark.unit @@ -185,4 +185,4 @@ def test_cli_loads_explicit_profile_file( assert payload["profile"]["local_delegate"] == "tiny-delegate" assert payload["profile"]["local_model_id"] == "phi4-mini" # OMN-13001: persisted usage_source is the DB enum value (MEASURED -> API). - assert payload["joined"]["usage_source"] == "API" + assert payload["joined"]["usage_source"] == "measured" diff --git a/tests/unit/test_omn19968_usage_source_vocabulary.py b/tests/unit/test_omn19968_usage_source_vocabulary.py new file mode 100644 index 0000000000..f43cd8007c --- /dev/null +++ b/tests/unit/test_omn19968_usage_source_vocabulary.py @@ -0,0 +1,141 @@ +"""OMN-19968 amendment 2: llm_call_metrics speaks the shared usage-source vocabulary. + +omnibase_infra migration 077 (OMN-10382) moved ``usage_source_type`` to +``measured`` / ``estimated`` / ``unknown`` (EnumUsageSource). omnimarket's writer +still produced ``API`` / ``ESTIMATED`` / ``MISSING``, which a database migrated by +infra rejects (the ticket's lab step failed on exactly that, 2026-10-01 07:46Z). +""" + +from __future__ import annotations + +from pathlib import Path +from typing import Any + +import pytest + +from omnimarket.enums.enum_usage_source import EnumUsageSource +from omnimarket.nodes.node_projection_llm_cost.handlers.row_llm_call_metrics import ( + build_llm_call_metrics_row, +) +from omnimarket.projection import sqlite_database +from omnimarket.projection.sqlite_database import SqliteDatabaseAdapter + +SHARED = {member.value for member in EnumUsageSource} +RETIRED = {"API", "ESTIMATED", "MISSING"} +MIGRATIONS = ( + Path(__file__).parents[2] + / "src/omnimarket/nodes/node_projection_llm_cost/migrations" +) +VOCAB_MIGRATION = MIGRATIONS / "0003_usage_source_shared_vocabulary.sql" + + +def _event(**overrides: Any) -> dict[str, Any]: + event: dict[str, Any] = { + "correlation_id": "19968000-0000-4000-8000-0000000000aa", + "model_id": "claude-sonnet-5-5", + "prompt_tokens": 10, + "completion_tokens": 5, + "total_tokens": 15, + "timestamp": "2026-10-01T08:00:00+00:00", + } + event.update(overrides) + return event + + +@pytest.mark.parametrize( + ("raw", "expected"), + [ + ("MEASURED", "measured"), + ("measured", "measured"), + ("API", "measured"), + ("api", "measured"), + ("ESTIMATED", "estimated"), + ("estimated", "estimated"), + ("MISSING", "unknown"), + ("UNKNOWN", "unknown"), + ("unknown", "unknown"), + ("something-else", "unknown"), + ], +) +def test_row_writes_the_shared_vocabulary(raw: str, expected: str) -> None: + row = build_llm_call_metrics_row(_event(usage_source=raw)) + + assert row["usage_source"] == expected + assert row["usage_is_estimated"] is (expected != "measured") + + +def test_absent_usage_source_is_unknown_and_estimated() -> None: + row = build_llm_call_metrics_row(_event()) + + assert row["usage_source"] == "unknown" + assert row["usage_is_estimated"] is True + + +def test_nested_usage_normalized_source_maps_too() -> None: + row = build_llm_call_metrics_row(_event(usage_normalized={"source": "api"})) + + assert row["usage_source"] == "measured" + assert row["usage_is_estimated"] is False + + +def test_sqlite_schema_defaults_to_the_shared_vocabulary(tmp_path: Path) -> None: + adapter = SqliteDatabaseAdapter(tmp_path / "evidence.sqlite") + with adapter._connect() as conn: # the adapter's own schema bootstrap + conn.execute( + "INSERT INTO llm_call_metrics (model_id, input_hash) VALUES ('m', 'h1')" + ) + value = conn.execute("SELECT usage_source FROM llm_call_metrics").fetchone()[0] + + assert value == "unknown" + + +def test_existing_sqlite_rows_move_onto_the_shared_vocabulary(tmp_path: Path) -> None: + path = tmp_path / "evidence.sqlite" + adapter = SqliteDatabaseAdapter(path) + with adapter._connect() as conn: + for i, old in enumerate(("API", "ESTIMATED", "MISSING", "measured")): + conn.execute( + "INSERT INTO llm_call_metrics (model_id, input_hash, usage_source)" + " VALUES ('m', ?, ?)", + (f"h{i}", old), + ) + conn.commit() + + with adapter._connect() as conn: # reconnect: the reconcile step runs again + values = sorted( + r[0] for r in conn.execute("SELECT usage_source FROM llm_call_metrics") + ) + + assert values == ["estimated", "measured", "measured", "unknown"] + + +def test_no_retired_value_remains_in_the_writer_or_sqlite_schema() -> None: + row_module = Path(build_llm_call_metrics_row.__code__.co_filename).read_text( + encoding="utf-8" + ) + sqlite_module = Path(sqlite_database.__file__).read_text(encoding="utf-8") + + for retired in RETIRED: + assert f'"{retired}"' not in row_module, retired + assert "DEFAULT 'MISSING'" not in sqlite_module + + +def test_forward_migration_renames_the_retired_labels_in_place() -> None: + assert VOCAB_MIGRATION.is_file(), "a forward migration must move existing databases" + sql = VOCAB_MIGRATION.read_text(encoding="utf-8") + + for old, new in ( + ("API", "measured"), + ("ESTIMATED", "estimated"), + ("MISSING", "unknown"), + ): + assert f"RENAME VALUE '{old}' TO '{new}'" in sql + # resolve the type through search_path, never by name across every schema + assert "to_regtype('usage_source_type')" in sql + assert "SET DEFAULT 'unknown'" in sql + + +def test_migration_number_is_unique_in_the_node() -> None: + numbers = [p.name.split("_", 1)[0] for p in MIGRATIONS.glob("*.sql")] + + assert numbers.count("0003") == 1 From 3e4b8241cf2ad1415a7a847d072a74e2cdaa1cb0 Mon Sep 17 00:00:00 2001 From: Lakshman Patel Date: Sat, 3 Oct 2026 08:55:30 +0530 Subject: [PATCH 2/5] 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. --- contracts/OMN-19968.yaml | 47 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 contracts/OMN-19968.yaml diff --git a/contracts/OMN-19968.yaml b/contracts/OMN-19968.yaml new file mode 100644 index 0000000000..6269e06eb5 --- /dev/null +++ b/contracts/OMN-19968.yaml @@ -0,0 +1,47 @@ +--- +schema_version: "1.0.0" +ticket_id: "OMN-19968" +title: "Projection writers that run on a developer machine produce the same rows on SQLite and on Postgres" +summary: "The four writers with a SQLite path (delegation_events, llm_call_metrics, usage_by_model_day, metering_summary) give equal normalized rows through the real SQLite adapter and the real Postgres adapter, and no Postgres-only SQL is reachable from the SQLite path. This change makes the llm-cost writer speak the shared usage-source vocabulary on both stores." +is_seam_ticket: false +interface_change: false +interfaces_touched: [] +requirements: + - id: "req-store-neutral-writers" + statement: |- + Projection writers that run on a developer machine must produce the same rows on SQLite and on Postgres, with no Postgres-only SQL on the SQLite path. + acceptance: + - id: "AC1" + statement: |- + AC1: For each of the four writers with a SQLite path (delegation_events, llm_call_metrics, usage_by_model_day and metering_summary) the same recorded event fixtures give equal normalized rows through the real SQLite adapter and the real Postgres adapter -- falsifier: the omnimarket parity test run with uv run pytest shows a row difference + - id: "AC2" + statement: |- + AC2: No Postgres-only SQL ($n placeholders, ::jsonb, enum casts) is reachable from the SQLite path -- falsifier: the parity test with the SQLite adapter raising on any of those tokens fails +evidence_requirements: + - kind: "tests" + description: "Each writer's recorded fixtures give equal rows on SQLite and on Postgres; the Postgres half needs a real Postgres and is skipped without one." + command: "uv run pytest tests/test_omn19968_writer_store_parity.py -q -k rows_equal" + - kind: "tests" + description: "A recording SQLite connection refuses every Postgres-only token on each writer's SQLite path." + command: "uv run pytest tests/test_omn19968_writer_store_parity.py -q -k postgres_only_sql" +emergency_bypass: + enabled: false + justification: "" + follow_up_ticket_id: "" +dod_evidence: + - id: "dod-omn19968-ac1-rows-equal" + description: "The parity tests compare each writer's normalized rows on the real SQLite adapter and the real Postgres adapter for the same recorded fixtures." + source: "manual" + checks: + - check_type: "test_passes" + check_value: "uv run pytest tests/test_omn19968_writer_store_parity.py -q -k rows_equal" + cwd: "${OMNI_HOME}/omnimarket" + binds_ac: ["AC1"] + - id: "dod-omn19968-ac2-no-postgres-sql-on-sqlite" + description: "Each writer runs against a recording SQLite connection that raises on $n placeholders, ::jsonb and enum casts." + source: "manual" + checks: + - check_type: "test_passes" + check_value: "uv run pytest tests/test_omn19968_writer_store_parity.py -q -k postgres_only_sql" + cwd: "${OMNI_HOME}/omnimarket" + binds_ac: ["AC2"] From 1d9b2eed4889f750d726854736276bc342f7966d Mon Sep 17 00:00:00 2001 From: Lakshman Patel Date: Sat, 3 Oct 2026 09:17:30 +0530 Subject: [PATCH 3/5] 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 (2c6dabebc) it fails 17 of 17, on this head it passes. AC2 is not changed by this PR and is no longer claimed here. --- contracts/OMN-19968.yaml | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/contracts/OMN-19968.yaml b/contracts/OMN-19968.yaml index 6269e06eb5..acc375c426 100644 --- a/contracts/OMN-19968.yaml +++ b/contracts/OMN-19968.yaml @@ -2,7 +2,7 @@ schema_version: "1.0.0" ticket_id: "OMN-19968" title: "Projection writers that run on a developer machine produce the same rows on SQLite and on Postgres" -summary: "The four writers with a SQLite path (delegation_events, llm_call_metrics, usage_by_model_day, metering_summary) give equal normalized rows through the real SQLite adapter and the real Postgres adapter, and no Postgres-only SQL is reachable from the SQLite path. This change makes the llm-cost writer speak the shared usage-source vocabulary on both stores." +summary: "The four writers with a SQLite path (delegation_events, llm_call_metrics, usage_by_model_day, metering_summary) give equal normalized rows through the real SQLite adapter and the real Postgres adapter, and no Postgres-only SQL is reachable from the SQLite path. This change makes the llm-cost writer speak the shared usage-source vocabulary (measured/estimated/unknown) on both stores, which is what kept llm_call_metrics rows from matching on a database infra has migrated; AC1 binds to the test that pins it. AC2 was delivered earlier (omnimarket#3067) and is not changed here, so this contract does not claim it." is_seam_ticket: false interface_change: false interfaces_touched: [] @@ -14,34 +14,20 @@ requirements: - id: "AC1" statement: |- AC1: For each of the four writers with a SQLite path (delegation_events, llm_call_metrics, usage_by_model_day and metering_summary) the same recorded event fixtures give equal normalized rows through the real SQLite adapter and the real Postgres adapter -- falsifier: the omnimarket parity test run with uv run pytest shows a row difference - - id: "AC2" - statement: |- - AC2: No Postgres-only SQL ($n placeholders, ::jsonb, enum casts) is reachable from the SQLite path -- falsifier: the parity test with the SQLite adapter raising on any of those tokens fails evidence_requirements: - kind: "tests" - description: "Each writer's recorded fixtures give equal rows on SQLite and on Postgres; the Postgres half needs a real Postgres and is skipped without one." - command: "uv run pytest tests/test_omn19968_writer_store_parity.py -q -k rows_equal" - - kind: "tests" - description: "A recording SQLite connection refuses every Postgres-only token on each writer's SQLite path." - command: "uv run pytest tests/test_omn19968_writer_store_parity.py -q -k postgres_only_sql" + description: "The llm-cost writer, its SQLite schema and the forward migration use the shared usage-source vocabulary, so its rows match on SQLite and on a Postgres infra has migrated." + command: "uv run pytest tests/unit/test_omn19968_usage_source_vocabulary.py -q" emergency_bypass: enabled: false justification: "" follow_up_ticket_id: "" dod_evidence: - - id: "dod-omn19968-ac1-rows-equal" - description: "The parity tests compare each writer's normalized rows on the real SQLite adapter and the real Postgres adapter for the same recorded fixtures." + - id: "dod-omn19968-ac1-shared-usage-source" + description: "The llm-cost writer writes measured/estimated/unknown, the SQLite schema defaults to it, existing rows move onto it, and the forward migration renames the retired labels in place." source: "manual" checks: - check_type: "test_passes" - check_value: "uv run pytest tests/test_omn19968_writer_store_parity.py -q -k rows_equal" + check_value: "uv run pytest tests/unit/test_omn19968_usage_source_vocabulary.py -q" cwd: "${OMNI_HOME}/omnimarket" binds_ac: ["AC1"] - - id: "dod-omn19968-ac2-no-postgres-sql-on-sqlite" - description: "Each writer runs against a recording SQLite connection that raises on $n placeholders, ::jsonb and enum casts." - source: "manual" - checks: - - check_type: "test_passes" - check_value: "uv run pytest tests/test_omn19968_writer_store_parity.py -q -k postgres_only_sql" - cwd: "${OMNI_HOME}/omnimarket" - binds_ac: ["AC2"] From 0e413dfbe28071c5498bac8542bfa32e83933748 Mon Sep 17 00:00:00 2001 From: Lakshman Patel Date: Sat, 3 Oct 2026 22:01:02 +0530 Subject: [PATCH 4/5] 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 --- pyproject.toml | 12 ++++++++++-- uv.lock | 10 +++++----- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 677927361a..51c4551537 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -204,7 +204,11 @@ dependencies = [ # premium counterfactual is None, so every savings row is BASELINE_UNRESOLVED. # 0.38.61 declares omnibase-compat==0.5.7 and omnibase-spi==0.23.5; the # core floor above admits it, so this bump cascades nothing. - "omnibase-infra>=0.38.61,<0.39.0", + # OMN-19968: floor raised to 0.38.63, the first omnibase_infra release that + # vendors node_projection_llm_cost migration 0003 (omnibase_infra#4420); the + # sibling release compatibility gate refuses that migration below it. uv + # resolves it without moving any other package. + "omnibase-infra>=0.38.63,<0.39.0", "click>=8.1.0", "pydantic>=2.12.5,<3.0.0", "pydantic-settings>=2.0.0,<3.0.0", @@ -991,7 +995,11 @@ override-dependencies = [ # premium counterfactual is None, so every savings row is BASELINE_UNRESOLVED. # 0.38.61 declares omnibase-compat==0.5.7 and omnibase-spi==0.23.5; the # core floor above admits it, so this bump cascades nothing. - "omnibase-infra>=0.38.61,<0.39.0", + # OMN-19968: floor raised to 0.38.63, the first omnibase_infra release that + # vendors node_projection_llm_cost migration 0003 (omnibase_infra#4420); the + # sibling release compatibility gate refuses that migration below it. uv + # resolves it without moving any other package. + "omnibase-infra>=0.38.63,<0.39.0", "omnibase-compat==0.5.7", ] diff --git a/uv.lock b/uv.lock index 6d6e11f968..d3b07ba6df 100644 --- a/uv.lock +++ b/uv.lock @@ -12,7 +12,7 @@ resolution-markers = [ overrides = [ { name = "omnibase-compat", specifier = "==0.5.7" }, { name = "omnibase-core", specifier = ">=0.47.31,<0.48.0" }, - { name = "omnibase-infra", specifier = ">=0.38.61,<0.39.0" }, + { name = "omnibase-infra", specifier = ">=0.38.63,<0.39.0" }, { name = "omnibase-spi", specifier = ">=0.23.3,<0.24.0" }, ] @@ -3067,7 +3067,7 @@ wheels = [ [[package]] name = "omnibase-infra" -version = "0.38.61" +version = "0.38.63" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "a2a-sdk" }, @@ -3122,9 +3122,9 @@ dependencies = [ { name = "uvicorn" }, { name = "watchdog" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a9/cc/827813d48abfee83c99e78f665f5f09577ddf5e2800e346db16f43cd1b4c/omnibase_infra-0.38.61.tar.gz", hash = "sha256:471a633956af2a624690eee9a21c4120d89b5d8a266217b9f010462df2a5b862", size = 20954946, upload-time = "2026-10-01T08:54:16.041Z" } +sdist = { url = "https://files.pythonhosted.org/packages/55/88/ba2d199c1ccf0f3e6e3902156218b1cef9f26238aac6a4b2f2759b605e39/omnibase_infra-0.38.63.tar.gz", hash = "sha256:331677f53b25451759a4e42847ee86e2084156e5175ae6ccaf5b5962aa26f8fb", size = 21130999, upload-time = "2026-10-03T09:38:37.518Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/83/01d37f06023b6dc1ce62ab41713e33e35b87467ff0fd79a08010ee897f02/omnibase_infra-0.38.61-py3-none-any.whl", hash = "sha256:71ab720e4fba9ab88f9360e8c5eb05eee008adfd0b44e08c07e43f13780da97b", size = 7426491, upload-time = "2026-10-01T08:54:13.289Z" }, + { url = "https://files.pythonhosted.org/packages/8b/73/14ee18c376af03ac43aa6afa92afef7a2d31aac8aaa72a2db1731e9cf39f/omnibase_infra-0.38.63-py3-none-any.whl", hash = "sha256:cbeb906646e3bba60f35108b8118193753e15af3299996e733b22bc8278e54d4", size = 7442674, upload-time = "2026-10-03T09:38:34.466Z" }, ] [[package]] @@ -3221,7 +3221,7 @@ requires-dist = [ { name = "jsonschema", specifier = ">=4.18.0" }, { name = "omnibase-compat", specifier = "==0.5.7" }, { name = "omnibase-core", specifier = ">=0.47.31,<0.48.0" }, - { name = "omnibase-infra", specifier = ">=0.38.61,<0.39.0" }, + { name = "omnibase-infra", specifier = ">=0.38.63,<0.39.0" }, { name = "omnibase-spi", specifier = ">=0.23.3,<0.24.0" }, { name = "omninode-memory", specifier = "==0.18.0" }, { name = "packaging", specifier = ">=24.0" }, From 3a1beb08992004c653336cbb4bf8039d20364b58 Mon Sep 17 00:00:00 2001 From: Lakshman Patel Date: Sat, 3 Oct 2026 23:20:12 +0530 Subject: [PATCH 5/5] 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 76e1673d 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 --- .github/workflows/ci-bus-overlay-parity.yml | 6 +++--- .github/workflows/ci.yml | 2 +- .github/workflows/contract-topic-closure.yml | 2 +- .github/workflows/dispatcher-route-coverage.yml | 2 +- .github/workflows/node-migration-vendor-parity-gate.yml | 8 ++++---- .github/workflows/subscriber-dispatcher-resolution.yml | 2 +- tests/ci/test_ci_bus_overlay_parity.py | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci-bus-overlay-parity.yml b/.github/workflows/ci-bus-overlay-parity.yml index a2460f8bae..2dcdc70be6 100644 --- a/.github/workflows/ci-bus-overlay-parity.yml +++ b/.github/workflows/ci-bus-overlay-parity.yml @@ -106,7 +106,7 @@ jobs: # The omnibase_infra release this repo's uv.lock pins : a merge # in omnibase_infra cannot turn this repo's check red. Bump it with the # omnibase-infra lock pin, after an integration run against the new tag. - ref: v0.38.61 + ref: v0.38.63 path: .ci-bus-consumer token: ${{ secrets.OMNI_GITHUB_TOKEN || github.token }} persist-credentials: false @@ -122,7 +122,7 @@ jobs: set -euo pipefail SHA="$(git -C .ci-bus-consumer rev-parse HEAD)" echo "sha=${SHA}" >> "$GITHUB_OUTPUT" - echo "::notice::Consumer model resolved at omnibase_infra v0.38.61 ${SHA}" + echo "::notice::Consumer model resolved at omnibase_infra v0.38.63 ${SHA}" - name: Assert the consumer checkout produced the model shell: bash @@ -157,7 +157,7 @@ jobs: uv run python scripts/ci/check_ci_bus_overlay_parity.py \ --overlay config/ci_bus_lanes.yaml \ --consumer-model .ci-bus-consumer/scripts/trigger_rebuild_on_merge.py \ - --consumer-ref "omnibase_infra v0.38.61 ${{ steps.consumer.outputs.sha }}" + --consumer-ref "omnibase_infra v0.38.63 ${{ steps.consumer.outputs.sha }}" - name: Pin the gate's own behaviour run: | diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 07207b1663..05a8cbc87d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2980,7 +2980,7 @@ jobs: uses: actions/checkout@v6 with: repository: OmniNode-ai/omnibase_infra - ref: v0.38.61 + ref: v0.38.63 path: .sibling/omnibase_infra token: ${{ secrets.GITHUB_TOKEN }} - name: Set up Python diff --git a/.github/workflows/contract-topic-closure.yml b/.github/workflows/contract-topic-closure.yml index 47f36dfe03..259f09a032 100644 --- a/.github/workflows/contract-topic-closure.yml +++ b/.github/workflows/contract-topic-closure.yml @@ -112,7 +112,7 @@ jobs: repository: OmniNode-ai/omnibase_infra # dev, not main: main is release-synced, so a validator that has landed on the # integration branch would otherwise not be visible until the next release. - ref: v0.38.61 + ref: v0.38.63 path: omnibase_infra token: ${{ secrets.OMNI_GITHUB_TOKEN || github.token }} sparse-checkout: | diff --git a/.github/workflows/dispatcher-route-coverage.yml b/.github/workflows/dispatcher-route-coverage.yml index a950367035..8d9886085b 100644 --- a/.github/workflows/dispatcher-route-coverage.yml +++ b/.github/workflows/dispatcher-route-coverage.yml @@ -68,7 +68,7 @@ jobs: uses: actions/checkout@v4 with: repository: OmniNode-ai/omnibase_infra - ref: v0.38.61 + ref: v0.38.63 path: omnibase_infra token: ${{ secrets.OMNI_GITHUB_TOKEN || github.token }} # Sparse checkout: only the gate script is needed diff --git a/.github/workflows/node-migration-vendor-parity-gate.yml b/.github/workflows/node-migration-vendor-parity-gate.yml index 23206ef7e1..c4b514d7c4 100644 --- a/.github/workflows/node-migration-vendor-parity-gate.yml +++ b/.github/workflows/node-migration-vendor-parity-gate.yml @@ -91,7 +91,7 @@ jobs: uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: repository: OmniNode-ai/omnibase_infra - ref: v0.38.61 + ref: v0.38.63 path: omnibase_infra-dest token: ${{ secrets.OMNI_GITHUB_TOKEN || github.token }} sparse-checkout: | @@ -125,13 +125,13 @@ jobs: dest_file="omnibase_infra-dest/docker/migrations/forward/nodes/${node_name}/${filename}" if [ ! -f "${dest_file}" ]; then - echo "::warning::${src_file} has no vendored counterpart at docker/migrations/forward/nodes/${node_name}/${filename} in the pinned omnibase_infra v0.38.61. This repo is ahead of its pin; omnibase_infra vendors it through its own consumer PR (scripts/sync-node-migrations.sh) and this repo bumps the pin after integration testing." >&2 + echo "::warning::${src_file} has no vendored counterpart at docker/migrations/forward/nodes/${node_name}/${filename} in the pinned omnibase_infra v0.38.63. This repo is ahead of its pin; omnibase_infra vendors it through its own consumer PR (scripts/sync-node-migrations.sh) and this repo bumps the pin after integration testing." >&2 ahead=1 continue fi if ! cmp -s "${src_file}" "${dest_file}"; then - echo "::warning::${src_file} differs from the vendored copy in the pinned omnibase_infra v0.38.61 (${dest_file}). This repo is ahead of its pin; omnibase_infra re-runs scripts/sync-node-migrations.sh through its own consumer PR." >&2 + echo "::warning::${src_file} differs from the vendored copy in the pinned omnibase_infra v0.38.63 (${dest_file}). This repo is ahead of its pin; omnibase_infra re-runs scripts/sync-node-migrations.sh through its own consumer PR." >&2 ahead=1 fi done @@ -141,4 +141,4 @@ jobs: exit 0 fi - echo "::notice::All changed node migrations are already vendored in the pinned omnibase_infra v0.38.61." + echo "::notice::All changed node migrations are already vendored in the pinned omnibase_infra v0.38.63." diff --git a/.github/workflows/subscriber-dispatcher-resolution.yml b/.github/workflows/subscriber-dispatcher-resolution.yml index 814cc03f26..9f37d155a9 100644 --- a/.github/workflows/subscriber-dispatcher-resolution.yml +++ b/.github/workflows/subscriber-dispatcher-resolution.yml @@ -69,7 +69,7 @@ jobs: repository: OmniNode-ai/omnibase_infra # dev, not main: main is release-synced, so a validator that has landed on the # integration branch would otherwise not be visible here until the next release. - ref: v0.38.61 + ref: v0.38.63 path: omnibase_infra token: ${{ secrets.OMNI_GITHUB_TOKEN || github.token }} sparse-checkout: | diff --git a/tests/ci/test_ci_bus_overlay_parity.py b/tests/ci/test_ci_bus_overlay_parity.py index f414a42fcb..8b0de9e76a 100644 --- a/tests/ci/test_ci_bus_overlay_parity.py +++ b/tests/ci/test_ci_bus_overlay_parity.py @@ -625,7 +625,7 @@ def test_workflow_checks_out_the_consumer_at_its_default_branch(self) -> None: assert checkouts, "no checkout of OmniNode-ai/omnibase_infra" with_block = checkouts[0]["with"] - assert with_block["ref"] == "v0.38.61", ( + assert with_block["ref"] == "v0.38.63", ( "the gate must read the omnibase_infra release this repo pins in " "uv.lock, never omnibase_infra's live branch" )