Skip to content

refactor(BA-5979): split deployment search into admin and scoped layers#11522

Draft
jopemachine wants to merge 9 commits into
mainfrom
refactor/BA-5979-deployment-admin-repository
Draft

refactor(BA-5979): split deployment search into admin and scoped layers#11522
jopemachine wants to merge 9 commits into
mainfrom
refactor/BA-5979-deployment-admin-repository

Conversation

@jopemachine

@jopemachine jopemachine commented May 8, 2026

Copy link
Copy Markdown
Member

Summary

Split the deployment search/projection paths so each axis (admin / user / project / project-summary / GraphQL DataLoader) has a dedicated action and repository method.

  • API path (new, v2) for create / update / get / activate_revision reads through EndpointRow.to_model_deployment_data() directly at the db_source/ boundary, bypassing the DeploymentInfo intermediate.
  • No-scope admin queries live in a new DeploymentAdminRepository + DeploymentAdminService + DeploymentAdminProcessors package, mirroring the vfolder / login_client_type admin-split convention.
  • User-scoped (my_search) and project-scoped (project_search) reads each get their own Search{User,Project}DeploymentsAction and a {User,Project}DeploymentSearchScope; the scope filter lives in the repository, not on the adapter as an injected base_condition.
  • Legacy v1 REST POST /deployments/search keeps its no-path-segment shape so existing CLI/SDK callers don't break — the project scope now travels inline on the body via the new SearchLegacyDeploymentsRequest (just SearchDeploymentsRequest + a required project_id), and the handler resolves it to a ProjectDeploymentSearchScope and routes through the same search_project_deployments processor as v2.
  • GraphQL DataLoader (batch_load_by_ids) routes through admin_search_deployments (the only remaining unscoped search after the legacy no-scope action was dropped). The parent resolver has already authorised access to whatever references these IDs, so the admin processor is acceptable here — the admin label is enforced at the resolver, not at the DataLoader.

Resolves BA-5979. Builds on top of #11494 (BA-5963) which is already on main.

Layer-by-layer changes

Models

File Before After
models/endpoint/row.py to_deployment_info only adds to_model_deployment_data() — projects directly to ModelDeploymentData (used by every new ModelDeploymentData-returning DB-source method)

Repository

Repository Method Before After
DeploymentRepository get_endpoint_info returns DeploymentInfo unchanged (write helpers / lifecycle still use it)
DeploymentRepository get_deployment_data new — direct ModelDeploymentData for the API path
DeploymentRepository search_endpoints returns DeploymentInfo unchanged (only internal callers remain)
DeploymentRepository search_user_deployments new — user-scoped, returns ModelDeploymentData
DeploymentRepository search_project_deployments new — project-scoped, returns ModelDeploymentData
DeploymentRepository search_project_deployment_summary new (renamed from search_deployments_in_project) — still backs project admin list pages with DeploymentSummaryData
DeploymentAdminRepository search_deployments new — admin (no-scope) projection straight to ModelDeploymentData

repositories/deployment/db_source/db_source.py

Helper / field Before After
EndpointRow.to_model_deployment_data() called by every new ModelDeploymentData-returning DB-source method instead of routing through DeploymentInfo
DeploymentDBSource._storage_manager held removed — never read; storage I/O lives on DeploymentStorageSource (still owned by DeploymentRepository)

Scopes

Scope Before After
ProjectDeploymentSearchScope exists unchanged
UserDeploymentSearchScope newEndpointRow.created_user == user_id

Action

Action Before After
SearchDeploymentsAction exists (no-scope, used by every search path) dropped — every search path now has a scope or lives under the admin processor
AdminSearchDeploymentsAction new, lives under the admin package (admin-only callers + DataLoader)
SearchUserDeploymentsAction new — user-scoped
SearchProjectDeploymentsAction new — project-scoped, returns ModelDeploymentData
SearchProjectDeploymentSummaryAction existed as SearchDeploymentsInProjectAction renamed — returns DeploymentSummaryData for project admin list pages

