Skip to content

feat(inference): add providers/model_profiles/rate_cards schema (PR 1/4)#30153

Closed
credence-the-bot[bot] wants to merge 7 commits into
mainfrom
credence/inference-providers-schema
Closed

feat(inference): add providers/model_profiles/rate_cards schema (PR 1/4)#30153
credence-the-bot[bot] wants to merge 7 commits into
mainfrom
credence/inference-providers-schema

Conversation

@credence-the-bot
Copy link
Copy Markdown
Contributor

Summary

This is PR 1 of 4 in the inference providers redesign (design doc: scratch/inference-providers/DESIGN.md).

  • Adds three Drizzle migrations under assistant/src/inference/migrations/:
    • 001-providers.tsproviders table (§3.1)
    • 002-model-profiles.tsmodel_profiles table (§3.2)
    • 003-rate-cards.tsrate_cards table (§3.3)
  • Adds Drizzle schema files under assistant/src/inference/schema/ for each table, including a Zod discriminated-union schema for providers.auth (§3.4)
  • Registers all three migrations in the daemon's startup migration runner (memory/db-init.ts) and in MIGRATION_REGISTRY (memory/migrations/registry.ts) so validateMigrationState can track them and rollbackMemoryMigration can reverse them
  • No code paths read or write from these tables yet — that is PR 2 (seed/backfill) and PR 4 (dispatcher cutover)

Design references

  • §2.5 — v1 scope cut. All three tables are in scope. No seed, backfill, or CLI in this PR.
  • §3.1 providersid (uuid PK), name (unique), contract (closed enum), base_url, auth (JSON, validated by Zod), is_canonical, canonical_revision, canonical_equivalent_id (self-ref FK for mode-flip pairing), disabled, modality (default 'chat'), timestamps.
  • §3.2 model_profiles — FK to providers.id with ON DELETE RESTRICT. name unique, model free-form text (§11.2 — no validation at schema layer). system_prompt, temperature, max_tokens nullable.
  • §3.3 rate_cards — Insert-only versioned history keyed by (provider_id, model, effective_from). cache_write_cost_per_1m and cache_read_cost_per_1m nullable for Anthropic-style cache pricing. No updated_at (rows are never updated per §3.3).

Schema decisions

  • providers.auth stored as TEXT NOT NULL (raw JSON). The ProviderAuth Zod schema in inference/schema/providers.ts validates it at the service layer (PR 2+). The three v1 variants are api_key, platform, and none per §3.4. The Zod union is additively extensible without a schema migration.
  • canonical_equivalent_id is a nullable self-referential FK on providers. SQLite supports this via deferred constraint checking.
  • Timestamps stored as INTEGER (epoch ms), consistent with all other tables in the daemon.

Migration approach

Each migration uses withCrashRecovery (the daemon's checkpoint-based idempotency guard), CREATE TABLE IF NOT EXISTS, and CREATE INDEX IF NOT EXISTS. They are idempotent and safe to re-run on every startup. down() functions drop the respective tables for rollback support. Checkpoint keys follow the migration_inference_*_v1 convention and are registered in MIGRATION_REGISTRY at versions 48-50.

Test plan

  • bun test src/inference/migrations — 13 tests pass across 3 files
  • bun typecheck — no errors
  • Each test file verifies: column names/types/nullability, unique constraints, FK enforcement (with PRAGMA foreign_keys=ON), re-run idempotency, down() drops table and is idempotent

Stack

PR Scope Status
PR 1 (this) Schema + migrations + Zod schemas Open
PR 2 Manifest, seed routine, backfill from config.json Blocked on PR 1
PR 3 Read CLIs (list/get for providers + profiles) Blocked on PR 1
PR 4 Contract adapters + dispatcher cutover Blocked on PR 1+2

🤖 Generated with Claude Code

credence-the-bot[bot] and others added 4 commits May 9, 2026 19:03
… rate_cards tables

Creates three Drizzle migrations under assistant/src/inference/migrations/:
- 001-providers: id, name (unique), contract, base_url, auth (JSON),
  is_canonical, canonical_revision, canonical_equivalent_id (self-ref FK),
  disabled, modality, timestamps
- 002-model-profiles: id, name (unique), provider_id (FK → providers,
  ON DELETE RESTRICT), model, system_prompt, temperature, max_tokens,
  is_canonical, canonical_revision, disabled, timestamps
- 003-rate-cards: id, provider_id (FK → providers), model, input/output/
  cache token costs, currency (default USD), effective_from, source

Each migration uses withCrashRecovery for idempotent checkpoint tracking
and exports a down() for rollback. See DESIGN.md §3.1, §3.2, §3.3.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…les, rate_cards

Adds inference/schema/ with:
- providers.ts: Drizzle table with self-referential canonical_equivalent_id FK;
  Zod ProviderAuth discriminated union (api_key | platform | none) per §3.4
- model-profiles.ts: Drizzle table with FK to providers (onDelete: restrict)
- rate-cards.ts: Drizzle table with composite index on (provider_id, model,
  effective_from) for cost-lookup query
- index.ts: barrel re-export

Auth union is additively extensible without schema migration (column is JSON).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ate_cards

One test file per migration covering:
- Table created with expected columns (name, type, NOT NULL, default)
- Unique constraints enforced (name column)
- FK constraints enforced (provider_id, WITH foreign_keys=ON)
- Re-running the migration is a no-op (withCrashRecovery checkpoint gate)
- down() drops the table and is idempotent

13 tests, 102 expect() calls. All pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…er and registry

- db-init.ts: adds migrateInferenceProviders, migrateInferenceModelProfiles,
  migrateInferenceRateCards to the startup migrationSteps array (after existing
  migrations; providers runs before model_profiles since there is a FK dependency)
- registry.ts: adds MIGRATION_REGISTRY entries for versions 48-50 with down()
  functions so validateMigrationState doesn't warn about unknown checkpoints and
  rollbackMemoryMigration can reverse inference migrations

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0d16ba944d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +25 to +26
CREATE INDEX IF NOT EXISTS idx_rate_cards_provider_model_effective_from
ON rate_cards (provider_id, model, effective_from)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Enforce uniqueness on rate-card version key

Make the (provider_id, model, effective_from) key unique instead of a plain index. Right now the migration allows multiple rows for the same provider/model/effective timestamp, so repeated seed/backfill runs or manual inserts can create duplicate versions and make downstream pricing resolution ambiguous (different rows can be returned for what should be one version).

Useful? React with 👍 / 👎.

credence-the-bot[bot] and others added 3 commits May 9, 2026 19:09
Autofixed by eslint --fix: sorts imports alphabetically within each
import group across test files, schema/index.ts, db-init.ts, and
registry.ts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Schema files are a public API intended for use in PR 2+ (seed/backfill)
and PR 4 (dispatcher). Marking as library entry so knip doesn't flag
them as unused files during development of the stacked PR chain.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…index UNIQUE

Addresses Codex review: a plain index allows duplicate rows for the same
provider/model/timestamp, making cost-lookup ambiguous. The version key
(provider_id, model, effective_from) must be unique since each entry
represents one pricing version at a point in time.

Updated the migration DDL, Drizzle schema (uniqueIndex), and added a
UNIQUE constraint test to the 003-rate-cards test suite.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@credence-the-bot
Copy link
Copy Markdown
Contributor Author

wrap

PR 1/4 — merge-ready

  • All CI checks pass (Lint, Type Check, Test, OpenAPI Spec Check)
  • Codex P2 feedback addressed: rate_cards (provider_id, model, effective_from) upgraded to UNIQUE INDEX — prevents ambiguous duplicate rate-card versions on repeated seed/backfill runs
  • 14 tests across 3 files, 103 expect() calls
  • Scope strictly contained to assistant/src/inference/ + migration runner registration lines in memory/db-init.ts and memory/migrations/registry.ts

Ready for merge. @noa PR 2 (seed/backfill) and PR 3 (read CLIs) are blocked on this.

@noanflaherty
Copy link
Copy Markdown
Contributor

Superseded by v1 redesign. Closing per pivot: dropping the data-driven providers approach (Sidd's pushback held — providers need code anyway, OpenRouter-style edge cases force code changes). New direction is auth-first: a single provider_connections table that names (provider, auth-config) tuples, with provider impls staying in code. No model_profiles or rate_cards tables. v1 design doc forthcoming. Branch will be deleted.

@noanflaherty noanflaherty deleted the credence/inference-providers-schema branch May 9, 2026 22:08
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.

1 participant