Skip to content

refactor(models): split entities.py into one model module per domain - #1255

Merged
peteski22 merged 14 commits into
mainfrom
refactor/models-into-domain-modules
Sep 16, 2026
Merged

peteski22 merged 14 commits into
mainfrom
refactor/models-into-domain-modules

Conversation

@peteski22

@peteski22 peteski22 commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

Description

Every table the gateway stores lived in one 2,016-line models file, so nearly every feature that touched the database edited it: 52 commits from 6 authors in the last 90 days. This moves each table into the model module of the domain it belongs to (budgets, pricing, tools, and so on), and moves the shared base types into a module of their own.

Nothing changes for someone running Otari. The database schema, the API, and the dashboard behave exactly as before, and no migration is added. The only wire-visible difference is three schema descriptions in the OpenAPI spec that named the old module.

Open PRs that add or edit a table will conflict with this. The update is mechanical; see "Updating an open PR" below.

Updating an open PR

Downstream: otari-ai. The enterprise overlay imports the old module in its runtime and tests, and three of its migration drift guards read the old file. A matching change is prepared and has to land with the otari pin bump to the first release that contains this PR. Nothing breaks before that bump, because the pin does not move on its own.

Measures

Measure Before After
entities.py 2,016 lines; 52 commits, 6 authors in 90 days removed
Largest model module entities.py, 2,016 lines tenancy.py, 1,588 lines
Model modules that declare tables 4 14

The issue's own measure (entities.py leaves the 90-day churn list) can only be read once 90 days have passed.

How to test it locally

make lint && make typecheck && make test
uv run --frozen --no-dev python scripts/oss_edition_smoke.py
make openapi-check && make postman-check
uv run alembic heads    # one head

To confirm the schema did not move, migrate a PostgreSQL database to head and run uv run alembic check on main and on this branch. Both report the same single item, a duplicate unique constraint on api_keys.key_hash that already exists on main, and nothing else. That item is why the issue's "no drift" criterion is not literally met; fixing it needs a migration, which this PR must not add.

What was checked:

  • Every commit on its own, not only the tip: make lint, make typecheck, make openapi-check, make postman-check, and a clean import of every model module and app entry point in a fresh interpreter.
  • Schema identical at every commit: the generated CREATE TABLE and CREATE INDEX statements for all 48 tables on SQLite and PostgreSQL, plus every mapper, column attribute and relationship, match main byte for byte.
  • Moved code is verbatim: all 137 top-level definitions in the two old files still exist with the same code. The only text changes are in comments and docstrings: a few pointed "above", "below" or at "neighbors" that now live in another module, and now state their own reason; and links to moved classes name their new module.
  • Suites on the tip: unit 3,698 passed; integration 2,416 passed; the OSS smoke gate passes; the committed dashboard client matches a fresh generation. After the last rebase onto security(auth): stop is_superuser from bypassing tenant role checks #1014, which changed no commit in this branch, lint, typecheck, the artifact checks, the schema comparison and the 226 integration tests for the areas security(auth): stop is_superuser from bypassing tenant role checks #1014 touched passed again. One integration test, test_mcp_dependency_ceiling, fails locally because this machine's Python cannot build the throwaway virtualenv the test creates. It fails the same way on main.

PR Type

  • New Feature
  • Bug Fix
  • Refactor
  • Documentation
  • Infrastructure / CI

Relevant issues

Fixes #1192

Part of #1171

Checklist

  • I understand the code I am submitting.
  • I have added or updated tests that cover my change (tests/unit, tests/integration).
  • I ran the Definition of Done checks locally (make lint, make typecheck, make test).
  • Documentation was updated where necessary.
  • If the API contract changed, I regenerated the OpenAPI spec (uv run python scripts/generate_openapi.py).

No tests were added: behavior does not change, and the existing suites cover it. Test files changed only their imports. The API contract did not change either; the spec, Postman collection and dashboard client were regenerated because three schema descriptions named the old module.

AI Usage

  • No AI was used.
  • AI was used for drafting/refactoring.
  • This is fully AI-generated.

AI Model/Tool used:

An AI coding agent, run by the author.

Any additional AI details you'd like to share:

The author made the layout decisions: one module per domain; a separate module for the billing User rather than renaming either User; new domain modules beside the existing ones whose names do not match a domain (provider_keys, playground, mcp), which keep their names for now; deleting the old module rather than keeping a re-export shim; and a shared base module. The agent did the moves and import rewrites, wrote the schema comparison run at every commit, and drafted this description.

NOTE:
When responding to reviewer questions, please respond yourself rather than copy/pasting reviewer comments into an AI and pasting back its answer. We want to discuss with you, not your AI :)

  • I am an AI Agent filling out this form (check box if true)

@peteski22
peteski22 requested a review from a team as a code owner September 16, 2026 17:38
@peteski22
peteski22 requested review from njbrake and tbille and removed request for a team September 16, 2026 17:38
@peteski22
peteski22 deployed to integration-tests September 16, 2026 17:38 — with GitHub Actions Active
@coderabbitai

coderabbitai Bot commented Sep 16, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 186 files, which is 86 over the limit of 100.

To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch.

Upgrade to a paid plan to raise the limit.

This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.

Check out review usage here.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 93d92ff6-d8fa-4ea6-b102-78a62508905a

📥 Commits

Reviewing files that changed from the base of the PR and between 09cb17d and 31bbec6.

⛔ Files ignored due to path filters (1)
  • docs/public/openapi.json is excluded by !docs/public/openapi.json
