Skip to content

feat(BA-5982): expose revision_number, separate Spec/Data on DeploymentInfo read path#11529

Draft
jopemachine wants to merge 10 commits intomainfrom
feature/BA-5982-expose-revision-number
Draft

feat(BA-5982): expose revision_number, separate Spec/Data on DeploymentInfo read path#11529
jopemachine wants to merge 10 commits intomainfrom
feature/BA-5982-expose-revision-number

Conversation

@jopemachine
Copy link
Copy Markdown
Member

@jopemachine jopemachine commented May 8, 2026

Summary

Two related changes on the deployment read path:

1. Expose revision_number end-to-end

deployment_revisions.revision_number was settable as a ModelRevisionFilter / ModelRevisionOrderField key but the value was dropped at every layer above the row, so clients could filter/order by it without ever reading it back. It now flows through ModelRevisionData -> RevisionNode DTO -> the GraphQL ModelRevision type (added via gql_added_field(NEXT_RELEASE_VERSION)).

2. Stop using Spec types on the read path

Per project convention (KernelSpec / SessionSpec docstrings — "Full spec to create one Row"), XXXSpec types are write-only. But ModelRevisionSpec, ReplicaSpec, and DeploymentNetworkSpec were all being loaded from DB and passed around the read path via DeploymentInfo, forcing the write-shaped revision_number field onto ModelRevisionSpec and bleeding write types into output DTOs.

Audit + fixes per type:

Type Before After
ModelRevisionSpec Loaded from DB via Row.to_model_revision_spec(); on DeploymentInfo.model_revisions Write-only. DeploymentInfo.model_revisions: list[ModelRevisionData]
ReplicaSpec On DeploymentInfo.replica_spec Write-only. New ReplicaCountData (carries replica_count / desired_replica_count / target_replica_count) on DeploymentInfo.replica_counts
DeploymentNetworkSpec On DeploymentInfo.network and ModelDeploymentData.network_access Write-only. New DeploymentNetworkData on both fields
DeploymentSessionSpec Defined but unused Removed
ResourceSpec / ExecutionSpec / PresetValueSpec Already write-only (only nested inside ModelRevisionSpec / Creator / Draft) Unchanged

Concrete restructuring to support the rule:

  • Drop ModelRevisionSpec.revision_number. revision_number lives only on ModelRevisionData now.
  • Add the write-time fields downstream callers consumed (startup_command, bootstrap_script, callback_url, extra_vfolder_mounts, preset_values) onto ModelRevisionData so write paths can rebuild specs from data without round-tripping through the DB.
  • Remove Row.to_model_revision_spec(), DeploymentRepository.get_current_revision_spec / get_revision_spec and the matching db_source methods. Spec is no longer loadable from DB.
  • Rewire write-side callers off Spec getters: _build_creator_from_revision_data, SessionValidationSpec.from_revision, and DeploymentSessionDraftBuilder.build all take ModelRevisionData and reconstruct ResourceSpec / ExecutionSpec locally. RevisionDraft.to_model_revision_spec (the legitimate draft -> spec write transform) is unchanged.
  • EndpointRow.to_deployment_info populates revisions via rev_row.to_data() instead of to_model_revision_spec().
  • Sokovan callers that need a spec (controller update_deployment validation, route executor draft assembly) fetch via repository.get_revision / get_current_revision — both return ModelRevisionData.

Side effect: _convert_deployment_info_to_data

Now that info.model_revisions is already ModelRevisionData, the 35-line spec-unpack in service._convert_deployment_info_to_data collapses to a 6-line lookup.

Resolves BA-5982.

Test plan

  • pants test tests/unit/manager:: (services, sokovan, repositories, api adapters)
  • Manual verification on a live manager:
    • Create a deployment, then call the v2 GraphQL revisionHistory query — revision_number is populated.
    • Filter / order by revision_number via ModelRevisionFilter / ModelRevisionOrderField.
    • Trigger admin revision refresh; verify _build_creator_from_revision_data produces a valid new revision.
    • Trigger a rolling update on a deployment with an existing route; verify route session draft assembly still works (now via repository.get_revision).

The `deployment_revisions.revision_number` column has been settable by
clients via filter and sort fields on `ModelRevisionFilter` and
`ModelRevisionOrderField`, but the value itself was dropped at every
layer above the row. Plumb it end-to-end so callers can render
"Revision #N" labels and order revisions client-side without an extra
round-trip.

* `ModelRevisionData` (data layer) gains a required `revision_number` and
  is populated from `DeploymentRevisionRow.to_data()`.
* `ModelRevisionSpec` gains an optional `revision_number` so the existing
  draft → spec construction path (which has no row yet) is unaffected,
  while the row → spec path (`to_model_revision_spec()`) carries the
  number forward. The legacy `_convert_deployment_info_to_data` bridge
  asserts the spec carries a number and propagates it.
* `RevisionNode` DTO and the adapter `_revision_data_to_dto` mapping pass
  the field through to REST v2 responses.
* `ModelRevision` GraphQL type exposes the field via `gql_added_field`
  with `NEXT_RELEASE_VERSION`.

BA-5982
Copilot AI review requested due to automatic review settings May 8, 2026 09:22
@github-actions github-actions Bot added size:M 30~100 LoC comp:manager Related to Manager component comp:common Related to Common component labels May 8, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR propagates deployment_revisions.revision_number through the manager read path so clients can display “Revision #N” and order revisions client-side without extra queries.

Changes:

  • Add revision_number to ModelRevisionData (required) and ModelRevisionSpec (optional) and populate from DeploymentRevisionRow.
  • Expose revision_number via REST v2 RevisionNode and GraphQL ModelRevision (gated with gql_added_field(NEXT_RELEASE_VERSION)).
  • Update unit-test fixtures to include the new required field.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/unit/manager/services/deployment/test_deployment_service.py Update fixtures to construct ModelRevisionData/Spec with revision_number.
tests/unit/manager/services/deployment/test_deployment_crud_actions.py Update fixtures to include revision_number in ModelRevisionData.
tests/unit/manager/api/adapters/test_deployment_adapter.py Update adapter test fixture to include revision_number.
src/ai/backend/manager/services/deployment/service.py Require revision_number when bridging DeploymentInfoModelRevisionData.
src/ai/backend/manager/models/deployment_revision/row.py Populate revision_number in row → spec/data projections.
src/ai/backend/manager/data/deployment/types.py Add revision_number field to spec/data layer types and document semantics.
src/ai/backend/manager/api/gql/deployment/types/revision.py Add GraphQL revision_number field on ModelRevision.
src/ai/backend/manager/api/adapters/deployment/adapter.py Map ModelRevisionData.revision_number into RevisionNode DTO.
src/ai/backend/common/dto/manager/v2/deployment/response.py Add revision_number to REST v2 RevisionNode response model.
changes/11529.feature.md Changelog entry for exposing revision_number over GraphQL/REST.

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