Service

Service Handler Before After
DeploymentService create_deployment get_endpoint_info + _convert_deployment_info_to_data get_deployment_data
DeploymentService update_deployment controller returns DeploymentInfo → convert controller updates, then get_deployment_data
DeploymentService get_deployment_by_id get_endpoint_info + convert get_deployment_data
DeploymentService activate_revision controller returns DeploymentInfo → convert controller activates, then get_deployment_data
DeploymentService search_user_deployments new — user-scoped
DeploymentService search_project_deployments new — project-scoped, returns ModelDeploymentData; serves both the v2 adapter and the legacy v1 REST handler
DeploymentService search_project_deployment_summary renamed from search_deployments_in_project; still returns the lighter DeploymentSummaryData
DeploymentAdminService admin_search_deployments new — calls DeploymentAdminRepository.search_deployments
DeploymentService (private) _convert_deployment_info_to_data existed removed with the legacy converter

Processor

Processor Field Before After
DeploymentProcessors search_deployments exists (no-scope) dropped
DeploymentProcessors search_user_deployments new
DeploymentProcessors search_project_deployments new
DeploymentProcessors search_project_deployment_summary renamed from search_deployments_in_project
DeploymentAdminProcessors (new) admin_search_deployments new package, registered in the top-level Processors

Adapter routing

Adapter method Before action After action
admin_search SearchDeploymentsAction (regular processor) AdminSearchDeploymentsAction (admin processor)
my_search SearchDeploymentsAction + created_user==user_id base-condition SearchUserDeploymentsAction + UserDeploymentSearchScope (regular processor)
project_search SearchDeploymentsAction + project==project_id base-condition SearchProjectDeploymentsAction + ProjectDeploymentSearchScope (regular processor)
batch_load_by_ids (DataLoader) SearchDeploymentsAction + by_ids condition AdminSearchDeploymentsAction + by_ids condition (admin processor; the parent resolver already authorised access)

Legacy v1 REST handler

File Change
api/rest/deployment/handler.py POST /deployments/search keeps its no-path-segment URL but now takes a SearchLegacyDeploymentsRequest body (project_id required), builds a ProjectDeploymentSearchScope, and routes through the shared search_project_deployments processor. RBAC therefore now enforces project-scoped MODEL_DEPLOYMENT:READ on this endpoint.
api/rest/tree.py unchanged from origin/main
client/cli/deployment.py v1 ./bai deployment list now requires --project-id.

Tests

File Change
tests/unit/manager/repositories/deployment/test_endpoint_projection.py new unit test for EndpointRow.to_model_deployment_data() covering reversed revisions order, dangling current_revision references, and the lifecycle status mapping
tests/component/deployment/conftest.py new regular_user_project_model_deployment_read_permission fixture — grants PROJECT-scoped MODEL_DEPLOYMENT:READ to regular_user_fixture so legacy-path regular-user search tests can pass the now-enforced RBAC check

Test plan

  • pants fmt fix lint check on every changed file
  • tests/unit/manager/repositories/deployment/test_endpoint_projection.py
  • tests/component/deployment/test_deployment_lifecycle.py::TestUserAccessDeployment::test_user_searches_empty_deployments (now seeds the project-scoped read role; previously passed only because the action was unscoped)
  • Live ./bai smoke after merge: admin search, my search, project search, GraphQL modelDeployment resolver (DataLoader), legacy POST /deployments/search
  • CI

🤖 Generated with Claude Code


📚 Documentation preview 📚: https://sorna--11522.org.readthedocs.build/en/11522/


📚 Documentation preview 📚: https://sorna-ko--11522.org.readthedocs.build/ko/11522/

@github-actions github-actions Bot added the size:XL 500~ LoC label May 8, 2026
@github-actions github-actions Bot added comp:manager Related to Manager component comp:common Related to Common component labels May 8, 2026
Comment thread src/ai/backend/manager/repositories/deployment/db_source/db_source.py Outdated
Comment thread src/ai/backend/manager/repositories/deployment/repository.py Outdated
Comment thread src/ai/backend/manager/repositories/deployment/repository.py Outdated
@github-actions github-actions Bot added comp:client Related to Client component area:docs Documentations comp:cli Related to CLI component labels May 15, 2026
@jopemachine jopemachine force-pushed the refactor/BA-5979-deployment-admin-repository branch from 6f84f80 to 4b8f701 Compare May 18, 2026 01:42
@jopemachine jopemachine marked this pull request as ready for review May 18, 2026 01:44
@jopemachine jopemachine requested a review from a team as a code owner May 18, 2026 01:44
Copilot AI review requested due to automatic review settings May 18, 2026 01:44

Copilot AI 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.

Pull request overview

Splits the deployment search/projection paths into a dedicated admin layer plus scoped (user / project) actions. The v2 API path now projects EndpointRow directly to ModelDeploymentData via a new to_model_deployment_data() method, bypassing the DeploymentInfo intermediate. A new DeploymentAdminRepository + DeploymentAdminService + DeploymentAdminProcessors package mirrors the vfolder/login-admin convention, and my_search / project_search get their own scopes and actions. The legacy v1 POST /deployments/search keeps its URL but now requires project_id in the body.

Changes:

  • New EndpointRow.to_model_deployment_data() and ModelDeploymentStatus.from_lifecycle(); service-side _convert_deployment_info_to_data removed.
  • New DeploymentAdminRepository / DeploymentAdminService / DeploymentAdminProcessors + AdminSearchDeploymentsAction; user/project scoped search actions and UserDeploymentSearchScope added; legacy v1 search routed through project scope.
  • DTO renames: AdminSearchDeploymentsInput/PayloadSearchDeploymentsInput/Payload; SearchDeploymentsRequestSearchLegacyDeploymentsRequest (requires project_id); CLI bai deployment list --project-id now required.

Reviewed changes

Copilot reviewed 45 out of 45 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/ai/backend/manager/models/endpoint/row.py Adds to_model_deployment_data() projection.
src/ai/backend/common/data/model_deployment/types.py Adds ModelDeploymentStatus.from_lifecycle().
src/ai/backend/manager/data/deployment/types.py Renames DeploymentInfoSearchResultModelDeploymentDataSearchResult.
src/ai/backend/manager/repositories/deployment/db_source/db_source.py Drops storage_manager from DBSource; adds get_deployment_data, admin_search_deployments, search_user_deployments, renames project search methods.
src/ai/backend/manager/repositories/deployment/admin_repository.py New admin repository owning unscoped search.
src/ai/backend/manager/repositories/deployment/repository.py / repositories.py / init.py Wire admin repository, add new scoped search methods.
src/ai/backend/manager/repositories/deployment/types/endpoint.py / init.py New UserDeploymentSearchScope.
src/ai/backend/manager/services/deployment/admin_service.py New admin service.
src/ai/backend/manager/services/deployment/processors.py Adds DeploymentAdminProcessors and scoped processors.
src/ai/backend/manager/services/deployment/service.py Removes legacy converter; switches create/update/get/activate to get_deployment_data; adds scoped search service methods.
src/ai/backend/manager/services/deployment/actions/admin_search_deployments.py / search_user_deployments.py / search_project_deployments.py / search_project_deployment_summary.py Action classes for the new split.
src/ai/backend/manager/services/factory.py / processors.py Register admin service + processors.
src/ai/backend/manager/api/adapters/deployment/adapter.py Routes admin_search / my_search / project_search / batch_load_by_ids to the new actions with scopes.
src/ai/backend/manager/api/rest/v2/deployment/handler.py Uses renamed SearchDeploymentsInput.
src/ai/backend/manager/api/rest/deployment/{handler,adapter}.py Legacy v1 search uses SearchLegacyDeploymentsRequest and routes through project-scoped action.
src/ai/backend/manager/api/gql/deployment/resolver/deployment.py Renamed input class.
src/ai/backend/common/dto/manager/{deployment,v2/deployment}/* DTO renames and shared SearchDeploymentsInput/Payload.
src/ai/backend/common/metrics/metric.py Adds DEPLOYMENT_ADMIN_REPOSITORY layer.
src/ai/backend/client/** Client/CLI renames; bai deployment list now requires --project-id.
tests/unit/manager/repositories/deployment/test_endpoint_projection.py New unit tests for the projection.
tests/unit/manager/repositories/deployment/test_search_project_deployment_summary.py Renames to match new repository method.
tests/unit/manager/services/deployment/test_deployment_service.py Removes tests for deleted converter.
tests/component/deployment/conftest.py Adds RBAC fixture granting PROJECT-scoped MODEL_DEPLOYMENT:READ.
tests/component/deployment/test_deployment*.py, tests/unit/client_v2/test_deployment.py Adopt renamed inputs and new RBAC fixture.
changes/11522.enhance.md News fragment.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/ai/backend/common/data/model_deployment/types.py
Comment thread src/ai/backend/manager/models/endpoint/row.py Outdated
Comment thread src/ai/backend/manager/repositories/deployment/admin_repository.py Outdated
Comment thread src/ai/backend/client/cli/deployment.py
Comment thread src/ai/backend/manager/api/adapters/deployment/adapter.py
Comment thread src/ai/backend/common/data/model_deployment/types.py Outdated
@jopemachine jopemachine force-pushed the refactor/BA-5979-deployment-admin-repository branch from db1f00b to 4b8f701 Compare May 18, 2026 01:52
@jopemachine jopemachine marked this pull request as draft May 18, 2026 02:22
@jopemachine jopemachine force-pushed the refactor/BA-5979-deployment-admin-repository branch from 0c2a928 to 646ffdb Compare May 18, 2026 03:21
@jopemachine jopemachine marked this pull request as ready for review May 18, 2026 03:21
@jopemachine jopemachine requested review from a team and HyeockJinKim May 18, 2026 03:35
Comment on lines +5223 to +5233
"""
Added in UNRELEASED. Sub-step within a deployment's current lifecycle phase. ``None`` when the deployment is not in a sub-step-bearing phase.
"""
enum DeploymentLifecycleSubStep
@join__type(graph: STRAWBERRY)
{
DEPLOYING_PROVISIONING @join__enumValue(graph: STRAWBERRY)
DEPLOYING_ROLLING_BACK @join__enumValue(graph: STRAWBERRY)
DEPLOYING_COMPLETED @join__enumValue(graph: STRAWBERRY)
}

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.

have we decided to show substep?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I don't want to show this value yet.

@jopemachine jopemachine force-pushed the refactor/BA-5979-deployment-admin-repository branch from a0b38c2 to 8c37fc5 Compare May 26, 2026 04:07
jopemachine and others added 3 commits May 26, 2026 13:47
Restructures the deployment search/projection stack so every layer
(db_source → repository → service → processor → adapter → REST/GQL/SDK/CLI)
makes the search scope explicit:

- Admin-only search: ``DeploymentAdminRepository``/``DeploymentAdminService``
  with ``admin_search_deployments`` at every level. The DBSource side
  carries the prefix too so the unscoped intent is visible alongside
  the scoped ``search_user_deployments`` / ``search_project_deployments``
  variants in the same namespace.
- User/project search: dedicated actions (``SearchUserDeploymentsAction``,
  ``SearchProjectDeploymentsAction``) with their own RBAC scope. The v2
  adapter's ``my_search`` / ``project_search`` now route through these.
- Project summary: ``SearchProjectDeploymentSummaryAction`` for the
  lightweight project-admin list view (renamed from
  ``SearchDeploymentsInProject``).

EndpointRow → ModelDeploymentData projection moves onto the row as
``EndpointRow.to_model_deployment_data()``, following the existing
``to_deployment_info`` shape and the BA-6056 relationship split: the
projection reads ``current_revision_row`` / ``deploying_revision_row``
directly instead of scanning a ``revisions`` list. The fragile
``DeploymentInfo``-via-helper path (``_convert_deployment_info_to_data``)
disappears together with its placeholder defaults; service callers now
fetch ``ModelDeploymentData`` straight from the repository.

API surface:

- v1 REST: ``POST /deployments/search`` contract preserved; a new
  ``SearchLegacyDeploymentsRequest`` (standalone, not a subclass) carries
  the ``project_id`` inline on the body, and the handler routes through
  ``SearchProjectDeploymentsAction``. v1 SDK, ``./bai`` CLI, and
  ``client/func`` migrate to the new request type.
- v2 REST/GQL: ``SearchDeploymentsInput`` / ``SearchDeploymentsPayload``
  (neutral names, no admin prefix) are shared by admin / project / my
  variants — mirrors the vfolder / model_card / user / session v2
  convention. Admin and scope variants only differ in URL + middleware.
- GraphQL ``deployment_loader`` keeps the unscoped batch lookup it was
  born with, now routed through ``admin_search_deployments`` rather
  than the legacy slot — parent resolvers stay responsible for RBAC.

Tests:

- ``tests/unit/manager/repositories/deployment/test_endpoint_projection.py``
  pins the new ``EndpointRow.to_model_deployment_data`` against the
  ``current_revision_row`` relationship (replaces the BA-5963 list-order
  regression test, which is structurally impossible under the new lookup).
- v1 component / unit / SDK tests migrate to ``SearchLegacyDeploymentsRequest``.
- Existing ``SearchProjectDeploymentSummary`` action becomes
  ``@dataclass(frozen=True)`` to match the rest of the search-action set.

Co-Authored-By: Gyubong <gbl@lablup.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The DBSource's ``get_deployment_data`` was still selectinload-ing
``EndpointRow.revisions`` while the projection method
``to_model_deployment_data`` reads ``current_revision_row`` /
``deploying_revision_row`` directly. Those relationships default to
``lazy="select"``, so accessing them on a row fetched under an async
session would trip the SQLAlchemy ``greenlet_spawn`` error on every API
path that calls ``get_deployment_data`` (``create_deployment``,
``update_deployment``, ``get_deployment_by_id``, ``activate_revision``).

Align the eager-load with what ``admin_search_deployments`` /
``search_user_deployments`` / ``search_project_deployments`` already do,
and drop the now-unused ``revisions`` chain.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… shape

Drop nested revision / policy / id-list payloads from
``ModelDeploymentData`` so the projection only ever carries the endpoint
row's own columns. The v2 GQL node already exposes only scope IDs and
defers the revision spec / policy / replica list / auto-scaling rules /
access tokens to dedicated DataLoader resolvers; the v1 REST surface
now mirrors that — clients fetch the spec through the nested endpoints
(``/deployments/{id}/revisions/{revision_id}``,
``/deployments/{id}/policy``, etc.). **Breaking change for v1 REST**:
``DeploymentDTO.current_revision`` and ``DeploymentDTO.deployment_policy``
are removed; ``current_revision_id`` and ``deploying_revision_id`` are
exposed instead.

With the projection no longer touching ``EndpointRow.current_revision_row``
/ ``deploying_revision_row`` / ``deployment_policy``, the four search /
get paths that consume ``to_model_deployment_data`` drop their
``selectinload`` chains — each was incurring a per-row dead eager load.
The v2 ``DeploymentNode.policy`` field is removed for the same reason
(GQL never read it; the resolver always went through the policy
DataLoader). The v1 handler's revision-variant resolver is no longer
needed on the deployment DTO path; ``_deployment_dto`` becomes
synchronous.

Tests:
- ``test_endpoint_projection`` drops the now-obsolete revision-row
  scenarios (the BA-5963 list-order regression is structurally
  impossible once the projection only reads columns) and keeps the
  column-pass-through plus the lifecycle status mapping pins.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jopemachine and others added 5 commits May 26, 2026 13:47
…factor

a70582c dropped the ``policy`` field from ``DeploymentNode`` (v2 GQL
response) but left this test module still building / asserting on it,
so mypy hit ``"DeploymentNode" has no attribute "policy"`` in 7 spots
and the typecheck CI job failed. Remove the ``policy`` default from the
test factory, drop the three now-meaningless policy test cases, and
prune the imports they exclusively used.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The v1 ``DeploymentDTO.sub_step`` had no v2 counterpart, so the GQL
``ModelDeployment`` node and the v2 REST ``DeploymentNode`` were missing
the rolling-update progress signal. Promote ``DeploymentLifecycleSubStep``
to ``common.data.model_deployment.types`` so the v2 DTO layer can reach it
(``common/`` cannot import from ``manager/``), repoint every existing
importer at the common location, then surface the enum on:

- ``DeploymentNode.sub_step`` (v2 REST DTO)
- ``ModelDeployment.subStep`` (GQL, backed by a new
  ``DeploymentLifecycleSubStep`` GQL enum)

The v2 adapter pipes ``ModelDeploymentData.sub_step`` through to the
new DTO field, and the v2 schema dump picks up the enum + field.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI typecheck on the previous commit failed because three sokovan
deployment test files still pulled ``DeploymentLifecycleSubStep`` from
``ai.backend.manager.data.deployment.types`` — that re-export was the
casualty of moving the enum to ``common.data.model_deployment.types``.
Update the test imports to the common location to match every other
manager/sokovan caller migrated in the previous commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The projection unit test drove a bare MagicMock, so a column the
projection reads that gets renamed/removed on EndpointRow would be
silently fabricated and the test would keep passing. Bind the stub to
spec=EndpointRow so an unset, non-existent column read raises
AttributeError instead.

Also trim the BA-5979 news fragment to the EndpointRow ->
ModelDeploymentData projection change only; the scope-based
service/repository split is out of this PR's intended scope.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Fold DeploymentAdminRepository / DeploymentAdminService /
DeploymentAdminProcessors back into DeploymentRepository /
DeploymentService / DeploymentProcessors. admin_search_deployments now
lives alongside the scoped search methods, distinguished only by its
admin_ prefix and the absence of a scope filter — no dedicated admin
classes. Drops the now-unused DEPLOYMENT_ADMIN_REPOSITORY metric layer.

Also fix two rebase-induced CI breakages:
- main renamed execute_batch_querier's scope= kwarg to scopes=; update
  the user/project scoped search calls to scopes=[scope].
- repoint the new sokovan deploying_rolling_back handler (and its test)
  at the common DeploymentLifecycleSubStep location, since this branch
  moved the enum off manager.data.deployment.types.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@jopemachine jopemachine force-pushed the refactor/BA-5979-deployment-admin-repository branch from 96ccbc7 to 54c7ab8 Compare May 26, 2026 04:49
The Admin prefix on the action signals the unscoped superadmin search
intent, but the result is a plain data/count/paging bag identical to the
scoped variants, so it does not need the prefix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jopemachine jopemachine marked this pull request as draft May 29, 2026 06:27
@github-actions

Copy link
Copy Markdown

This PR has had no activity for 14 days and has been marked as stale. It will be closed on 2026-07-02 (in 14 days) if no further activity occurs. Push a commit or leave a comment to keep it open.

@github-actions github-actions Bot added the stale label Jun 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:docs Documentations comp:cli Related to CLI component comp:client Related to Client component comp:common Related to Common component comp:manager Related to Manager component size:XL 500~ LoC stale

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants