Skip to content

feat: add model alias domain model and SQLite repository (Part 1/2) - #109

Merged
yacosta738 merged 7 commits into
mainfrom
feat/model-aliasing-foundation
Jun 5, 2026
Merged

yacosta738 merged 7 commits into
mainfrom
feat/model-aliasing-foundation

Conversation

@yacosta738

Copy link
Copy Markdown
Contributor

Summary

Part 1 of 2 for Model Aliasing & Normalization (#47). This PR establishes the foundation layer: domain model, repository port, SQLite implementation, and migration.

Changes

Domain Layer

  • Add ModelAlias struct with alias, canonical model ID, optional provider ID, and timestamp
  • Add ModelAliasRepositoryPort trait with CRUD + seeding methods

Infrastructure Layer

  • Create alias-sqlite crate following existing SQLite patterns (combo-sqlite, provider-sqlite)
  • Implement SqliteModelAliasRepository with parameterized queries and cycle prevention
  • Add V5 migration for model_aliases table with indexed columns
  • Define 26 built-in aliases (OpenAI, Anthropic, Google, Mistral, Groq)
  • Implement idempotent seeding logic with INSERT OR IGNORE

Tests

  • 11 unit tests covering CRUD operations, cycle detection, and seeding
  • All existing tests pass (368 total)

Out of Scope (Part 2)

The following will be implemented in a follow-up PR:

  • Config section ([model_aliases])
  • DI wiring into RouteRequest
  • Alias resolution logic before routing
  • HTTP API endpoints (/api/models/aliases)
  • Integration tests

Verification

cargo test -p alias-sqlite  # ✅ 11 passed
cargo test                  # ✅ 368 passed

Related

- Add ModelAlias struct and ModelAliasRepositoryPort trait
- Create alias-sqlite crate with SqliteModelAliasRepository
- Add V5 migration for model_aliases table
- Include 26 built-in aliases (OpenAI, Anthropic, Google, Mistral, Groq)
- Add 11 unit tests for repository operations
- Implement cycle prevention and idempotent seeding

Part of #47
@github-actions github-actions Bot added the area/ci CI, tooling, and automation label Jun 5, 2026
@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@yacosta738, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 7 minutes and 13 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 224c02b9-41a7-456f-af5c-e3ea1de3f4a7

📥 Commits

Reviewing files that changed from the base of the PR and between 7a397b2 and f7297d6.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (23)
  • apps/rook/src/config.rs
  • apps/rook/src/di.rs
  • apps/rook/tests/alias_routing_e2e.rs
  • apps/rook/tests/config_tests.rs
  • crates/application/rook-usecases/src/route_request.rs
  • crates/application/rook-usecases/tests/route_request_restrictions.rs
  • crates/domain/rook-core/src/model.rs
  • crates/domain/rook-core/src/ports.rs
  • crates/infrastructure/alias-sqlite/src/builtin.rs
  • crates/infrastructure/alias-sqlite/src/repository.rs
  • crates/infrastructure/cache-memory/Cargo.toml
  • crates/infrastructure/cache-memory/src/lib.rs
  • crates/infrastructure/observability/src/metrics.rs
  • crates/infrastructure/transport-axum/Cargo.toml
  • crates/infrastructure/transport-axum/src/bootstrap_helpers.rs
  • crates/infrastructure/transport-axum/src/handlers/aliases.rs
  • crates/infrastructure/transport-axum/src/handlers/cache.rs
  • crates/infrastructure/transport-axum/src/handlers/mod.rs
  • crates/infrastructure/transport-axum/src/routes.rs
  • crates/infrastructure/transport-axum/tests/alias_api.rs
  • crates/infrastructure/transport-axum/tests/cache_routes.rs
  • crates/infrastructure/transport-axum/tests/format_translation_integration.rs
  • openspec/changes/read-cache/tasks.md
📝 Walkthrough

Walkthrough

This PR introduces end-to-end model alias resolution: friendly model aliases map to canonical models before routing decisions. Domain types and a SQLite repository provide persistent storage; configuration enables optional startup seeding; the application layer optionally resolves aliases before restrictions; HTTP handlers expose alias CRUD operations; comprehensive tests validate all layers.

Changes

Model Alias Resolution Feature

Layer / File(s) Summary
Domain Model & Persistence Port
crates/domain/rook-core/src/model.rs, crates/domain/rook-core/src/ports.rs
ModelAlias struct represents alias→canonical mappings with optional provider scope and timestamps. ModelAliasRepositoryPort trait defines async methods for find, list, create, delete, and idempotent seed operations; ModelAliasRepositoryError enum provides error handling for not-found, duplicate, invalid, and database failures.
SQLite Repository & Database
crates/infrastructure/alias-sqlite/Cargo.toml, crates/infrastructure/alias-sqlite/src/lib.rs, crates/infrastructure/alias-sqlite/src/builtin.rs, crates/infrastructure/alias-sqlite/src/repository.rs, crates/infrastructure/db-migration/src/migrations/V5__model_aliases.sql
SqliteModelAliasRepository manages in-memory or file-based aliases with pragma config and automatic migrations. Implements find (with optional provider filter), list (ordered), create (with cycle prevention), delete, and seed (INSERT OR IGNORE). builtin_aliases() helper converts DEFAULT_ALIASES constant to domain objects. Database migration creates model_aliases table with indexes on canonical (cycle detection) and provider_id (scoped queries). Async unit tests cover all operations, including duplicate/cycle rejection, idempotency, and concurrency.
Configuration & Dependency Injection
Cargo.toml, apps/rook/Cargo.toml, apps/rook/src/config.rs, apps/rook/src/di.rs, apps/rook/tests/config_tests.rs
ModelAliasesConfig with enabled/auto_seed flags (both default true) extends RookConfig; new alias-sqlite crate joins workspace. DI container constructs SqliteModelAliasRepository and optionally seeds with DEFAULT_ALIASES during startup (logs warnings on failure without halting). Repository is wired into RouteRequest constructor. Configuration tests verify deserialization, defaults, and partial configuration scenarios.
Application Layer Integration
crates/application/rook-usecases/src/route_request.rs, crates/application/rook-usecases/tests/route_request_restrictions.rs
RouteRequest accepts alias_repository and alias_config in constructor and exposes alias_repository() accessor. During execute_with_format, if aliasing is enabled, the use case attempts to resolve req.model via find_by_alias before model restriction checks; on success rewrites req.model to canonical; on error logs warning and continues. Tests provide TestAliasRepository stub and test_alias_config() helper; all test call sites updated to wire repository/config dependencies.
HTTP API: Routes & Handlers
crates/infrastructure/transport-axum/src/alias_routes.rs, crates/infrastructure/transport-axum/src/handlers/aliases.rs, crates/infrastructure/transport-axum/src/handlers/mod.rs, crates/infrastructure/transport-axum/src/lib.rs, crates/infrastructure/transport-axum/src/routes.rs
New alias_routes module exposes REST endpoints nested under /api/models/aliases: GET and POST on /, DELETE on /{alias}. Handler implementations: list_aliases fetches all; create_alias validates inputs, prevents cycles by resolving canonical, inserts with RFC3339 timestamp, returns 201 or maps errors to 400; delete_alias returns 204 on success or 404 when not found. Router state carries repository instance.
Test Suite
apps/rook/tests/alias_routing_e2e.rs, crates/infrastructure/transport-axum/tests/alias_api.rs, crates/infrastructure/transport-axum/tests/bootstrap_helpers.rs, crates/infrastructure/transport-axum/tests/format_translation_integration.rs
E2E tests exercise repository semantics: alias resolution, fail-open for unknown aliases, provider-scoped lookup, idempotent seeding, concurrent access. HTTP API integration tests verify CRUD, duplicate/cycle rejection, validation errors, and response payloads using in-memory test repository. Bootstrap and format-translation tests wire stub alias repositories with aliasing disabled, ensuring no interference with existing flows.

🎯 3 (Moderate) | ⏱️ ~25 minutes

A rabbit hops with glee—aliases hop too! ✨
Model names now bloom in friendly disguise,
Canonical truths beneath; cycles caught by our eyes. 🐰
Routes resolve with grace, both swift and precise.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.16% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding a model alias domain model and SQLite repository as Part 1 of a two-part feature, which aligns with the entire changeset.
Description check ✅ Passed The description clearly explains the PR's purpose, changes across domain and infrastructure layers, testing coverage, and what remains for Part 2, all directly related to the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/model-aliasing-foundation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

- Add [model_aliases] config section with enabled and auto_seed flags
- Wire SqliteModelAliasRepository into DI container with startup seeding
- Implement alias resolution in RouteRequest before restrictions check
- Add GET/POST/DELETE endpoints for alias management at /api/models/aliases
- Add cycle prevention validation in create endpoint
- Add 7 E2E tests for alias resolution and seeding
- Add 10 HTTP API integration tests
- Add 5 config tests for model aliases section

Part of #47
@github-actions github-actions Bot added the area/testing Tests and testing infrastructure label Jun 5, 2026
…y (2/2) (#110)

* feat(cache): content-based cache keys with SHA-256 signatures

Implement content-based caching for issue #50:
- Add signature field to CacheKey (SHA-256 hex string)
- Update CompletionRequest.cache_key() to hash (model + messages + params)
- Add CacheStats struct with hit_rate() and utilization() methods
- Add LRU eviction to InMemoryCache with max_entries support
- Add AtomicU64 counters (hits, misses, evictions)
- Implement stats() method in InMemoryCache
- Add comprehensive unit tests for hashing, LRU, and stats

Breaking change: CacheKey now requires signature field (documented in proposal)

* fix(cache): address code review findings

- Add documentation for approximate LRU under concurrency
- Fix evict_if_needed to only trigger on new keys (not overwrites)
- Add test for no-eviction-on-overwrite behavior
- Clarify spec: None=unlimited, Some(0)=rejected (usize cannot be negative)
- Update tasks to specify max_entries validation rules

* feat(cache): HTTP management API, config validation, and observability (2/2)

Completes issue #50 implementation:

**Phase 3: Ports**
- Add stats() method to CachePort trait
- Add delete_by_signature() for HTTP endpoint support
- Implement both methods in InMemoryCache

**Phase 4: Configuration**
- Add max_entries field to CacheConfig
- Implement validate() rejecting ttl > 24h and max_entries = Some(0)
- Wire validation at config load (fail-fast)
- Pass max_entries to cache constructor in DI

**Phase 5: Application**
- Add cache() accessor to RouteRequest (already tracking stats)

**Phase 6: Transport**
- Create cache.rs handler module
- Implement GET /api/cache/stats (200 with CacheStats JSON)
- Implement DELETE /api/cache (204 clear all)
- Implement DELETE /api/cache/:signature (204/404)
- Wire cache routes (management API, requires auth)
- Extend /health with cache_entries, cache_hit_rate, cache_utilization

**Phase 7: Observability**
- Add rook_cache_evictions counter description
- Wire eviction metric in InMemoryCache

**Tests**
- 5 config validation tests
- 6 cache HTTP endpoint integration tests
- All 450+ tests passing

* fix: apply remaining code review findings from PR #110

- cache-memory: only increment deleted/evictions when store.remove returns Some
- routes.rs: use Axum 0.8 path syntax {signature}
- cache_routes.rs: add test_cache_routes_require_management_auth
- config_tests.rs: fix assertion message to match actual validation error
- tasks.md: fix dependencies (9.* → 9.1-9.4), update delete behavior description
- Add ModelAlias struct and ModelAliasRepositoryPort trait
- Create alias-sqlite crate with SqliteModelAliasRepository
- Add V5 migration for model_aliases table
- Include 26 built-in aliases (OpenAI, Anthropic, Google, Mistral, Groq)
- Add 11 unit tests for repository operations
- Implement cycle prevention and idempotent seeding

Part of #47
- Add [model_aliases] config section with enabled and auto_seed flags
- Wire SqliteModelAliasRepository into DI container with startup seeding
- Implement alias resolution in RouteRequest before restrictions check
- Add GET/POST/DELETE endpoints for alias management at /api/models/aliases
- Add cycle prevention validation in create endpoint
- Add 7 E2E tests for alias resolution and seeding
- Add 10 HTTP API integration tests
- Add 5 config tests for model aliases section

Part of #47

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/application/rook-usecases/src/route_request.rs`:
- Around line 99-137: execute_stream_with_format currently skips the alias
resolution performed in execute_with_format, causing streaming requests to use
unresolved req.model; update execute_stream_with_format to perform the same
alias lookup when self.alias_config.enabled by calling
self.alias_repository.find_by_alias(&req.model, None).await and, on
Ok(Some(alias_entry)), set req.model = alias_entry.canonical (log with
tracing::debug), handle Ok(None) silently and Err(e) with tracing::warn like
execute_with_format does so restrictions and routing see the canonical model for
streaming requests as well.

In `@crates/domain/rook-core/src/model.rs`:
- Line 560: ModelAlias.created_at is currently a String but should use
DateTime<Utc> like other domain models (e.g., User.created_at,
Session.created_at, Combo.created_at); change the field type in the ModelAlias
struct to chrono::DateTime<chrono::Utc>, add the appropriate chrono imports, and
then update all places that construct, serialize/deserialize, and persist
ModelAlias instances (repository methods and any conversion/mapper functions) to
pass and store DateTime<Utc> values instead of strings, ensuring any
database/ORM conversion code is adapted to accept DateTime<Utc>.

In `@crates/infrastructure/alias-sqlite/src/builtin.rs`:
- Around line 3-5: The comment above DEFAULT_ALIASES is stale: it claims
"Provider ID is None for global aliases" but every tuple in DEFAULT_ALIASES
currently uses Some(provider_id); either implement global aliases with None
entries or update the comment to match the current data. Fix by editing the doc
comment for DEFAULT_ALIASES to accurately describe that each alias is
provider-scoped (provider_id is always Some) or, if global aliases are required,
add the intended tuples with Option::None provider IDs and corresponding tests;
reference DEFAULT_ALIASES and the tuple shape (&str, &str, Option<&str>) when
making the change.

In `@crates/infrastructure/alias-sqlite/src/repository.rs`:
- Around line 221-236: The builtin_aliases function currently captures a single
timestamp (let now = Utc::now().to_rfc3339()) and clones it for every
ModelAlias, causing all built-in aliases to share the same created_at; to make
timestamps unique, move the Utc::now().to_rfc3339() call inside the map closure
(e.g., compute created_at per item) so each ModelAlias constructed in the
iterator gets its own created_at instead of cloning a shared now; update
references to remove the outer now variable and stop cloning it.
- Around line 133-147: The comment "prevent cycles" is misleading because the
code around canonical_is_alias (query_row checking alias.canonical) only
prevents direct (depth-1) canonical aliases; update the comment to explicitly
state it only prevents direct aliasing (e.g., "prevent direct canonical-as-alias
(depth-1)"), and optionally rename/comment the canonical_is_alias variable or
add a FIXME if you want full transitive cycle detection later; keep the existing
logic (the query on alias.canonical and the
ModelAliasRepositoryError::InvalidAlias error) unchanged unless you intend to
implement full graph traversal cycle detection.

In `@crates/infrastructure/transport-axum/src/handlers/aliases.rs`:
- Around line 136-141: Replace the brittle string match on
e.to_string().contains("already exists") with a direct match on the repository
error enum (ModelAliasRepositoryError) in the alias creation handler in
handlers/aliases.rs: match the specific variant (e.g.,
ModelAliasRepositoryError::AlreadyExists or the exact variant name used by your
repo) and map it to the HttpError with StatusCode::BAD_REQUEST, code
"ALIAS_ALREADY_EXISTS" and the same message; leave the final generic Err(e) arm
to handle other errors. Ensure you reference the repository error type in the
match arm (bring it into scope if needed) so the handler discriminates
duplicates by type rather than by display text.
- Around line 93-96: The cycle pre-check currently calls
repo.find_by_alias(&canonical_model_id, None) ignoring the request's provider
scope; update the aliases handler to pass the request's provider_id (i.e. use
req.provider_id.map(ProviderId::new)) into find_by_alias so the lookup is
provider-scoped (change the call in the handler where
ModelId::new(req.canonical.clone()) is used). Also update the SQLite
repository's create() cycle query (in alias-sqlite::repository::create) to
include provider_id in the WHERE clause (make the SELECT 1 FROM model_aliases
... LIMIT 1 query filter on provider_id) so the database-level cycle check
matches find_by_alias semantics.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: cd85acf4-9c69-4b8d-9005-ff4e21bc6455

📥 Commits

Reviewing files that changed from the base of the PR and between 91f140a and 7a397b2.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (23)
  • Cargo.toml
  • apps/rook/Cargo.toml
  • apps/rook/src/config.rs
  • apps/rook/src/di.rs
  • apps/rook/tests/alias_routing_e2e.rs
  • apps/rook/tests/config_tests.rs
  • crates/application/rook-usecases/src/route_request.rs
  • crates/application/rook-usecases/tests/route_request_restrictions.rs
  • crates/domain/rook-core/src/model.rs
  • crates/domain/rook-core/src/ports.rs
  • crates/infrastructure/alias-sqlite/Cargo.toml
  • crates/infrastructure/alias-sqlite/src/builtin.rs
  • crates/infrastructure/alias-sqlite/src/lib.rs
  • crates/infrastructure/alias-sqlite/src/repository.rs
  • crates/infrastructure/db-migration/src/migrations/V5__model_aliases.sql
  • crates/infrastructure/transport-axum/src/alias_routes.rs
  • crates/infrastructure/transport-axum/src/bootstrap_helpers.rs
  • crates/infrastructure/transport-axum/src/handlers/aliases.rs
  • crates/infrastructure/transport-axum/src/handlers/mod.rs
  • crates/infrastructure/transport-axum/src/lib.rs
  • crates/infrastructure/transport-axum/src/routes.rs
  • crates/infrastructure/transport-axum/tests/alias_api.rs
  • crates/infrastructure/transport-axum/tests/format_translation_integration.rs

Comment thread crates/application/rook-usecases/src/route_request.rs
Comment thread crates/domain/rook-core/src/model.rs Outdated
Comment thread crates/infrastructure/alias-sqlite/src/builtin.rs
Comment thread crates/infrastructure/alias-sqlite/src/repository.rs Outdated
Comment thread crates/infrastructure/alias-sqlite/src/repository.rs
Comment thread crates/infrastructure/transport-axum/src/handlers/aliases.rs
Comment thread crates/infrastructure/transport-axum/src/handlers/aliases.rs Outdated
…tion

- Merged cache validation tests with model alias tests in config_tests.rs
- Exposed both cache() and alias_repository() in RouteRequest for management APIs
- Add alias resolution to execute_stream_with_format for streaming requests
- Change ModelAlias.created_at from String to DateTime<Utc> for consistency
- Generate unique timestamps per alias in builtin_aliases()
- Update builtin.rs comment to reflect provider-scoped aliases
- Clarify cycle detection as depth-1 only in repository
- Replace string matching with enum matching for AlreadyExists error
- Make cycle check provider-scoped in handler and repository query
- Add db_migration import to fix test initialization

All changes verified with full CI passing.
@yacosta738
yacosta738 merged commit e2df057 into main Jun 5, 2026
10 of 11 checks passed
@yacosta738
yacosta738 deleted the feat/model-aliasing-foundation branch June 5, 2026 08:39
@dallay-bot dallay-bot Bot mentioned this pull request Jun 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/ci CI, tooling, and automation area/testing Tests and testing infrastructure

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant