Skip to content

refactor: convert to dependency to live on version not deployment#1084

Merged
adityachoudhari26 merged 3 commits intomainfrom
fix-dep-version-dependecy
Apr 29, 2026
Merged

refactor: convert to dependency to live on version not deployment#1084
adityachoudhari26 merged 3 commits intomainfrom
fix-dep-version-dependecy

Conversation

@adityachoudhari26
Copy link
Copy Markdown
Member

@adityachoudhari26 adityachoudhari26 commented Apr 29, 2026

Summary by CodeRabbit

  • New Features

    • Dependencies can now be managed independently for each deployment version.
    • Added new API endpoints for version-specific dependency configuration.
  • Refactor

    • Transitioned dependency management from deployment-level to deployment-version-level throughout the API and database schema.
  • Tests

    • Added comprehensive test coverage for version-specific dependency evaluation and release policy constraints.

Copilot AI review requested due to automatic review settings April 29, 2026 18:40
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 29, 2026

Warning

Rate limit exceeded

@adityachoudhari26 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 36 minutes and 14 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, 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 have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 41429e9e-97d7-4bcc-9e1a-7341ec8d8785

📥 Commits

Reviewing files that changed from the base of the PR and between ab7ade9 and 02aab7b.

📒 Files selected for processing (10)
  • apps/api/openapi/openapi.json
  • apps/api/openapi/paths/deploymentversions.jsonnet
  • apps/api/src/routes/v1/workspaces/deployment-versions.ts
  • apps/api/src/types/openapi.ts
  • apps/workspace-engine/test/controllers/deployment_version_dependency_test.go
  • packages/db/drizzle/0192_sharp_jackpot.sql
  • packages/db/drizzle/meta/0192_snapshot.json
  • packages/db/drizzle/meta/_journal.json
  • packages/db/src/schema/deployment-version.ts
  • packages/workspace-engine-sdk/src/schema.ts
📝 Walkthrough

Walkthrough

This PR migrates dependency management from deployment-level to deployment-version-level scope. It removes deployment-scoped dependency schemas and endpoints across the OpenAPI spec, database, and route handlers, replacing them with version-scoped equivalents. The database table shifts from deployment_dependency to deployment_version_dependency, and evaluator logic is updated accordingly with comprehensive test coverage.

Changes

Cohort / File(s) Summary
OpenAPI Specification
apps/api/openapi/openapi.json, apps/api/openapi/paths/deployments.jsonnet, apps/api/openapi/paths/deploymentversions.jsonnet, apps/api/openapi/schemas/deployments.jsonnet, apps/workspace-engine/oapi/openapi.json, apps/workspace-engine/oapi/spec/schemas/deployments.jsonnet
Removes DeploymentDependency and UpsertDeploymentDependencyRequest schemas; removes /deployments/{deploymentId}/dependencies endpoints; adds DeploymentVersionDependency and UpsertDeploymentVersionDependencyRequest schemas; adds /deploymentversions/{deploymentVersionId}/dependencies endpoints with GET, PUT, DELETE operations.
API Routes and Type Definitions
apps/api/src/routes/v1/workspaces/deployment-versions.ts, apps/api/src/routes/v1/workspaces/deployments.ts, apps/api/src/types/openapi.ts
Removes deployment-scoped dependency route handlers (166 lines removed); adds version-scoped route handlers with workspace-scoped loader, CEL validation, self-dependency rejection, workspace verification, and 202 responses; updates OpenAPI type definitions to reflect schema and operation changes.
Database Migrations and Schema
packages/db/drizzle/0191_equal_talisman.sql, packages/db/drizzle/meta/_journal.json, packages/db/src/schema/deployment-version.ts, packages/db/src/schema/deployment.ts
Adds new migration defining deployment_version_dependency join table with composite key (deployment_version_id, dependency_deployment_id) and version_selector field; removes deploymentDependency schema export; adds deploymentVersionDependency schema export with cascade delete referential integrity.
Database Query Layer
apps/workspace-engine/pkg/db/queries/deployments.sql, apps/workspace-engine/pkg/db/deployments.sql.go, apps/workspace-engine/pkg/oapi/oapi.gen.go
Renames query from GetDeploymentDependenciesByDeploymentID to GetDeploymentDependenciesByVersionID switching table to deployment_version_dependency; adds new GetDeploymentsWithVersionsDependingOn query to find downstream deployments; removes DeploymentDependency generated Go type.
Release Manager Evaluator
apps/workspace-engine/pkg/workspace/releasemanager/policy/evaluator/deploymentversiondependency/deployment_version_dependency.go, apps/workspace-engine/pkg/workspace/releasemanager/policy/evaluator/deploymentversiondependency/deployment_version_dependency_test.go, apps/workspace-engine/pkg/workspace/releasemanager/policy/evaluator/deploymentversiondependency/getter.go, apps/workspace-engine/pkg/workspace/releasemanager/policy/evaluator/deploymentversiondependency/getter_postgres.go
Updates evaluator scope from ScopeDeployment to ScopeVersion; shifts telemetry and dependency retrieval to use Version.Id instead of Deployment.Id; updates Getters interface and implementation to accept deploymentVersionID parameter; updates test harness to populate Version field in scope.
Test Coverage
apps/workspace-engine/test/controllers/deployment_version_dependency_test.go, apps/workspace-engine/test/controllers/harness/mocks.go
Adds new 369-line integration test covering version-scoped dependency evaluation, including constraint satisfaction, AND semantics across edges, and per-version selector pinning scenarios; updates DesiredReleaseGetter mock to support per-deployment-version dependency lookups via new DeploymentVersionDependencies map.