Comment on lines 252 to 260
if rev is not None:
if rev.revision_id is None:
raise ValueError(f"ModelRevisionSpec has no revision_id for deployment {info.id}")
if rev.revision_number is None:
raise ValueError(f"ModelRevisionSpec has no revision_number for deployment {info.id}")
revision = ModelRevisionData(
id=rev.revision_id,
revision_number=rev.revision_number,
cluster_config=ClusterConfigData(
Comment on lines +882 to +884
# ``deployment_revisions.revision_number`` (UNIQUE per endpoint). Useful
# for surfacing "Revision #N" labels and for client-side ordering
# without having to re-query the server.
Co-authored-by: octodog <mu001@lablup.com>
@github-actions github-actions Bot added the area:docs Documentations label May 8, 2026
@jopemachine jopemachine requested a review from a team May 8, 2026 09:31
Comment on lines +433 to +435
class ModelRevisionSpec(ConfiguredModel):
revision_id: DeploymentRevisionID | None = None
revision_number: int | None = None
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 think there's any reason to go in there, is there?

…delRevisionData on read

`ModelRevisionSpec` is by convention a write/create-time type (matches
`KernelSpec`/`SessionSpec` docstrings — "Full spec to create one Row"),
yet `DeploymentInfo.model_revisions` carried it on the read path,
forcing `service._convert_deployment_info_to_data` to unpack the spec
back into a `ModelRevisionData` field-by-field, and bleeding the
write-shaped `revision_number` field onto the spec.

- Drop `ModelRevisionSpec.revision_number`; `Row.to_model_revision_spec`
  no longer fills it. `revision_number` now lives only on
  `ModelRevisionData` where it belongs.
- Switch `DeploymentInfo.model_revisions` to `list[ModelRevisionData]`
  and rename `resolve_revision_spec` -> `resolve_revision_data`.
- `EndpointRow.to_deployment_info` populates revisions via
  `rev_row.to_data()` instead of `to_model_revision_spec()`.
- Read sites collapse: the 35-line spec-unpack in
  `_convert_deployment_info_to_data` becomes a 6-line lookup;
  `api/rest/service/handler._resolve_target_revision_data` reads the
  same fields via `ModelRevisionData` (`model_mount_config.vfolder_id`,
  `extra_vfolder_mounts`, ...).
- Sokovan paths that legitimately need the spec shape — session
  validation in `deployment_controller.update_deployment` and route
  draft assembly in `route/executor` — fetch it explicitly from the
  repository. Add `DeploymentRepository.get_revision_spec(revision_id)`
  alongside the existing `get_current_revision_spec(endpoint_id)`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jopemachine jopemachine marked this pull request as draft May 8, 2026 10:26
@github-actions github-actions Bot added size:L 100~500 LoC and removed size:M 30~100 LoC labels May 8, 2026
jopemachine and others added 3 commits May 8, 2026 19:29
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e-time fields on ModelRevisionData

Spec-suffix types are write-only by convention (matches `KernelSpec` /
`SessionSpec` docstrings). Reading a `ModelRevisionSpec` from DB
violated that, but was needed because `ModelRevisionData` lacked the
fields downstream write paths consumed.

- Add `startup_command`, `bootstrap_script`, `callback_url`,
  `extra_vfolder_mounts`, `preset_values` to `ModelRevisionData`;
  populate them in `Row.to_data()`.
- Remove `Row.to_model_revision_spec()`,
  `DeploymentRepository.get_current_revision_spec` /
  `get_revision_spec`, and the matching `db_source` methods. Spec is
  no longer loadable from DB.
- Rewire write-side callers to start from `ModelRevisionData`:
  - `service._build_creator_from_revision_data` reconstructs
    `ResourceSpec` / `ExecutionSpec` from the data fields.
  - `SessionValidationSpec.from_revision` now takes
    `ModelRevisionData`.
  - `DeploymentSessionDraftBuilder.build` now takes
    `ModelRevisionData`; route executor and controller fetch via
    `repository.get_revision` / `get_current_revision`.
- `RevisionDraft.to_model_revision_spec` (the legitimate
  draft -> spec write transform) is unchanged.

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

`ReplicaSpec` was leaking onto the read path through
`DeploymentInfo.replica_spec`, mirroring the `ModelRevisionSpec`
violation. Add a `ReplicaCountData` dataclass that carries the same
two counts (and `target_replica_count` derivation) and is the
read-side type. `ReplicaSpec` stays on Creator / Draft / Updater for
write flows.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added size:XL 500~ LoC and removed size:L 100~500 LoC labels May 8, 2026
@jopemachine jopemachine changed the title feat(BA-5982): expose revision_number on ModelRevision read path feat(BA-5982): expose revision_number, separate Spec/Data on DeploymentInfo read path May 8, 2026
Comment on lines -900 to -903
override. Fields that do not live on ``ModelRevisionData``
(``startup_command``, ``bootstrap_script``, ``callback_url``) remain
``None`` — the reader pipeline resolves them from preset /
``deployment-config.yaml`` / ``model-definition.yaml`` / user override.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

don't delete existing comment

jopemachine and others added 2 commits May 8, 2026 20:02
…tSessionSpec

`DeploymentNetworkSpec` was the last `Spec` type still leaking onto
the read path via `DeploymentInfo.network` and
`ModelDeploymentData.network_access`. Add `DeploymentNetworkData` for
the read side; `DeploymentNetworkSpec` stays on Creator / Draft.
Also drop `DeploymentSessionSpec` — no callers anywhere.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:docs Documentations comp:common Related to Common component comp:manager Related to Manager component size:XL 500~ LoC

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants