Summary
In openmeter/llmcost, a per-namespace manual price override (POST /api/v1/llm-cost/overrides → CreateOverride) is silently ignored whenever a global/system price exists for the same (provider, model_id) — which is the normal case, because the background sync job (UpsertGlobalPrice) populates a global price for every known model.
The override row is written and is correctly selected as a candidate at read time, but it loses the final tie-break in ResolvePrice and the global price is returned instead. As a result, manual overrides have no effect on cost computation (e.g. Feature Cost / LLM unit cost).
Root cause
openmeter/llmcost/adapter/price.go, ResolvePrice:
entity, err := a.db.LLMCostPrice.Query().
Where(pricedb.DeletedAtIsNil()).
Where(pricedb.Or(
pricedb.NamespaceEQ(input.Namespace), // override (namespace set)
pricedb.NamespaceIsNil(), // global/system price (NULL)
)).
Where(pricedb.ProviderEQ(string(input.Provider))).
Where(pricedb.ModelIDEQ(input.ModelID)).
Where(pricedb.EffectiveFromLTE(at)).
Where(pricedb.Or(
pricedb.EffectiveToIsNil(),
pricedb.EffectiveToGT(at),
)).
Order(
// Prioritize namespace overrides
pricedb.ByNamespace(sql.OrderDesc()), // <-- missing NULLS LAST
pricedb.ByEffectiveFrom(sql.OrderDesc()),
).
First(ctx)
- Manual overrides are stored with a non-NULL
namespace (CreateOverride calls SetNamespace(input.Namespace) with source = manual).
- Global/system prices are stored with
namespace = NULL (UpsertGlobalPrice never sets a namespace; the schema field is Optional().Nillable() and is documented as "Nil for global prices, set for namespace overrides").
The ordering intent ("Prioritize namespace overrides") relies on the non-NULL namespace sorting before the NULL namespace. But ByNamespace(sql.OrderDesc()) emits a bare ORDER BY namespace DESC with no NULLS clause, and PostgreSQL's default for DESC is NULLS FIRST. So the NULL-namespace (global) row sorts first and First(ctx) returns it — the override never wins.
ent confirms the emitted SQL has no NULLS clause (entgo.io/ent@v0.14.6, dialect/sql/sql.go, OrderFieldTerm.ToFunc):
if f.Desc { b.WriteString(" DESC") }
if f.NullsFirst { b.WriteString(" NULLS FIRST") } else if f.NullsLast { b.WriteString(" NULLS LAST") }
// neither option set -> bare " DESC", PostgreSQL default = NULLS FIRST
Reproduction (PostgreSQL 14)
CREATE TEMP TABLE t (id text, namespace text, price numeric);
INSERT INTO t VALUES ('global_sync', NULL, 30), ('override', 'default', 20);
-- What ResolvePrice emits today:
SELECT id, namespace, price FROM t ORDER BY namespace DESC LIMIT 1;
-- id | namespace | price
-- -------------+-----------+-------
-- global_sync | | 30 <-- override is ignored
-- With the fix:
SELECT id, namespace, price FROM t ORDER BY namespace DESC NULLS LAST LIMIT 1;
-- id | namespace | price
-- ----------+-----------+-------
-- override | default | 20 <-- override wins, as intended
Impact
- Any per-namespace LLM price override created via
POST /llm-cost/overrides does not affect cost resolution as long as a global price exists for the same (provider, model_id) (the common case, since sync seeds global prices).
- This affects all consumers of
ResolvePrice, including LLM unit cost / Feature Cost computation in openmeter/cost/adapter/adapter.go (getLLMPrices passes feat.Namespace correctly; the override is matched as a candidate but loses the sort).
- The override row is persisted and is returned by
ListOverrides, so it appears to have been applied — making this a silent, hard-to-notice correctness bug.
Why it wasn't caught
- There is no integration test exercising the real
ResolvePrice SQL ordering: openmeter/llmcost/service tests use a mockAdapter, and there is no openmeter/llmcost/adapter/*_test.go. The cost and e2e suites don't create overrides.
- The same NULL-ordering pitfall is handled correctly elsewhere in the codebase —
openmeter/subject/adapter/subject.go explicitly adds sql.OrderNullsLast() with the comment "Make sure null display names are last" — confirming the default is NULLS FIRST and must be overridden.
Suggested fix
Make the namespace ordering put NULLs last so non-NULL (override) namespaces sort first:
Order(
pricedb.ByNamespace(sql.OrderDesc(), sql.OrderNullsLast()),
pricedb.ByEffectiveFrom(sql.OrderDesc()),
)
A regression test that inserts both a global price and a namespace override for the same (provider, model_id) and asserts ResolvePrice returns the override would lock this down.
Environment
- Package:
openmeter/llmcost
- ent: v0.14.6
- PostgreSQL: 14 (default
NULLS FIRST on DESC applies to all supported PostgreSQL versions)
Summary
In
openmeter/llmcost, a per-namespace manual price override (POST /api/v1/llm-cost/overrides→CreateOverride) is silently ignored whenever a global/system price exists for the same(provider, model_id)— which is the normal case, because the background sync job (UpsertGlobalPrice) populates a global price for every known model.The override row is written and is correctly selected as a candidate at read time, but it loses the final tie-break in
ResolvePriceand the global price is returned instead. As a result, manual overrides have no effect on cost computation (e.g. Feature Cost / LLM unit cost).Root cause
openmeter/llmcost/adapter/price.go,ResolvePrice:namespace(CreateOverridecallsSetNamespace(input.Namespace)withsource = manual).namespace = NULL(UpsertGlobalPricenever sets a namespace; the schema field isOptional().Nillable()and is documented as "Nil for global prices, set for namespace overrides").The ordering intent ("Prioritize namespace overrides") relies on the non-NULL namespace sorting before the NULL namespace. But
ByNamespace(sql.OrderDesc())emits a bareORDER BY namespace DESCwith noNULLSclause, and PostgreSQL's default forDESCisNULLS FIRST. So the NULL-namespace (global) row sorts first andFirst(ctx)returns it — the override never wins.ent confirms the emitted SQL has no NULLS clause (
entgo.io/ent@v0.14.6,dialect/sql/sql.go,OrderFieldTerm.ToFunc):Reproduction (PostgreSQL 14)
Impact
POST /llm-cost/overridesdoes not affect cost resolution as long as a global price exists for the same(provider, model_id)(the common case, since sync seeds global prices).ResolvePrice, including LLM unit cost / Feature Cost computation inopenmeter/cost/adapter/adapter.go(getLLMPricespassesfeat.Namespacecorrectly; the override is matched as a candidate but loses the sort).ListOverrides, so it appears to have been applied — making this a silent, hard-to-notice correctness bug.Why it wasn't caught
ResolvePriceSQL ordering:openmeter/llmcost/servicetests use amockAdapter, and there is noopenmeter/llmcost/adapter/*_test.go. Thecostande2esuites don't create overrides.openmeter/subject/adapter/subject.goexplicitly addssql.OrderNullsLast()with the comment "Make sure null display names are last" — confirming the default isNULLS FIRSTand must be overridden.Suggested fix
Make the namespace ordering put NULLs last so non-NULL (override) namespaces sort first:
A regression test that inserts both a global price and a namespace override for the same
(provider, model_id)and assertsResolvePricereturns the override would lock this down.Environment
openmeter/llmcostNULLS FIRSTonDESCapplies to all supported PostgreSQL versions)