Sequence Diagram(s)

No sequence diagrams are applicable to this change, as it is primarily a scope migration and refactoring that preserves existing control flows rather than introducing new interactions between components.

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • PR #627: Implements the same shift from deployment-scoped to deployment-version-scoped dependencies, adding versioned dependency APIs, DB tables, evaluator logic, and tests.
  • PR #1083: Directly replaced by this PR—it added the earlier deployment-level dependency artifacts that are now migrated to version scope.
  • PR #1081: Modifies the same dependency-evaluation stack (evaluator, getters, DB queries), with this PR shifting implementation from deployment-scoped to deployment-version-scoped dependencies.

Suggested reviewers

  • jsbroks
  • mleonidas

Poem

🐰 A hop, a skip, a version away,
Dependencies now versioned to stay,
No more per-deployment's scattered ties,
Each version pinned—oh what a prize!
Cascading deletes flow just right,
Migration complete, the future's bright! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: converting dependency management from being scoped at the deployment level to the deployment version level across all affected files and APIs.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.
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 fix-dep-version-dependecy

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
Review rate limit: 0/1 reviews remaining, refill in 36 minutes and 14 seconds.

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

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

Refactors “deployment dependency” edges to be pinned to a specific deployment_version rather than the parent deployment, updating storage, API surface, and the workspace-engine evaluator to reflect per-version semantics.

Changes:

  • Replaces deployment_dependency with deployment_version_dependency (schema + migration) and updates sqlc queries accordingly.
  • Removes deployment-level dependency API endpoints and adds deployment-version dependency endpoints (OpenAPI + Express handlers).
  • Updates workspace-engine dependency evaluator/getters and adds end-to-end controller tests for per-version dependency behavior.

Reviewed changes

Copilot reviewed 22 out of 23 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
packages/db/src/schema/deployment.ts Removes the deployment_dependency table definition from Drizzle schema.
packages/db/src/schema/deployment-version.ts Adds deployment_version_dependency table definition (PK: version + dependency deployment).
packages/db/drizzle/meta/_journal.json Registers the new Drizzle migration tag.
packages/db/drizzle/0191_equal_talisman.sql Creates deployment_version_dependency and drops deployment_dependency.
apps/workspace-engine/test/controllers/harness/mocks.go Updates test harness getter mock to return dependencies keyed by deployment version ID.
apps/workspace-engine/test/controllers/deployment_version_dependency_test.go Adds controller-level tests covering satisfied/unsatisfied/multi-edge/per-version pinning behavior.
apps/workspace-engine/pkg/workspace/releasemanager/policy/evaluator/deploymentversiondependency/getter_postgres.go Changes dependency lookup to use deployment version ID and new sqlc query.
apps/workspace-engine/pkg/workspace/releasemanager/policy/evaluator/deploymentversiondependency/getter.go Updates getter interface contract/docs to be per-version.
apps/workspace-engine/pkg/workspace/releasemanager/policy/evaluator/deploymentversiondependency/deployment_version_dependency_test.go Adjusts evaluator unit test scope to include Version.
apps/workspace-engine/pkg/workspace/releasemanager/policy/evaluator/deploymentversiondependency/deployment_version_dependency.go Evaluator now scopes on Version+Resource and queries dependencies by version ID.
apps/workspace-engine/pkg/oapi/oapi.gen.go Removes the no-longer-used DeploymentDependency model from generated types.
apps/workspace-engine/pkg/db/queries/deployments.sql Renames dependency query to GetDeploymentDependenciesByVersionID; adds downstream lookup query.
apps/workspace-engine/pkg/db/deployments.sql.go Regenerates sqlc code for updated/new queries.
apps/workspace-engine/oapi/spec/schemas/deployments.jsonnet Removes DeploymentDependency schema from workspace-engine OpenAPI spec.
apps/workspace-engine/oapi/openapi.json Removes DeploymentDependency from generated OpenAPI JSON.
apps/api/src/types/openapi.ts Updates generated API typings: remove deployment dependency endpoints; add deployment-version dependency endpoints + schemas.
apps/api/src/routes/v1/workspaces/deployments.ts Removes Express handlers/routes for deployment-level dependency CRUD.
apps/api/src/routes/v1/workspaces/deployment-versions.ts Adds Express handlers/routes for deployment-version dependency list/upsert/delete and workspace scoping helper.
apps/api/openapi/schemas/deployments.jsonnet Renames request/response schemas to UpsertDeploymentVersionDependencyRequest + DeploymentVersionDependency.
apps/api/openapi/paths/deploymentversions.jsonnet Adds OpenAPI paths for listing/upserting/deleting deployment-version dependencies.
apps/api/openapi/paths/deployments.jsonnet Removes OpenAPI paths for deployment-level dependencies.
apps/api/openapi/openapi.json Updates generated OpenAPI JSON to reflect the new endpoints and schemas.
Comments suppressed due to low confidence (1)

apps/workspace-engine/pkg/workspace/releasemanager/policy/evaluator/deploymentversiondependency/deployment_version_dependency.go:57

  • The evaluator now operates on deployment versions (scope uses ScopeVersion and scope.Version.Id), but the user-facing messages still say "Deployment dependency". This makes evaluation output harder to interpret when debugging. Consider updating these strings to explicitly say "Deployment version dependency" (and similarly for the error message prefix) to match the new rule type/semantics.
	edges, err := e.getters.GetDependencies(ctx, scope.Version.Id)
	if err != nil {
		span.RecordError(err)
		return results.NewDeniedResult(
			fmt.Sprintf("Deployment dependency: failed to load dependencies: %v", err),

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

Comment on lines +137 to +163
const listDeploymentVersionDependencies: AsyncTypedHandler<
"/v1/workspaces/{workspaceId}/deploymentversions/{deploymentVersionId}/dependencies",
"get"
> = async (req, res) => {
const { workspaceId, deploymentVersionId } = req.params;

const found = await loadVersionInWorkspace(workspaceId, deploymentVersionId);
if (found == null) throw new ApiError("Deployment version not found", 404);

const rows = await db
.select()
.from(schema.deploymentVersionDependency)
.where(
eq(
schema.deploymentVersionDependency.deploymentVersionId,
deploymentVersionId,
),
)
.orderBy(asc(schema.deploymentVersionDependency.dependencyDeploymentId));

res.status(200).json(rows);
};

const upsertDeploymentVersionDependency: AsyncTypedHandler<
"/v1/workspaces/{workspaceId}/deploymentversions/{deploymentVersionId}/dependencies/{dependencyDeploymentId}",
"put"
> = async (req, res) => {
"version_selector" text DEFAULT 'false' NOT NULL,
CONSTRAINT "deployment_version_dependency_deployment_version_id_dependency_deployment_id_pk" PRIMARY KEY("deployment_version_id","dependency_deployment_id")
);
--> statement-breakpoint
"version_selector" text DEFAULT 'false' NOT NULL,
CONSTRAINT "deployment_version_dependency_deployment_version_id_dependency_deployment_id_pk" PRIMARY KEY("deployment_version_id","dependency_deployment_id")
);
--> statement-breakpoint
versionSelector: text("version_selector").notNull().default("false"),
},
(t) => [
primaryKey({ columns: [t.deploymentVersionId, t.dependencyDeploymentId] }),
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (3)
apps/workspace-engine/pkg/db/deployments.sql.go (2)

81-82: Drop the redundant comment in generated query accessor.

This note mostly repeats what the query/function name already conveys and adds little extra signal.

As per coding guidelines, "Do not add comments that simply restate what the code does."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/workspace-engine/pkg/db/deployments.sql.go` around lines 81 - 82, Remove
the redundant explanatory comment above the generated query accessor in
apps/workspace-engine/pkg/db/deployments.sql.go (the line "Dependencies are
pinned per deployment_version, so this returns the dep edges that belong to a
single deployment_version row."); since the function/query name already conveys
this, delete that comment so the generated accessor remains concise and free of
restating-comments.

127-132: Make downstream deployment ID results deterministic.

SELECT DISTINCT without ORDER BY can return rows in varying order, which can cause unstable downstream iteration/test behavior.

Proposed diff
 const getDeploymentsWithVersionsDependingOn = `-- name: GetDeploymentsWithVersionsDependingOn :many
 SELECT DISTINCT dv.deployment_id
 FROM deployment_version_dependency dvd
 JOIN deployment_version dv ON dv.id = dvd.deployment_version_id
 WHERE dvd.dependency_deployment_id = $1
+ORDER BY dv.deployment_id
 `
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/workspace-engine/pkg/db/deployments.sql.go` around lines 127 - 132, The
query in the GetDeploymentsWithVersionsDependingOn SQL constant uses SELECT
DISTINCT without ORDER BY, causing non-deterministic row ordering; make results
deterministic by adding an ORDER BY clause (e.g., ORDER BY dv.deployment_id) to
the SQL in GetDeploymentsWithVersionsDependingOn so callers receive a stable,
predictable sequence of deployment IDs.
apps/api/openapi/openapi.json (1)

5320-5320: Pick one deployment-version path style before adding more endpoints.

These new routes use /deploymentversions/..., but the spec already exposes /deployment-versions/{deploymentVersionId}/user-approval-records for the same resource family. Shipping both spellings in v1 will make the API harder to navigate and produces awkward generated client surfaces.

Also applies to: 5372-5372

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/openapi/openapi.json` at line 5320, The spec is exposing two
inconsistent path styles for the same resource
("/v1/workspaces/{workspaceId}/deploymentversions/{deploymentVersionId}/dependencies"
vs "/v1/.../deployment-versions/{deploymentVersionId}/user-approval-records");
pick one canonical path style (either "deployment-versions" with hyphen or
"deploymentversions" without) and update all new endpoints to match it. Rename
the new path(s) and their operationId/route names to use the chosen style,
update any schema $ref/parameter references that point to the old path, and
adjust related tests, client generators, and docs to use the single consistent
path (ensure you also change the duplicate occurrence around lines referenced by
the reviewer).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/api/openapi/openapi.json`:
- Around line 5320-5514: You changed the dependency endpoints from
/v1/workspaces/{workspaceId}/deployments/{deploymentId}/dependencies... to
/v1/workspaces/{workspaceId}/deploymentversions/{deploymentVersionId}/dependencies...,
which is a breaking v1 API change; restore a deprecated compatibility shim by
reintroducing the original routes (the /deployments/{deploymentId}/dependencies
paths) and have them delegate to the new handlers (reuse the logic behind
operationId listDeploymentVersionDependencies,
requestDeploymentVersionDependencyDeletion, and
requestDeploymentVersionDependencyUpsert) while marking those old paths
deprecated in the OpenAPI spec and adding a comment/response header indicating
deprecation and migration instructions for one release cycle.

In `@apps/api/src/routes/v1/workspaces/deployment-versions.ts`:
- Around line 210-214: The async calls to enqueueReleaseTargetsForDeployment are
currently fire-and-forget and can cause unhandled rejections; update each call
site that currently calls enqueueReleaseTargetsForDeployment(db, workspaceId,
found.deployment.id) (and the second similar call later in the file) to
explicitly handle the returned promise using the non-blocking pattern: void
enqueueReleaseTargetsForDeployment(...).catch(err =>
logger.warn("enqueueReleaseTargetsForDeployment failed", { err, workspaceId,
deploymentId: found.deployment.id })); use the existing logger variable in this
module so the call remains non-blocking but any rejection is logged.

In `@packages/db/drizzle/0191_equal_talisman.sql`:
- Around line 1-6: The join table deployment_version_dependency lacks workspace
scoping: add a NOT NULL workspace_id uuid column, include workspace_id in the
primary key (so PK becomes (workspace_id, deployment_version_id,
dependency_deployment_id)), and add foreign key constraints that pair
workspace_id with each id (e.g., (workspace_id, deployment_version_id) ->
deployment_version and (workspace_id, dependency_deployment_id) ->
deployment/deployment_version as appropriate) to enforce tenant isolation;
update constraint names to reflect the new PK/FK columns.
- Line 8: The migration currently drops the legacy table via DROP TABLE
"deployment_dependency" which will lose all existing dependency rows; instead,
implement a data migration that preserves existing data by selecting from
deployment_dependency and inserting into deployment_version_dependency (mapping
columns and foreign keys as needed), validate and backfill any new
columns/defaults, and only drop "deployment_dependency" after successful
copy/verification (or keep it as a fallback). Update the migration that contains
DROP TABLE "deployment_dependency" to perform: 1) create any missing
columns/indexes on deployment_version_dependency, 2) insert transformed rows
from deployment_dependency into deployment_version_dependency, 3) verify
counts/constraints, and finally 4) drop the legacy table (or mark it deprecated)
— reference the table names deployment_dependency and
deployment_version_dependency and the DROP TABLE statement to locate where to
apply these changes.

