Move the llm_records and llm_usage DDL out of Go constants into embedded
files db/migrations/0001_llm_records.sql and 0002_llm_usage.sql (byte-for-byte
the old constants). Open now applies pending migrations after schema.sql and
before seedBuiltins, under the existing schema advisory lock and on the
lock-holding connection. Each migration runs in its own transaction with its
schema_migrations row (version, name, SHA-256 checksum, applied_at).
Open fails closed when the database has a version missing from the binary, or
an applied migration whose file name or checksum changed. A failing migration
is rolled back and blocks startup; earlier ones stay applied.
EnsureLLMRecordsTable and EnsureLLMUsageTable re-run the embedded files and
keep their transaction-level schema lock; Open never calls them.
input_tokens includes the cached subset under Anthropic and OpenAI usage semantics, so pricing input and cache read at their full prices double-counted the cache. The fresh share is now input minus cache reads, clamped at zero for gateways reporting disjoint buckets.
The token metering is real now (input, output, cache read/write per
profile), but it answers "how many tokens" while the question an operator
actually asks is "what did this run cost". Converting requires per-profile
prices, which nowhere exist.
Changes:
- llm_profiles gains four optional price columns (EUR per 1M tokens:
input, output, cache read, cache write; 0/NULL = unset), migrated
idempotently and round-tripped through SaveProfile / the profile API
in one small auditable write
- the usage aggregation computes cost_eur per profile from the stored
prices, in Go after the archived-task merge so cold-storage tokens are
priced identically; profiles without prices keep cost_eur nil instead
of showing a fake zero
- web types carry cost_eur and the price fields; the dashboard card and
profile-dialog inputs consume them in a follow-up (the API serves the
data already)
- integration test: priced profile + usage rows -> expected EUR to the
cent (2M in @1.5 + 0.5M out @4 + 1M cache-read @0.15 = 5.15), plus a
prices round-trip check
Note: the Go side activates on the next build; the schema migration is
idempotent at startup.