📒 Files selected for processing (186)
  • .github/skills/backend-standards/SKILL.md
  • ARCHITECTURE.md
  • alembic/env.py
  • scripts/demo_gif/seed.py
  • scripts/seed_usage_smoke.py
  • src/gateway/AGENTS.md
  • src/gateway/adapters/telemetry_storage_adapter.py
  • src/gateway/api/deps.py
  • src/gateway/api/routes/_passthrough.py
  • src/gateway/api/routes/_pipeline.py
  • src/gateway/api/routes/agent_telemetry.py
  • src/gateway/api/routes/aliases.py
  • src/gateway/api/routes/audio.py
  • src/gateway/api/routes/batches.py
  • src/gateway/api/routes/budgets.py
  • src/gateway/api/routes/catalog.py
  • src/gateway/api/routes/embeddings.py
  • src/gateway/api/routes/files.py
  • src/gateway/api/routes/images.py
  • src/gateway/api/routes/keys.py
  • src/gateway/api/routes/mcp.py
  • src/gateway/api/routes/models.py
  • src/gateway/api/routes/moderations.py
  • src/gateway/api/routes/organization_keys.py
  • src/gateway/api/routes/organization_pricing.py
  • src/gateway/api/routes/organization_routing.py
  • src/gateway/api/routes/organization_usage.py
  • src/gateway/api/routes/otlp.py
  • src/gateway/api/routes/pricing.py
  • src/gateway/api/routes/providers.py
  • src/gateway/api/routes/rerank.py
  • src/gateway/api/routes/routing.py
  • src/gateway/api/routes/routing_memory.py
  • src/gateway/api/routes/scoped_budgets.py
  • src/gateway/api/routes/search.py
  • src/gateway/api/routes/search_tools.py
  • src/gateway/api/routes/usage.py
  • src/gateway/api/routes/users.py
  • src/gateway/db/__init__.py
  • src/gateway/models/__init__.py
  • src/gateway/models/api_keys.py
  • src/gateway/models/base.py
  • src/gateway/models/budgets.py
  • src/gateway/models/entities.py
  • src/gateway/models/guardrails.py
  • src/gateway/models/inference.py
  • src/gateway/models/platform.py
  • src/gateway/models/playground.py
  • src/gateway/models/pricing.py
  • src/gateway/models/provider_keys.py
  • src/gateway/models/providers.py
  • src/gateway/models/routing.py
  • src/gateway/models/tenancy.py
  • src/gateway/models/tools.py
  • src/gateway/models/usage.py
  • src/gateway/models/users.py
  • src/gateway/repositories/users_repository.py
  • src/gateway/services/agent_telemetry_service.py
  • src/gateway/services/alias_service.py
  • src/gateway/services/batch_service.py
  • src/gateway/services/bootstrap_service.py
  • src/gateway/services/budget_reservation_ledger.py
  • src/gateway/services/budget_retiming.py
  • src/gateway/services/budget_service.py
  • src/gateway/services/dashboard_session_service.py
  • src/gateway/services/external_usage_service.py
  • src/gateway/services/file_service.py
  • src/gateway/services/file_store.py
  • src/gateway/services/log_writer.py
  • src/gateway/services/maintenance_mode_service.py
  • src/gateway/services/master_key_service.py
  • src/gateway/services/merged_catalog_service.py
  • src/gateway/services/model_access.py
  • src/gateway/services/organization_pricing_service.py
  • src/gateway/services/playground_service.py
  • src/gateway/services/policy_store.py
  • src/gateway/services/pricing_init_service.py
  • src/gateway/services/pricing_refresh_service.py
  • src/gateway/services/pricing_service.py
  • src/gateway/services/provider_store_service.py
  • src/gateway/services/routing/knn.py
  • src/gateway/services/runtime_settings_service.py
  • src/gateway/services/scoped_budget_service.py
  • src/gateway/services/search_tool_store_service.py
  • src/gateway/services/tenancy/organization_budget_service.py
  • src/gateway/services/tenancy/organization_guardrail_service.py
  • src/gateway/services/tenancy/provisioning_service.py
  • src/gateway/services/tenancy/workspace_activation_service.py
  • src/gateway/services/tenancy/workspace_budget_default_service.py
  • src/gateway/services/tenancy/workspace_code_execution_policy_service.py
  • src/gateway/services/tenancy/workspace_mcp_server_service.py
  • src/gateway/services/tenancy/workspace_service.py
  • src/gateway/services/tenancy/workspace_web_search_service.py
  • src/gateway/services/tool_settings_service.py
  • src/gateway/services/usage_admin_service.py
  • src/gateway/services/workspace_scope.py
  • tests/integration/test_agent_telemetry_admin.py
  • tests/integration/test_agent_telemetry_read.py
  • tests/integration/test_atomic_spend_update.py
  • tests/integration/test_budget_dashboard.py
  • tests/integration/test_budget_race_condition.py
  • tests/integration/test_budget_reservation_ledger.py
  • tests/integration/test_catalog_endpoint.py
  • tests/integration/test_deployment_operator_gate.py
  • tests/integration/test_deployment_user_administration.py
  • tests/integration/test_exact_budget_ledger.py
  • tests/integration/test_exact_money_columns.py
  • tests/integration/test_exclude_from_budget.py
  • tests/integration/test_external_usage_events.py
  • tests/integration/test_files_endpoint.py
  • tests/integration/test_growth_signal_lifecycle.py
  • tests/integration/test_hosted_credential_request_path.py
  • tests/integration/test_log_usage_commit_scope.py
  • tests/integration/test_mcp_stored_server_endpoints.py
  • tests/integration/test_messages_streaming_usage.py
  • tests/integration/test_migrated_key_auth.py
  • tests/integration/test_organization_budgets.py
  • tests/integration/test_organization_guardrails.py
  • tests/integration/test_organization_member_keys.py
  • tests/integration/test_organization_pricing_routes.py
  • tests/integration/test_organization_routing_policies_scope.py
  • tests/integration/test_organization_routing_write_scope.py
  • tests/integration/test_organization_usage_scope.py
  • tests/integration/test_otlp.py
  • tests/integration/test_otlp_logs_behavioral.py
  • tests/integration/test_otlp_metrics.py
  • tests/integration/test_playground.py
  • tests/integration/test_pricing_config.py
  • tests/integration/test_pricing_provenance_columns.py
  • tests/integration/test_pricing_refresh_endpoint.py
  • tests/integration/test_pricing_startup_warning.py
  • tests/integration/test_provider_credentials_api.py
  • tests/integration/test_schema_metadata_parity.py
  • tests/integration/test_scoped_budgets.py
  • tests/integration/test_session_model_catalog_scope.py
  • tests/integration/test_streaming_precommit_refund.py
  • tests/integration/test_streaming_ttft.py
  • tests/integration/test_tenancy_api.py
  • tests/integration/test_tenancy_attribution.py
  • tests/integration/test_tenancy_races.py
  • tests/integration/test_timezone_consistency.py
  • tests/integration/test_tool_billing_settlement.py
  • tests/integration/test_tool_settings_tenant_read.py
  • tests/integration/test_transaction_safety.py
  • tests/integration/test_usage_admin.py
  • tests/integration/test_usage_endpoint.py
  • tests/integration/test_usage_summary.py
  • tests/integration/test_usage_tracking.py
  • tests/integration/test_user_delete_preserve_logs.py
  • tests/integration/test_users_organization_scope.py
  • tests/integration/test_web_search_config_enforcement.py
  • tests/integration/test_workspace_activation.py
  • tests/integration/test_workspace_code_execution_policy.py
  • tests/integration/test_workspace_mcp_servers.py
  • tests/integration/test_workspace_member_budget_policies.py
  • tests/integration/test_workspace_scope.py
  • tests/integration/test_workspace_web_search.py
  • tests/unit/test_agent_telemetry_ingest.py
  • tests/unit/test_budget_cost_estimate.py
  • tests/unit/test_check_architecture.py
  • tests/unit/test_compute_cost.py
  • tests/unit/test_content_normalizer.py
  • tests/unit/test_dashboard_session.py
  • tests/unit/test_exact_money_schema_chain.py
  • tests/unit/test_gateway_rejection_logging_best_effort.py
  • tests/unit/test_key_fingerprint_schemas.py
  • tests/unit/test_knn_router.py
  • tests/unit/test_log_writer.py
  • tests/unit/test_master_key_service.py
  • tests/unit/test_models_default_pricing.py
  • tests/unit/test_organization_pricing_resolution.py
  • tests/unit/test_password_reset.py
  • tests/unit/test_password_sign_in.py
  • tests/unit/test_pricing_provenance_schema_chain.py
  • tests/unit/test_pricing_refresh_service.py
  • tests/unit/test_pricing_snapshot_history_schema_chain.py
  • tests/unit/test_pricing_unit_conventions.py
  • tests/unit/test_pricing_unit_origin_schema_chain.py
  • tests/unit/test_provider_store_service.py
  • tests/unit/test_runtime_settings_service.py
  • tests/unit/test_search_tool_store_service.py
  • tests/unit/test_secret_fields.py
  • tests/unit/test_tenancy_schema_chain.py
  • tests/unit/test_usage_cache_tokens.py
  • tests/unit/test_usage_log_metering_pool.py
  • web/src/client/schema.ts

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


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.

@peteski22
peteski22 requested a review from daavoo September 16, 2026 17:56
…ase.py

`Base` lived in entities.py and `UtcDateTime` plus the SQLModel mixins
lived in tenancy.py, so every model module imported one or both of those
files for a type rather than a table. Splitting the tables by domain
(#1192) needs a neutral home for them first, or each new domain module
would import another domain to get a column type.

Pure move: no table, column, constraint, or class name changes.
First domain out of entities.py (#1192). Importers name the domain
module, and the package import list registers it so the metadata stays
whole.

Pure move: the table, its columns and constraints, and the class name
are unchanged. The `workspace_id` comment is cut to what a reader needs,
since its pointer to "the three tables below" no longer held.
Budgets, scoped ceilings, the reservation ledger, reset logs, workspace
budget defaults, and MAX_COUNT_LIMIT leave entities.py for their domain
module (#1192). Importers name the domain module, and the package import
list registers it.

Pure move: tables, columns, constraints, and class names are unchanged.
…guardrails.py

The guardrails domain already had a model module for the request-body
field, so its two tables join it rather than a second module (#1192).
The package import list now registers it, since it declares tables.

Pure move: tables, columns, constraints, and class names are unchanged.
One comment that leaned on "its neighbors", which stayed behind, now
states its own reason.
Batches belong to the inference domain (#1192). The package import list
registers the new module and moves to one module per line, since it no
longer fits on one.

Pure move: the table, its columns, and the class name are unchanged.
…m.py

Runtime settings belong to the platform domain (#1192). Importers name
the domain module, and the package import list registers it.

Pure move: the table, its columns, and the class name are unchanged.
The deployment price list, organization overrides, catalog snapshots and
their history, and the unit and origin vocabularies leave entities.py
for their domain module (#1192). Importers name the domain module, and
the package import list registers it.

Pure move: tables, columns, constraints, and class names are unchanged.
One comment that pointed at the tables "above", which stayed behind, now
states its own reason.
…models/providers.py

Runtime provider instances and model aliases belong to the providers
domain (#1192). They get a domain-named module beside provider_keys.py,
which keeps its name until the providers domain is reshaped on its own.
Importers name the domain module, and the package import list registers
it.

Pure move: tables, columns, constraints, and class names are unchanged.
Docstrings that named these classes through entities now name their
module, and one comment drops the word "above". Three of those
docstrings belong to request schemas, so the OpenAPI spec and the
dashboard client are regenerated with the new module names.
The routing domain already had a model module for the policy schema, so
stored policies, routing memory, and router preferences join it rather
than a second module (#1192). The package import list now registers it,
since it declares tables, and its `__all__` names the three tables.

Pure move: tables, columns, constraints, and class names are unchanged.
Search tool credentials, uploaded file metadata, and the workspace MCP
server, code execution, and web search tables belong to the tools domain
(#1192). They get a domain-named module beside mcp.py, which stays a
request schema module. Importers name the domain module, and the package
import list registers it.

Pure move: tables, columns, constraints, and class names are unchanged.
Docstrings that named these classes through entities now name their
module, and two comments that leaned on tables elsewhere now state their
own reason. One of those docstrings belongs to a request schema, so the
OpenAPI spec and the dashboard client are regenerated with the new
module name.
…ls/usage.py

Usage rows and coding-agent telemetry belong to the usage domain
(#1192). Importers name the domain module, and the package import list
registers it.

Pure move: tables, columns, constraints, and class names are unchanged.
…to models/tenancy.py

Dashboard sessions and the setup guide's per-workspace state belong to
the tenancy domain (#1192), so they join its model module as its two
declarative tables. The module docstring now says why both styles sit in
one module, and `__all__` names the two tables.

Pure move: tables, columns, constraints, and class names are unchanged.
One comment that pointed at a table "above", now in budgets, states its
own reason.
…d drop entities.py

The gateway's string-keyed `users` table is the last one in entities.py
(#1192). It gets its own module rather than joining tenancy.py, because
tenancy.py already declares the sign-in `User` and the two are different
identities whose convergence is still undecided. With it gone
entities.py is empty, so the module and its entry in the package import
list go too.

Pure move: the table, its columns, and the class name are unchanged.
With entities.py gone (#1192), the gateway and skill guidance, the
architecture overview, and two model docstrings named a module that no
longer exists. They now say where a table goes: the model module of its
domain, registered in the package import list. The gateway guidance also
names the two `User` classes, since the split puts them in different
modules and the names alone do not say which identity is which.
@peteski22
peteski22 force-pushed the refactor/models-into-domain-modules branch from 4fe7cf6 to 31bbec6 Compare September 16, 2026 18:17
@peteski22
peteski22 deployed to integration-tests September 16, 2026 18:17 — with GitHub Actions Active
@peteski22
peteski22 enabled auto-merge (squash) September 16, 2026 18:30

@tbille tbille left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Verified independently on the PR head: the full SQLAlchemy metadata (all 48 tables: columns, types, defaults, FKs + ondelete, constraints, indexes, mappers) is byte-identical to main; all 138 top-level definitions from the old modules exist with AST-identical code; every changed line outside models/ and docs is an import rewrite; the model-module dependency graph is acyclic (table modules depend only on base, money, secret_fields); lint, architecture check, and single-head check pass locally.

One leftover the sweep missed, in a file this PR does not touch so it cannot carry an inline comment: alembic/versions/a7d3f5b1c9e4_add_organization_model_pricing.py line 11 still points readers at gateway.models.entities.OrganizationModelPricing, which this PR deletes. Should read gateway.models.pricing.OrganizationModelPricing. Fine as a follow-up commit here or a separate one-line PR.

Reviewed by Claude Code (Fable 5), run by the reviewer.

@peteski22
peteski22 merged commit 4ce280f into main Sep 16, 2026
22 checks passed
@peteski22
peteski22 deleted the refactor/models-into-domain-modules branch September 16, 2026 18:41

This branch was successfully deployed

1 active deployment
integration-tests — 31bbec68 Deployed Sep 16, 2026 by peteski22 via test-integration #2090
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.

Models into domain modules

2 participants