In `@packages/db/src/schema/deployment-version.ts`:
- Around line 69-83: The table definition deploymentVersionDependency is missing
workspaceId which allows cross-tenant edges; add a workspaceId column (uuid
referencing workspace.id with notNull and onDelete cascade), include workspaceId
in the primaryKey alongside deploymentVersionId and dependencyDeploymentId, and
update the migration/schema to create that column and constraint; also update
any insert/update code paths that write to deploymentVersionDependency (and any
relational helpers) to set and validate workspaceId equals the workspaceId of
deploymentVersionId and dependencyDeploymentId so the same-workspace invariant
is enforced at write time.

---

Nitpick comments:
In `@apps/api/openapi/openapi.json`:
- Line 5320: The spec is exposing two inconsistent path styles for the same
resource
("/v1/workspaces/{workspaceId}/deploymentversions/{deploymentVersionId}/dependencies"
vs "/v1/.../deployment-versions/{deploymentVersionId}/user-approval-records");
pick one canonical path style (either "deployment-versions" with hyphen or
"deploymentversions" without) and update all new endpoints to match it. Rename
the new path(s) and their operationId/route names to use the chosen style,
update any schema $ref/parameter references that point to the old path, and
adjust related tests, client generators, and docs to use the single consistent
path (ensure you also change the duplicate occurrence around lines referenced by
the reviewer).

In `@apps/workspace-engine/pkg/db/deployments.sql.go`:
- Around line 81-82: Remove the redundant explanatory comment above the
generated query accessor in apps/workspace-engine/pkg/db/deployments.sql.go (the
line "Dependencies are pinned per deployment_version, so this returns the dep
edges that belong to a single deployment_version row."); since the
function/query name already conveys this, delete that comment so the generated
accessor remains concise and free of restating-comments.
- Around line 127-132: The query in the GetDeploymentsWithVersionsDependingOn
SQL constant uses SELECT DISTINCT without ORDER BY, causing non-deterministic
row ordering; make results deterministic by adding an ORDER BY clause (e.g.,
ORDER BY dv.deployment_id) to the SQL in GetDeploymentsWithVersionsDependingOn
so callers receive a stable, predictable sequence of deployment IDs.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d018bf80-8ad1-431b-b531-0124aa571e7b

📥 Commits

Reviewing files that changed from the base of the PR and between ee91e2d and ab7ade9.

📒 Files selected for processing (23)
  • apps/api/openapi/openapi.json
  • apps/api/openapi/paths/deployments.jsonnet
  • apps/api/openapi/paths/deploymentversions.jsonnet
  • apps/api/openapi/schemas/deployments.jsonnet
  • apps/api/src/routes/v1/workspaces/deployment-versions.ts
  • apps/api/src/routes/v1/workspaces/deployments.ts
  • apps/api/src/types/openapi.ts
  • apps/workspace-engine/oapi/openapi.json
  • apps/workspace-engine/oapi/spec/schemas/deployments.jsonnet
  • apps/workspace-engine/pkg/db/deployments.sql.go
  • apps/workspace-engine/pkg/db/queries/deployments.sql
  • apps/workspace-engine/pkg/oapi/oapi.gen.go
  • apps/workspace-engine/pkg/workspace/releasemanager/policy/evaluator/deploymentversiondependency/deployment_version_dependency.go
  • apps/workspace-engine/pkg/workspace/releasemanager/policy/evaluator/deploymentversiondependency/deployment_version_dependency_test.go
  • apps/workspace-engine/pkg/workspace/releasemanager/policy/evaluator/deploymentversiondependency/getter.go
  • apps/workspace-engine/pkg/workspace/releasemanager/policy/evaluator/deploymentversiondependency/getter_postgres.go
  • apps/workspace-engine/test/controllers/deployment_version_dependency_test.go
  • apps/workspace-engine/test/controllers/harness/mocks.go
  • packages/db/drizzle/0191_equal_talisman.sql
  • packages/db/drizzle/meta/0191_snapshot.json
  • packages/db/drizzle/meta/_journal.json
  • packages/db/src/schema/deployment-version.ts
  • packages/db/src/schema/deployment.ts
💤 Files with no reviewable changes (5)
  • apps/workspace-engine/oapi/openapi.json
  • apps/workspace-engine/oapi/spec/schemas/deployments.jsonnet
  • packages/db/src/schema/deployment.ts
  • apps/workspace-engine/pkg/oapi/oapi.gen.go
  • apps/api/openapi/paths/deployments.jsonnet

Comment thread apps/api/openapi/openapi.json Outdated
Comment on lines +5320 to +5514
"/v1/workspaces/{workspaceId}/deploymentversions/{deploymentVersionId}/dependencies": {
"get": {
"description": "Returns the dependency edges declared by this deployment version.",
"operationId": "listDeploymentVersionDependencies",
"parameters": [
{
"description": "ID of the workspace",
"in": "path",
"name": "workspaceId",
"required": true,
"schema": {
"type": "string"
}
},
{
"description": "ID of the deployment version",
"in": "path",
"name": "deploymentVersionId",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/DeploymentVersionDependency"
},
"type": "array"
}
}
},
"description": "OK response"
},
"404": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
},
"description": "Resource not found"
}
},
"summary": "List deployment-version dependencies"
}
},
"/v1/workspaces/{workspaceId}/deploymentversions/{deploymentVersionId}/dependencies/{dependencyDeploymentId}": {
"delete": {
"operationId": "requestDeploymentVersionDependencyDeletion",
"parameters": [
{
"description": "ID of the workspace",
"in": "path",
"name": "workspaceId",
"required": true,
"schema": {
"type": "string"
}
},
{
"description": "ID of the deployment version",
"in": "path",
"name": "deploymentVersionId",
"required": true,
"schema": {
"type": "string"
}
},
{
"description": "ID of the dependency deployment",
"in": "path",
"name": "dependencyDeploymentId",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"202": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/DeploymentRequestAccepted"
}
}
},
"description": "Accepted response"
},
"400": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
},
"description": "Invalid request"
},
"404": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
},
"description": "Resource not found"
}
},
"summary": "Delete deployment-version dependency"
},
"put": {
"description": "Declare or update a version-selector dependency from this deployment version to another deployment. Identified by the (deploymentVersionId, dependencyDeploymentId) pair.",
"operationId": "requestDeploymentVersionDependencyUpsert",
"parameters": [
{
"description": "ID of the workspace",
"in": "path",
"name": "workspaceId",
"required": true,
"schema": {
"type": "string"
}
},
{
"description": "ID of the deployment version",
"in": "path",
"name": "deploymentVersionId",
"required": true,
"schema": {
"type": "string"
}
},
{
"description": "ID of the dependency deployment",
"in": "path",
"name": "dependencyDeploymentId",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpsertDeploymentVersionDependencyRequest"
}
}
},
"required": true
},
"responses": {
"202": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/DeploymentRequestAccepted"
}
}
},
"description": "Accepted response"
},
"400": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
},
"description": "Invalid request"
},
"404": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
},
"description": "Resource not found"
}
},
"summary": "Upsert deployment-version dependency"
}
},
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.

⚠️ Potential issue | 🟠 Major

This is a breaking v1 API replacement unless the old routes stay alive.

Switching dependency management from /deployments/{deploymentId}/dependencies... to /deploymentversions/{deploymentVersionId}/dependencies... changes both the URL shape and the required identifier. Existing clients that only know a deploymentId cannot migrate transparently and will start failing once the old handlers disappear. Please keep a deprecated compatibility shim for at least one release, or treat this as an explicit versioned API break with migration notes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/openapi/openapi.json` around lines 5320 - 5514, You changed the
dependency endpoints from
/v1/workspaces/{workspaceId}/deployments/{deploymentId}/dependencies... to
/v1/workspaces/{workspaceId}/deploymentversions/{deploymentVersionId}/dependencies...,
which is a breaking v1 API change; restore a deprecated compatibility shim by
reintroducing the original routes (the /deployments/{deploymentId}/dependencies
paths) and have them delegate to the new handlers (reuse the logic behind
operationId listDeploymentVersionDependencies,
requestDeploymentVersionDependencyDeletion, and
requestDeploymentVersionDependencyUpsert) while marking those old paths
deprecated in the OpenAPI spec and adding a comment/response header indicating
deprecation and migration instructions for one release cycle.

Comment on lines +210 to +214
enqueueReleaseTargetsForDeployment(
db,
workspaceId,
found.deployment.id,
);
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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C3 '\benqueueReleaseTargetsForDeployment\s*\(' apps packages -g '*.ts'

Repository: ctrlplanedev/ctrlplane

Length of output: 8531


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find the function definition of enqueueReleaseTargetsForDeployment
rg -n 'export.*enqueueReleaseTargetsForDeployment|function enqueueReleaseTargetsForDeployment' apps packages -g '*.ts' -A 5

Repository: ctrlplanedev/ctrlplane

Length of output: 528


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the specific file around the flagged lines
cat -n apps/api/src/routes/v1/workspaces/deployment-versions.ts | sed -n '205,260p'

Repository: ctrlplanedev/ctrlplane

Length of output: 1979


Handle the enqueue promise explicitly to prevent unhandled rejections.

enqueueReleaseTargetsForDeployment is async and these fire-and-forget calls do not detach or catch rejections. If the promise rejects after the 202 response is sent, it becomes an unhandled rejection. Use the pattern void enqueueReleaseTargetsForDeployment(...).catch(err => logger.warn(...)) to maintain non-blocking behavior while preventing unhandled rejections.

This applies to lines 210–214 and 252–256 in this file.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/routes/v1/workspaces/deployment-versions.ts` around lines 210 -
214, The async calls to enqueueReleaseTargetsForDeployment are currently
fire-and-forget and can cause unhandled rejections; update each call site that
currently calls enqueueReleaseTargetsForDeployment(db, workspaceId,
found.deployment.id) (and the second similar call later in the file) to
explicitly handle the returned promise using the non-blocking pattern: void
enqueueReleaseTargetsForDeployment(...).catch(err =>
logger.warn("enqueueReleaseTargetsForDeployment failed", { err, workspaceId,
deploymentId: found.deployment.id })); use the existing logger variable in this
module so the call remains non-blocking but any rejection is logged.

Comment on lines +1 to +6
CREATE TABLE "deployment_version_dependency" (
"deployment_version_id" uuid NOT NULL,
"dependency_deployment_id" uuid NOT NULL,
"version_selector" text DEFAULT 'false' NOT NULL,
CONSTRAINT "deployment_version_dependency_deployment_version_id_dependency_deployment_id_pk" PRIMARY KEY("deployment_version_id","dependency_deployment_id")
);
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.

⚠️ Potential issue | 🟠 Major

Add tenant scoping to this join table.

deployment_version_dependency is missing workspace_id, so the database can accept an edge from a version in workspace A to a dependency deployment in workspace B as long as both UUIDs exist. That weakens multi-tenant isolation at the schema level and forces every caller to infer tenancy through joins. As per coding guidelines: All database tables include workspaceId for multi-tenant isolation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/db/drizzle/0191_equal_talisman.sql` around lines 1 - 6, The join
table deployment_version_dependency lacks workspace scoping: add a NOT NULL
workspace_id uuid column, include workspace_id in the primary key (so PK becomes
(workspace_id, deployment_version_id, dependency_deployment_id)), and add
foreign key constraints that pair workspace_id with each id (e.g.,
(workspace_id, deployment_version_id) -> deployment_version and (workspace_id,
dependency_deployment_id) -> deployment/deployment_version as appropriate) to
enforce tenant isolation; update constraint names to reflect the new PK/FK
columns.

CONSTRAINT "deployment_version_dependency_deployment_version_id_dependency_deployment_id_pk" PRIMARY KEY("deployment_version_id","dependency_deployment_id")
);
--> statement-breakpoint
DROP TABLE "deployment_dependency" CASCADE;--> statement-breakpoint
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.

⚠️ Potential issue | 🔴 Critical

Don't drop the legacy dependency table without a data migration.

Line 8 deletes every existing deployment_dependency row, and the refactored evaluator now reads only deployment_version_dependency. On upgrade, all previously configured dependencies disappear until users recreate them manually.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/db/drizzle/0191_equal_talisman.sql` at line 8, The migration
currently drops the legacy table via DROP TABLE "deployment_dependency" which
will lose all existing dependency rows; instead, implement a data migration that
preserves existing data by selecting from deployment_dependency and inserting
into deployment_version_dependency (mapping columns and foreign keys as needed),
validate and backfill any new columns/defaults, and only drop
"deployment_dependency" after successful copy/verification (or keep it as a
fallback). Update the migration that contains DROP TABLE "deployment_dependency"
to perform: 1) create any missing columns/indexes on
deployment_version_dependency, 2) insert transformed rows from
deployment_dependency into deployment_version_dependency, 3) verify
counts/constraints, and finally 4) drop the legacy table (or mark it deprecated)
— reference the table names deployment_dependency and
deployment_version_dependency and the DROP TABLE statement to locate where to
apply these changes.

Comment on lines +69 to +83
export const deploymentVersionDependency = pgTable(
"deployment_version_dependency",
{
deploymentVersionId: uuid("deployment_version_id")
.references(() => deploymentVersion.id, { onDelete: "cascade" })
.notNull(),
dependencyDeploymentId: uuid("dependency_deployment_id")
.references(() => deployment.id, { onDelete: "cascade" })
.notNull(),
versionSelector: text("version_selector").notNull().default("false"),
},
(t) => [
primaryKey({ columns: [t.deploymentVersionId, t.dependencyDeploymentId] }),
],
);
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.

⚠️ Potential issue | 🟠 Major

This schema still needs workspaceId.

Without tenant scoping on deploymentVersionDependency, Drizzle can persist cross-workspace dependency edges because deploymentVersionId and dependencyDeploymentId are each valid on their own. Please carry workspaceId on the row and enforce the same invariant in the migration/write path. As per coding guidelines: All database tables include workspaceId for multi-tenant isolation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/db/src/schema/deployment-version.ts` around lines 69 - 83, The table
definition deploymentVersionDependency is missing workspaceId which allows
cross-tenant edges; add a workspaceId column (uuid referencing workspace.id with
notNull and onDelete cascade), include workspaceId in the primaryKey alongside
deploymentVersionId and dependencyDeploymentId, and update the migration/schema
to create that column and constraint; also update any insert/update code paths
that write to deploymentVersionDependency (and any relational helpers) to set
and validate workspaceId equals the workspaceId of deploymentVersionId and
dependencyDeploymentId so the same-workspace invariant is enforced at write
time.

@adityachoudhari26
Copy link
Copy Markdown
Member Author

Review response

Worked through all 9 inline comments. Summary of what's fixed vs. what we're skipping with rationale.

✅ Fixed

# Comment Fix
1 Path hyphen mismatch (Copilot, deployment-versions.ts:163) — OAPI used /deploymentversions/... but router mount is /deployment-versions, so requests would 404 from the validator. Updated apps/api/openapi/paths/deploymentversions.jsonnet and the AsyncTypedHandler generics in deployment-versions.ts to use the hyphenated path.
3, 4 Missing index on dependency_deployment_id (Copilot, schema + migration) — GetDeploymentsWithVersionsDependingOn filters by this column, which the PK can't satisfy (PK leftmost is deployment_version_id). Added index("deployment_version_dependency_target_idx").on(t.dependencyDeploymentId) in packages/db/src/schema/deployment-version.ts. Drizzle migration regen will pick it up.
6 Fire-and-forget enqueueReleaseTargetsForDeployment could leak unhandled rejections (CodeRabbit, deployment-versions.ts:214). Both call sites now use void enqueueReleaseTargetsForDeployment(...).catch(console.error). Matches the existing pattern in relationship-rules.ts:50.

❌ Skipping (with reasons)

# Comment Why skip
2, 8 Drop deployment_dependency without data migration (Copilot + CodeRabbit) The original deployment_dependency table was added in PR #1083, which landed only days ago and was never wired up to a user-facing API in the released form (the API endpoints we're now removing were also added in #1083). No production rows exist. Adding a "copy old → new" migration step would be busywork against an empty table. Calling out as a deliberate breaking change in the PR description rather than synthesizing a migration.
5 Breaking v1 API change without deprecation shim (CodeRabbit, openapi.json:5514) Same context as #2/#8 — the /deployments/{deploymentId}/dependencies routes were introduced in #1083 and never advertised/released externally. Any client calling them would have done so within the last week against an internal-only feature. Maintaining a deprecated shim for an unreleased endpoint creates dead code.
7, 9 Missing workspaceId on deployment_version_dependency (CodeRabbit) This deviates from the codebase convention rather than enforces it. None of the comparable edge tables in packages/db/src/schema/release.ts carry their own workspaceIdrelease_target_desired_release, release, release_job, release_variable all rely on the FK chain to deployment (which carries workspaceId) for tenant boundaries. Adding it only here would create an inconsistent pattern. The API layer (deployment-versions.ts's loadVersionInWorkspace) joins through deployment.workspace_id to enforce isolation at write time. If we want workspace-stamping on every edge table, that should be a codebase-wide decision in a separate change.

Action needed before merge

  • Rerun the API app's openapi codegen to pick up the hyphenated paths in deploymentversions.jsonnet.
  • Rerun drizzle to regenerate the migration with the new index.

@adityachoudhari26 adityachoudhari26 merged commit 8967385 into main Apr 29, 2026
11 checks passed
@adityachoudhari26 adityachoudhari26 deleted the fix-dep-version-dependecy branch April 29, 2026 19:04
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.

2 participants