Skip to content

Conversation

@adityachoudhari26
Copy link
Contributor

@adityachoudhari26 adityachoudhari26 commented Jun 18, 2025

Summary by CodeRabbit

  • New Features

    • Added the ability to approve or reject deployment versions for specific environments, with new API endpoints and UI support for selecting multiple environments during approval or rejection.
    • Approval and rejection records now include environment context, allowing for more granular release management.
  • Improvements

    • Enhanced the approval dialog to support multi-environment selection with an improved user interface.
    • Approval policies and rollout logic are now environment-aware, providing more precise control over deployment approvals.
    • Extended deployment and system components to include system identifiers for better environment context in UI.
  • Bug Fixes

    • Prevented duplicate approvals for the same deployment version and environment.
  • API Changes

    • Introduced new endpoints for environment-scoped approvals and rejections.
    • Updated API schemas to include environment identifiers in approval records.
  • Tests

    • Added end-to-end tests to verify environment-specific approval and rejection behavior.
  • Database

    • Updated database schema to support environment-scoped approval records and enforce uniqueness per deployment version, environment, and user.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jun 18, 2025

Walkthrough

This change introduces environment-scoped approval and rejection for deployment versions across the API, database schema, rule engine, and frontend. New endpoints allow approvals and rejections per environment. Database tables and rule logic are updated to store and evaluate approvals with environment context. The frontend is refactored to support multi-environment approval workflows.

Changes

File(s) Change Summary
apps/event-worker/src/workers/compute-systems-release-targets.ts Reformatted logger child object literal to a single line (style only).
apps/webservice/.../ApprovalRequiredCell.tsx, .../DeploymentVersionEnvironmentCell.tsx, .../DeploymentEnvironmentCell.tsx Updated system prop types to include both id and slug.
apps/webservice/.../ApprovalDialog.tsx Refactored for multi-environment selection and approval; added EnvironmentCombobox and ApprovalDialogControl; fetches environments by systemId.
apps/webservice/.../approve/environment/[environmentId]/openapi.ts, .../route.ts Added OpenAPI spec and route for per-environment approval endpoint.
apps/webservice/.../approve/openapi.ts Added required environmentId to ApprovalRecord schema.
apps/webservice/.../approve/route.ts Modified to create approval records for all environments of a deployment's system.
apps/webservice/.../reject/environment/[environmentId]/openapi.ts, .../route.ts Added OpenAPI spec and route for per-environment rejection endpoint.
apps/webservice/.../reject/route.ts Modified to create rejection records for all environments of a deployment's system.
e2e/api/schema.ts, openapi.v1.json Added new endpoints and updated ApprovalRecord schema with environmentId.
e2e/tests/api/policies/approval-policy.spec.ts, .spec.yaml Added tests for per-environment approval and duplicate approval prevention; broadened policy environment selector.
packages/api/src/router/deployment-version.ts Updated addApprovalRecord mutation to handle multiple environmentIds and batch insert.
packages/db/drizzle/0117_little_the_santerians.sql, meta/_journal.json Schema migration: added environment_id columns, foreign keys, and new unique indexes to approval record tables; journal updated.
packages/db/src/schema/rules/approval-base.ts, approval-any.ts Added environmentId to base approval schema; updated unique index to include environmentId.
packages/rule-engine/src/manager/version-manager-rules.ts Modified getRules to asynchronously fetch environment-aware approval rules.
packages/rule-engine/src/manager/version-manager-rules/environment-version-rollout.ts Made rollout logic environment-aware; fetches and uses environmentId.
packages/rule-engine/src/manager/version-manager-rules/version-approval.ts Refactored approval rule constructors to accept environmentId; made getVersionApprovalRules async and environment-scoped.
packages/rule-engine/src/rules/version-approval-rule.ts Added environment-scoped approval record getters; updated filtering logic.
apps/webservice/.../checks/_components/flow-diagram/FlowDiagram.tsx, .../checks/Approval.tsx, .../nodes/EnvironmentNode.tsx Added systemId prop to flow diagram and related check/node components to propagate environment context.
apps/webservice/.../checks/page.tsx Passed full system object as prop to FlowDiagram.
apps/webservice/.../DeploymentPageContent.tsx, .../TableDeployments.tsx Updated system prop passed to child components to include both id and slug.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Frontend
    participant API
    participant DB
    participant RuleEngine

    User->>Frontend: Selects environments and submits approval
    Frontend->>API: POST /approve/environment/{envId}
    API->>DB: Insert approval record (deploymentVersionId, environmentId, userId)
    DB-->>API: Confirmation
    API->>RuleEngine: Evaluate release targets for deployment/environment
    RuleEngine->>DB: Fetch approval records for environment
    RuleEngine-->>API: Evaluation result
    API-->>Frontend: Return approval record
    Frontend-->>User: Show approval status
Loading

Possibly related PRs

Suggested reviewers

  • jsbroks

Poem

In every burrow, systems grow,
Approvals now by where they go!
Environments picked with care,
Each version finds its lair.
With rules and records side by side,
The code hops on, with ears of pride!
🐇✨


📜 Recent review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 347c4d3 and 8320cb1.

📒 Files selected for processing (5)
  • apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/approve/environment/[environmentId]/openapi.ts (1 hunks)
  • apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/approve/openapi.ts (3 hunks)
  • apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/reject/environment/[environmentId]/openapi.ts (1 hunks)
  • apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/reject/openapi.ts (1 hunks)
  • openapi.v1.json (6 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/approve/environment/[environmentId]/openapi.ts
  • apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/reject/environment/[environmentId]/openapi.ts
  • apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/approve/openapi.ts
  • openapi.v1.json
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.{ts,tsx}`: **Note on Error Handling:** Avoid strict enforcement of try/catch blocks. Code may use early returns, Promise chains (.then().catch()), or other patterns for error...

**/*.{ts,tsx}: Note on Error Handling:
Avoid strict enforcement of try/catch blocks. Code may use early returns, Promise chains (.then().catch()), or other patterns for error handling. These are acceptable as long as they maintain clarity and predictability.

  • apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/reject/openapi.ts
⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: build (linux/amd64)
  • GitHub Check: build (linux/amd64)
  • GitHub Check: build (linux/amd64)
  • GitHub Check: Typecheck
  • GitHub Check: Lint
🔇 Additional comments (2)
apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/reject/openapi.ts (2)

20-32: LGTM - Request body structure is well-defined.

The optional request body schema is properly structured with the reason field correctly defined as an optional string property. The OpenAPI specification follows v3 conventions correctly.


21-21: ```bash
#!/bin/bash
set -e

echo "1) Searching for POST handlers in reject directories:"
rg -n "export async function POST" --glob "apps/webservice/src/app/api/v1/deployment-versions/**/reject/*.ts" || echo " → No POST handlers found"

echo
echo "2) Checking for JSON body parsing (await req.json):"
rg -n "await req.json" --glob "apps/webservice/src/app/api/v1/deployment-versions/**/reject/*.ts" || echo " → No JSON parsing found"


</details>

</blockquote></details>

</details>
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

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.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=ctrlplanedev/ctrlplane&utm_content=593):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Explain this complex logic.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai explain this code block.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and explain its main purpose.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->

Copy link
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: 6

🔭 Outside diff range comments (1)
packages/db/src/schema/rules/approval-base.ts (1)

59-64: Add environmentId to validation schema.

The baseApprovalRecordValidationFields should include validation for the new environmentId field to maintain consistency with the database schema.

 export const baseApprovalRecordValidationFields = {
   deploymentVersionId: z.string().uuid(),
+  environmentId: z.string().uuid(),
   userId: z.string().uuid(),
   status: z.enum(approvalStatus.enumValues),
   reason: z.string().optional(),
 };
🧹 Nitpick comments (6)
apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/approve/route.ts (1)

38-43: Verify nested query performance

The nested query with deep relations (deployment -> system -> environments) could impact performance for systems with many environments. Consider adding database indexes on the foreign key relationships if not already present.

packages/rule-engine/src/rules/version-approval-rule.ts (1)

41-42: Remove debug logging before production.

Console.log statements should be removed or replaced with proper logging infrastructure before merging to production.

-    console.log("num approval records", approvalRecords.length);
-    console.log("min required", this.options.minApprovals);
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/_components/deployment-version/ApprovalDialog.tsx (1)

119-121: Consider validation for empty environment selection.

The component initializes with the passed environmentId, but users can remove all environments. Consider adding validation to prevent submitting with no environments selected.

 const ApprovalDialogControl: React.FC<{
   versionId: string;
   environments: schema.Environment[];
   environmentId: string;
   onSubmit: () => void;
   onCancel: () => void;
 }> = ({ versionId, environments, environmentId, onSubmit, onCancel }) => {
   const [environmentIds, setEnvironmentIds] = useState<string[]>([
     environmentId,
   ]);
+  
+  const hasSelectedEnvironments = environmentIds.length > 0;

Then update the approve/reject buttons:

           <Button
             variant="outline"
             onClick={() => handleSubmit(SCHEMA.ApprovalStatus.Rejected)}
-            disabled={addRecord.isPending}
+            disabled={addRecord.isPending || !hasSelectedEnvironments}
           >
             Reject
           </Button>
           <Button
             onClick={() => handleSubmit(SCHEMA.ApprovalStatus.Approved)}
-            disabled={addRecord.isPending}
+            disabled={addRecord.isPending || !hasSelectedEnvironments}
           >
             Approve
           </Button>
openapi.v1.json (3)

232-244: Add description for environmentId parameter
The environmentId path parameter lacks a description field. Please add a descriptive text to aid client code generation and API documentation.

{
  "name": "environmentId",
  "in": "path",
  "required": true,
  "schema": {
    "type": "string",
    "format": "uuid"
  },
+ "description": "UUID of the target environment"
}

639-648: Add description for environmentId parameter
Include a description for the environmentId path parameter in the rejection endpoint to improve the OpenAPI docs.

{
  "name": "environmentId",
  "in": "path",
  "required": true,
  "schema": {
    "type": "string",
    "format": "uuid"
  },
+ "description": "UUID of the target environment"
}

5467-5471: Add description for environmentId in ApprovalRecord
The newly added environmentId property in the ApprovalRecord schema lacks a description. Please document its purpose.

"environmentId": {
  "type": "string",
  "format": "uuid",
+ "description": "UUID of the environment associated with this approval"
},
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9b3bade and 5d6741d.

📒 Files selected for processing (26)
  • apps/event-worker/src/workers/compute-systems-release-targets.ts (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/deployments/[deploymentSlug]/(sidebar)/_components/release-cell/ApprovalRequiredCell.tsx (2 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/deployments/[deploymentSlug]/(sidebar)/_components/release-cell/DeploymentVersionEnvironmentCell.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/_components/deployment-version/ApprovalDialog.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/_components/deployments/environment-cell/DeploymentEnvironmentCell.tsx (2 hunks)
  • apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/approve/environment/[environmentId]/openapi.ts (1 hunks)
  • apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/approve/environment/[environmentId]/route.ts (1 hunks)
  • apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/approve/openapi.ts (2 hunks)
  • apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/approve/route.ts (2 hunks)
  • apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/reject/environment/[environmentId]/openapi.ts (1 hunks)
  • apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/reject/environment/[environmentId]/route.ts (1 hunks)
  • apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/reject/route.ts (2 hunks)
  • e2e/api/schema.ts (5 hunks)
  • e2e/tests/api/policies/approval-policy.spec.ts (1 hunks)
  • e2e/tests/api/policies/approval-policy.spec.yaml (2 hunks)
  • openapi.v1.json (4 hunks)
  • packages/api/src/router/deployment-version.ts (3 hunks)
  • packages/api/src/router/policy/evaluate.ts (1 hunks)
  • packages/db/drizzle/0117_little_the_santerians.sql (1 hunks)
  • packages/db/drizzle/meta/_journal.json (1 hunks)
  • packages/db/src/schema/rules/approval-any.ts (1 hunks)
  • packages/db/src/schema/rules/approval-base.ts (2 hunks)
  • packages/rule-engine/src/manager/version-manager-rules.ts (1 hunks)
  • packages/rule-engine/src/manager/version-manager-rules/environment-version-rollout.ts (4 hunks)
  • packages/rule-engine/src/manager/version-manager-rules/version-approval.ts (1 hunks)
  • packages/rule-engine/src/rules/version-approval-rule.ts (5 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.{ts,tsx}`: **Note on Error Handling:** Avoid strict enforcement of try/catch blocks. Code may use early returns, Promise chains (.then().catch()), or other patterns for error...

**/*.{ts,tsx}: Note on Error Handling:
Avoid strict enforcement of try/catch blocks. Code may use early returns, Promise chains (.then().catch()), or other patterns for error handling. These are acceptable as long as they maintain clarity and predictability.

  • apps/event-worker/src/workers/compute-systems-release-targets.ts
  • apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/reject/route.ts
  • apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/approve/openapi.ts
  • apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/approve/route.ts
  • packages/rule-engine/src/manager/version-manager-rules.ts
  • packages/api/src/router/policy/evaluate.ts
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/_components/deployments/environment-cell/DeploymentEnvironmentCell.tsx
  • packages/db/src/schema/rules/approval-any.ts
  • apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/reject/environment/[environmentId]/route.ts
  • apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/approve/environment/[environmentId]/openapi.ts
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/deployments/[deploymentSlug]/(sidebar)/_components/release-cell/ApprovalRequiredCell.tsx
  • e2e/tests/api/policies/approval-policy.spec.ts
  • apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/reject/environment/[environmentId]/openapi.ts
  • packages/rule-engine/src/rules/version-approval-rule.ts
  • apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/approve/environment/[environmentId]/route.ts
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/_components/deployment-version/ApprovalDialog.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/deployments/[deploymentSlug]/(sidebar)/_components/release-cell/DeploymentVersionEnvironmentCell.tsx
  • packages/db/src/schema/rules/approval-base.ts
  • packages/api/src/router/deployment-version.ts
  • packages/rule-engine/src/manager/version-manager-rules/environment-version-rollout.ts
  • packages/rule-engine/src/manager/version-manager-rules/version-approval.ts
  • e2e/api/schema.ts
🧬 Code Graph Analysis (7)
apps/event-worker/src/workers/compute-systems-release-targets.ts (1)
packages/logger/src/index.ts (1)
  • logger (48-48)
apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/reject/route.ts (2)
packages/db/src/schema/deployment-version.ts (1)
  • deploymentVersion (110-137)
packages/db/src/schema/environment.ts (1)
  • environment (59-84)
packages/api/src/router/policy/evaluate.ts (1)
packages/rule-engine/src/manager/version-manager-rules/version-approval.ts (3)
  • versionUserApprovalRule (41-53)
  • versionRoleApprovalRule (27-39)
  • versionAnyApprovalRule (14-25)
apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/reject/environment/[environmentId]/openapi.ts (2)
apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/approve/environment/[environmentId]/openapi.ts (1)
  • openapi (3-86)
apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/approve/openapi.ts (1)
  • openapi (3-112)
packages/rule-engine/src/rules/version-approval-rule.ts (1)
packages/db/src/client.ts (1)
  • db (15-15)
packages/api/src/router/deployment-version.ts (1)
packages/events/src/dispatch-jobs.ts (1)
  • dispatchQueueJob (154-158)
packages/rule-engine/src/manager/version-manager-rules/environment-version-rollout.ts (5)
packages/rule-engine/src/types.ts (1)
  • Policy (66-74)
packages/rule-engine/src/manager/version-rule-engine.ts (1)
  • Version (11-16)
packages/rule-engine/src/manager/version-manager-rules/version-approval.ts (1)
  • getVersionApprovalRules (55-70)
packages/db/src/common.ts (1)
  • takeFirst (9-13)
packages/db/src/schema/release.ts (1)
  • releaseTarget (21-43)
⏰ Context from checks skipped due to timeout of 90000ms (6)
  • GitHub Check: build (linux/amd64)
  • GitHub Check: build (linux/amd64)
  • GitHub Check: build (linux/amd64)
  • GitHub Check: Typecheck
  • GitHub Check: build (linux/amd64)
  • GitHub Check: Lint
🔇 Additional comments (54)
apps/event-worker/src/workers/compute-systems-release-targets.ts (1)

14-14: Stylistic simplification is safe and clear.

Inlining the logger.child object literal doesn’t alter behavior and improves conciseness. No functional impact detected.

packages/db/drizzle/meta/_journal.json (1)

824-830: LGTM! Migration journal entry is correctly formatted.

The new journal entry follows the standard format and properly tracks the schema changes for environment-scoped approvals.

apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/approve/openapi.ts (1)

13-13: LGTM! Environment ID correctly added to schema.

The environmentId field is properly defined with UUID format and marked as required, which aligns with the environment-scoped approval functionality.

Also applies to: 24-24

apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/reject/route.ts (2)

59-63: LGTM! Bulk insert with conflict handling is well implemented.

The bulk insert operation with onConflictDoNothing() properly handles potential duplicate rejection attempts and returns the inserted records.


47-57: ```shell
#!/bin/bash

Locate the approve route for deployment-version approvals and print its implementation

approve_route=$(find apps/webservice/src/app/api/v1/deployment-versions -type f -name route.ts | grep approve)
echo "Approve route file: $approve_route"
echo "----- Snippet (lines 1-200) -----"
sed -n '1,200p' "$approve_route"


</details>
<details>
<summary>apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/_components/deployments/environment-cell/DeploymentEnvironmentCell.tsx (2)</summary>

`10-10`: **LGTM! Import path improvement.**

Using absolute import path improves maintainability and reduces issues with relative path changes.

---

`27-27`: **LGTM! System prop type expansion aligns with feature requirements.**

Adding `id` to the system prop type provides the necessary context for environment-scoped operations while maintaining backward compatibility with the existing `slug` property.

</details>
<details>
<summary>apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/deployments/[deploymentSlug]/(sidebar)/_components/release-cell/ApprovalRequiredCell.tsx (2)</summary>

`77-77`: **LGTM! System prop type expansion supports environment-scoped functionality.**

Adding `id` to the system prop type provides the necessary context for the ApprovalDialog to fetch environments by system ID.

---

`146-146`: **LGTM! System ID correctly passed to ApprovalDialog.**

Passing `system.id` enables the ApprovalDialog to fetch environments for multi-environment approval workflows.

</details>
<details>
<summary>packages/rule-engine/src/manager/version-manager-rules.ts (2)</summary>

`73-76`: **LGTM - Good async handling pattern**

The change to call `getVersionApprovalRules` asynchronously and store the result in a variable before spreading is a good practice for async operations. This aligns with the environment-scoped approval changes that require database queries to fetch environment context.

---

`78-81`: **Minor formatting improvement**

The condensed formatting of `ReleaseTargetLockRule` and the spread of `versionApprovalRules` maintains consistency with the async pattern changes.

</details>
<details>
<summary>apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/deployments/[deploymentSlug]/(sidebar)/_components/release-cell/DeploymentVersionEnvironmentCell.tsx (1)</summary>

`117-117`: ```shell
#!/bin/bash
set -e

# Inspect system prop assignment in DeploymentPageContent.tsx
rg -C3 -F -n 'system={{' 'apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/deployments/[deploymentSlug]/(sidebar)/DeploymentPageContent.tsx'

# Inspect system data fetch usage in DeploymentPageContent.tsx
rg -C3 -n 'api.system' 'apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/deployments/[deploymentSlug]/(sidebar)/DeploymentPageContent.tsx'

# Inspect the props type for DeploymentEnvironmentCell
rg -C3 -n 'type DeploymentEnvironmentCellProps' 'apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/_components/deployments/environment-cell/DeploymentEnvironmentCell.tsx'

# Inspect 'system:' declaration inside DeploymentEnvironmentCellProps
rg -C3 -n 'system:' 'apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/_components/deployments/environment-cell/DeploymentEnvironmentCell.tsx'
apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/approve/route.ts (2)

68-70: Verify conflict handling with environment-scoped records

The onConflictDoNothing() behavior should now work correctly with the updated unique index that includes environmentId. Ensure this prevents duplicate approvals per environment while allowing the same user to approve for different environments.


57-64: Potential scaling concern with bulk environment approvals

Creating approval records for all environments in a system could lead to excessive records for systems with many environments. Consider whether this bulk approval approach aligns with the intended user workflow.

#!/bin/bash
# Description: Check if there are limits on number of environments per system
# Expected: Find any validation or limits on environment count

rg -A 5 -B 5 "environments.*length|environments.*count|environment.*limit" --type ts --type tsx
e2e/tests/api/policies/approval-policy.spec.yaml (2)

15-22: Good test setup for multi-environment scenarios

Adding a second environment with the same configuration enables testing environment-scoped approval logic. The naming pattern supports the policy selector changes.


42-43: Policy selector change appropriately broadens scope

Changing from "equals" to "contains" allows the policy to match both environments ({{ prefix }} and {{ prefix }}-prod), which is necessary for testing multi-environment approval workflows.

packages/db/src/schema/rules/approval-any.ts (1)

33-39: Correct implementation of environment-scoped unique constraint

The updated unique index properly enforces the constraint that a user can only approve a deployment version once per environment. The index name "unique_deployment_version_id_environment_id_user_id" is descriptive and follows good naming conventions.

packages/api/src/router/policy/evaluate.ts (1)

125-126: LGTM! Environment context properly integrated into approval rules.

The changes correctly pass the environmentId parameter to the approval rule functions, enabling environment-scoped policy evaluation. The parameter order and types align with the updated function signatures in the rule engine.

Also applies to: 133-134, 141-142

packages/db/src/schema/rules/approval-base.ts (2)

6-7: LGTM! Proper imports added for foreign key references.

The imports for deploymentVersion and environment are correctly added to support the new foreign key relationships.


20-26: LGTM! Well-designed foreign key relationships with proper cascade behavior.

The schema changes properly establish:

  • Explicit foreign key reference for deploymentVersionId with cascade delete
  • New required environmentId field with cascade delete behavior

This ensures referential integrity and automatic cleanup when parent records are deleted.

apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/approve/environment/[environmentId]/openapi.ts (1)

1-86: LGTM! Well-structured OpenAPI specification for environment-scoped approval.

The specification properly defines:

  • Required UUID path parameters for both deployment version and environment
  • Appropriate request body schema with mandatory reason field
  • Comprehensive response definitions covering success and error scenarios
  • Correct reference to the shared ApprovalRecord schema component

The API design follows RESTful patterns and maintains consistency with existing endpoints.

apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/reject/environment/[environmentId]/route.ts (1)

35-98: LGTM! Well-implemented environment-scoped rejection endpoint.

The implementation demonstrates excellent practices:

  • Proper middleware stack with authentication, authorization, and body parsing
  • Comprehensive validation of deployment version and environment existence
  • Sound business logic requiring existing approval before rejection
  • Appropriate use of onConflictDoNothing() for idempotent operations
  • Clear error responses with meaningful messages

The authorization check using DeploymentVersionUpdate permission is appropriate for this operation.

apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/reject/environment/[environmentId]/openapi.ts (1)

1-86: LGTM! OpenAPI specification follows consistent patterns.

The specification properly defines the environment-scoped rejection endpoint with:

  • Appropriate path parameters and validation
  • Correct HTTP status codes and error responses
  • Proper reference to the shared ApprovalRecord schema

The structure maintains consistency with the corresponding approval endpoint.

apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/approve/environment/[environmentId]/route.ts (6)

15-18: LGTM! Well-structured request validation.

The body schema correctly defines optional reason and approvedAt fields with proper validation using Zod datetime parsing.


20-35: Good separation of concerns with the helper function.

The getExistingRecord function properly encapsulates the duplicate approval check logic and correctly queries by all three required fields: deploymentVersionId, environmentId, and userId.


54-72: LGTM! Proper entity validation with clear error responses.

Both deployment version and environment existence checks follow consistent patterns with appropriate 404 responses.


90-102: Good use of conflict handling in database insert.

The .onConflictDoNothing() provides additional safety against race conditions, complementing the explicit duplicate check above.


103-113: LGTM! Proper job dispatch for rule evaluation.

The code correctly finds all affected release targets for the specific deployment and environment combination, then triggers rule evaluation.


41-46: Verify authorization covers environment access.

The current authorization only checks DeploymentVersionUpdate permission on the deployment version. Consider whether users should also have specific permissions on the target environment before approving.

#!/bin/bash
# Check if environment-specific permissions are enforced elsewhere
ast-grep --pattern $'can.perform($_).on({
  type: "environment",
  $$$
})'
packages/rule-engine/src/rules/version-approval-rule.ts (3)

71-87: LGTM! Well-structured environment-scoped getter function.

The getAnyApprovalRecordsGetter properly combines database conditions using and() and correctly maps the result to include versionId.


104-120: LGTM! Consistent pattern with role approval records.

The getRoleApprovalRecordsGetter follows the same pattern as the any approval getter, maintaining consistency across approval types.


137-153: LGTM! Complete coverage for user approval records.

The getUserApprovalRecordsGetter completes the set of environment-scoped getters, following the established pattern.

e2e/tests/api/policies/approval-policy.spec.ts (2)

283-377: Excellent test coverage for environment-scoped approvals.

This test properly verifies that:

  1. Approving a version for environment1 creates a release for that environment
  2. The same approval does NOT create a release for environment2
  3. The API correctly handles environment-specific approval endpoints

The test structure is clear and assertions are appropriate.


379-511: LGTM! Thorough test for duplicate approval prevention.

This test effectively validates:

  1. First approval succeeds (200 status)
  2. Duplicate approval is rejected (409 status)
  3. No release is created when minimum approvals aren't met (policy requires 2 approvals)

The test setup with a new policy and environment is well-structured.

packages/rule-engine/src/manager/version-manager-rules/environment-version-rollout.ts (4)

22-28: Good refactor to support environment-scoped approval rules.

The function now properly accepts environmentId and awaits the async getVersionApprovalRules call, making the approval rules environment-aware.


129-135: LGTM! Clean helper function for environment ID retrieval.

The getEnvironmentId function follows the established pattern using takeFirst to ensure a unique result and properly extracts the environment ID from the release target.


147-148: LGTM! Proper integration of environment context.

The code correctly fetches the environment ID and passes it to the rollout start time getter, maintaining the environment-scoped behavior.


186-190: LGTM! Consistent use of environment context in rollout info.

The function properly extracts environmentId from the release target and passes it to the rollout start time getter, maintaining consistency with the other changes.

packages/db/drizzle/0117_little_the_santerians.sql (4)

5-7: LGTM! Consistent schema changes across all approval tables.

All three approval record tables (policy_rule_user_approval_record, policy_rule_role_approval_record, policy_rule_any_approval_record) consistently receive the environment_id column with NOT NULL constraint.


8-13: LGTM! Proper foreign key constraints with cascade deletion.

The foreign key constraints properly reference both deployment_version and environment tables with ON DELETE cascade to maintain referential integrity.


14-14: LGTM! Updated unique constraint for environment scoping.

The new unique index correctly includes deployment_version_id, environment_id, and user_id, ensuring users can only approve a deployment version once per environment while allowing multiple approvals across different environments.


1-3: Breaking change: Verify data loss is acceptable.

The migration deletes all existing approval records. Ensure this data loss is intentional and stakeholders are aware of this breaking change.

What are the best practices for handling breaking database migrations that delete existing data?
packages/api/src/router/deployment-version.ts (3)

345-345: LGTM! Input schema correctly updated for multi-environment support.

The change from single environmentId to environmentIds array properly supports the new batch approval functionality.


358-368: Well-implemented batch record creation logic.

The mapping approach to create multiple approval records is clean and handles the multi-environment scenario correctly. Each record gets the same approval metadata but different environment IDs.


389-391: ```shell
#!/bin/bash

Display full context around the conditional to understand its usage

sed -n '340,420p' packages/api/src/router/deployment-version.ts


</details>
<details>
<summary>apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/_components/deployment-version/ApprovalDialog.tsx (2)</summary>

`31-110`: **Well-structured multi-environment selection component.**

The `EnvironmentCombobox` provides good UX with badges for selected environments and a searchable popover. The "All environments" option is a nice touch.

---

`204-204`: Here’s a deeper sweep to locate the `bySystemId` query and confirm the existence of an “environment” router. Please run this from the repo root:


```shell
#!/usr/bin/env bash

echo "⮕ Searching for any 'bySystemId' references:"
rg -n "bySystemId" || echo "No occurrences found."

echo
echo "⮕ Finding all tRPC routers and filtering for 'environment':"
rg -n "createTRPCRouter" | rg "environment" || echo "No environment router definitions found."

echo
echo "⮕ Listing all .ts files with 'environment' in their filename:"
fd -e ts environment || echo "No files matching '*environment*.ts'."

echo
echo "⮕ Inspecting those files for '.query(' definitions:"
for file in $(fd -e ts environment); do
  echo "---- $file ----"
  rg -n "\.query\(" "$file" || echo "  (no queries in this file)"
done
packages/rule-engine/src/manager/version-manager-rules/version-approval.ts (2)

14-25: LGTM! Approval rule properly parameterized with environment context.

The versionAnyApprovalRule function correctly accepts environmentId and passes it to the environment-specific getter function.


55-58: ```shell
#!/bin/bash

Searching for all call sites of getVersionApprovalRules across TS/JS files

echo "Searching for getVersionApprovalRules usage in .ts/.tsx/.js/.jsx files..."
rg -n "getVersionApprovalRules" -g ".ts" -g ".tsx" -g ".js" -g ".jsx" -C 3 || true


</details>
<details>
<summary>e2e/api/schema.ts (5)</summary>

`44-60`: **LGTM: Well-structured environment-scoped approval endpoint**

The new endpoint for environment-scoped deployment version approval follows proper RESTful patterns and maintains consistency with existing API structure.

---

`112-128`: **LGTM: Consistent environment-scoped rejection endpoint**

The rejection endpoint mirrors the approval endpoint structure, maintaining API consistency and providing symmetric functionality for environment-scoped operations.

---

`1039-1039`: **LGTM: Appropriate schema extension for environment scoping**

Adding the `environmentId` field to the `ApprovalRecord` schema with proper UUID formatting enables tracking approval records per environment, which is essential for the environment-scoped approval feature.

---

`1679-1742`: **LGTM: Comprehensive operation definition with proper error handling**

The `approveDeploymentVersionForEnvironment` operation is well-defined with:
- Correct path parameters for both deployment version and environment
- Appropriate request body structure with required `reason` field
- Comprehensive error response coverage (403, 404, 500)
- Consistent return type using `ApprovalRecord` schema

---

`1925-1988`: **LGTM: Symmetric reject operation maintains API consistency**

The rejection operation properly mirrors the approval operation structure, ensuring consistent behavior across the environment-scoped approval workflow. The request/response patterns align with existing API conventions.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Comment on lines 32 to 34
reason: { type: "string" },
approvedAt: { type: "string", format: "date-time" },
},
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Remove inappropriate approvedAt field from rejection request body.

The approvedAt field doesn't make sense in a rejection request body since rejections should have approvedAt: null. This field should be removed from the rejection endpoint's request schema.

 schema: {
   type: "object",
   properties: {
     reason: { type: "string" },
-    approvedAt: { type: "string", format: "date-time" },
   },
   required: ["reason"],
 },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
reason: { type: "string" },
approvedAt: { type: "string", format: "date-time" },
},
schema: {
type: "object",
properties: {
reason: { type: "string" },
},
required: ["reason"],
},
🤖 Prompt for AI Agents
In
apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/reject/environment/[environmentId]/openapi.ts
around lines 32 to 34, remove the `approvedAt` field from the rejection request
body schema because it is inappropriate for rejection requests. Update the
schema to only include relevant fields like `reason` and exclude `approvedAt`
entirely.

Comment on lines +138 to +142
const setEnvironmentSelected = (environmentIds: string[]) =>
setEnvironmentIds((prev) => [...prev, ...environmentIds]);

const setEnvironmentUnselected = (environmentId: string) =>
setEnvironmentIds((prev) => prev.filter((id) => id !== environmentId));
Copy link
Contributor

@coderabbitai coderabbitai bot Jun 18, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Potential issue with environment selection logic.

The setEnvironmentSelected function appends new IDs without checking for duplicates, while setEnvironmentUnselected properly filters. This could lead to duplicate environment IDs if the same environment is selected multiple times.

-  const setEnvironmentSelected = (environmentIds: string[]) =>
-    setEnvironmentIds((prev) => [...prev, ...environmentIds]);
+  const setEnvironmentSelected = (environmentIds: string[]) =>
+    setEnvironmentIds((prev) => {
+      const newIds = environmentIds.filter((id) => !prev.includes(id));
+      return [...prev, ...newIds];
+    });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const setEnvironmentSelected = (environmentIds: string[]) =>
setEnvironmentIds((prev) => [...prev, ...environmentIds]);
const setEnvironmentUnselected = (environmentId: string) =>
setEnvironmentIds((prev) => prev.filter((id) => id !== environmentId));
const setEnvironmentSelected = (environmentIds: string[]) =>
setEnvironmentIds((prev) => {
const newIds = environmentIds.filter((id) => !prev.includes(id));
return [...prev, ...newIds];
});
const setEnvironmentUnselected = (environmentId: string) =>
setEnvironmentIds((prev) => prev.filter((id) => id !== environmentId));
🤖 Prompt for AI Agents
In
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/_components/deployment-version/ApprovalDialog.tsx
around lines 138 to 142, the setEnvironmentSelected function appends new
environment IDs without checking for duplicates, which can cause duplicate IDs
in the state. Modify setEnvironmentSelected to merge the new IDs with the
existing ones while removing duplicates, for example by using a Set or filtering
duplicates before updating the state.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this probably won't happen and even if it does its a noop since its used in an inArray check in the db

Copy link
Contributor

Choose a reason for hiding this comment

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

@adityachoudhari26 You're absolutely right! I see the logic now:

  1. The unselectedEnvironments filter ensures users can only select from environments not already selected, making natural duplicate selection unlikely
  2. Even in edge cases with duplicates, the database inArray operations would treat them as identical values, making it effectively a no-op

Thanks for the clarification - your approach is sound and the additional complexity of duplicate filtering isn't necessary here.

Comment on lines +55 to +70
export const getVersionApprovalRules = async (
policy: Policy | null,
): FilterRule<Version>[] => [
...versionUserApprovalRule(policy?.versionUserApprovals),
...versionAnyApprovalRule(policy?.versionAnyApprovals),
...versionRoleApprovalRule(policy?.versionRoleApprovals),
];
releaseTargetId: string,
): Promise<FilterRule<Version>[]> => {
const releaseTarget = await db
.select()
.from(schema.releaseTarget)
.where(eq(schema.releaseTarget.id, releaseTargetId))
.then(takeFirst);
const { environmentId } = releaseTarget;
return [
...versionUserApprovalRule(environmentId, policy?.versionUserApprovals),
...versionAnyApprovalRule(environmentId, policy?.versionAnyApprovals),
...versionRoleApprovalRule(environmentId, policy?.versionRoleApprovals),
];
};
Copy link
Contributor

@coderabbitai coderabbitai bot Jun 18, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue

Verify error handling for missing release target.

The function queries the release target by ID but doesn't handle the case where the release target might not exist. The takeFirst will return undefined if no record is found, which would cause a runtime error when accessing releaseTarget.environmentId.

 export const getVersionApprovalRules = async (
   policy: Policy | null,
   releaseTargetId: string,
 ): Promise<FilterRule<Version>[]> => {
   const releaseTarget = await db
     .select()
     .from(schema.releaseTarget)
     .where(eq(schema.releaseTarget.id, releaseTargetId))
     .then(takeFirst);
+  
+  if (!releaseTarget) {
+    throw new Error(`Release target not found: ${releaseTargetId}`);
+  }
+  
   const { environmentId } = releaseTarget;
   return [
     ...versionUserApprovalRule(environmentId, policy?.versionUserApprovals),
     ...versionAnyApprovalRule(environmentId, policy?.versionAnyApprovals),
     ...versionRoleApprovalRule(environmentId, policy?.versionRoleApprovals),
   ];
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const getVersionApprovalRules = async (
policy: Policy | null,
): FilterRule<Version>[] => [
...versionUserApprovalRule(policy?.versionUserApprovals),
...versionAnyApprovalRule(policy?.versionAnyApprovals),
...versionRoleApprovalRule(policy?.versionRoleApprovals),
];
releaseTargetId: string,
): Promise<FilterRule<Version>[]> => {
const releaseTarget = await db
.select()
.from(schema.releaseTarget)
.where(eq(schema.releaseTarget.id, releaseTargetId))
.then(takeFirst);
const { environmentId } = releaseTarget;
return [
...versionUserApprovalRule(environmentId, policy?.versionUserApprovals),
...versionAnyApprovalRule(environmentId, policy?.versionAnyApprovals),
...versionRoleApprovalRule(environmentId, policy?.versionRoleApprovals),
];
};
export const getVersionApprovalRules = async (
policy: Policy | null,
releaseTargetId: string,
): Promise<FilterRule<Version>[]> => {
const releaseTarget = await db
.select()
.from(schema.releaseTarget)
.where(eq(schema.releaseTarget.id, releaseTargetId))
.then(takeFirst);
if (!releaseTarget) {
throw new Error(`Release target not found: ${releaseTargetId}`);
}
const { environmentId } = releaseTarget;
return [
...versionUserApprovalRule(environmentId, policy?.versionUserApprovals),
...versionAnyApprovalRule(environmentId, policy?.versionAnyApprovals),
...versionRoleApprovalRule(environmentId, policy?.versionRoleApprovals),
];
};
🤖 Prompt for AI Agents
In packages/rule-engine/src/manager/version-manager-rules/version-approval.ts
around lines 55 to 70, the function does not handle the case when the release
target is not found and takeFirst returns undefined, leading to a runtime error
when accessing environmentId. Add a check after fetching the release target to
verify it is not undefined; if it is, throw an appropriate error or handle the
missing case gracefully before accessing environmentId.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

takeFirst throws an error if release target is not found

Copy link
Contributor

Choose a reason for hiding this comment

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

@adityachoudhari26 Thank you for the clarification! You're absolutely right - if takeFirst throws an error when no record is found, then the current implementation already has proper error handling built-in. My suggested null check would indeed be unnecessary. I appreciate you correcting my understanding of how takeFirst behaves in this codebase.


✏️ Learnings added
Learnt from: adityachoudhari26
PR: ctrlplanedev/ctrlplane#593
File: packages/rule-engine/src/manager/version-manager-rules/version-approval.ts:55-70
Timestamp: 2025-06-18T21:46:51.448Z
Learning: The `takeFirst` utility function from the database layer throws an error if no record is found, rather than returning `undefined`. This means functions using `takeFirst` already have error handling built-in and don't require additional null checks.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

Comment on lines 5499 to 5503
"required": [
"id",
"deploymentVersionId",
"environmentId",
"userId",
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Review backwards compatibility of new required environmentId
Marking environmentId as required in ApprovalRecord will break existing singular approve/reject endpoints since they don’t supply this field. Consider marking it nullable or creating a separate schema for environment-scoped records to avoid invalid responses.

🤖 Prompt for AI Agents
In openapi.v1.json around lines 5499 to 5503, the field "environmentId" was
added as required in the ApprovalRecord schema, which breaks backward
compatibility with existing approve/reject endpoints that do not provide this
field. To fix this, either make "environmentId" nullable in the schema or create
a separate schema variant for environment-scoped approval records, ensuring the
original endpoints remain valid and do not produce invalid responses.

Comment on lines 625 to 728
"/v1/deployment-versions/{deploymentVersionId}/reject/environment/{environmentId}": {
"post": {
"summary": "Reject a deployment version for an environment",
"operationId": "rejectDeploymentVersionForEnvironment",
"parameters": [
{
"name": "deploymentVersionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "environmentId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"reason": {
"type": "string"
},
"approvedAt": {
"type": "string",
"format": "date-time"
}
},
"required": [
"reason"
]
}
}
}
},
"responses": {
"200": {
"description": "Rejection record created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApprovalRecord"
}
}
}
},
"403": {
"description": "Forbidden",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
}
}
}
},
"404": {
"description": "Deployment version or environment not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
}
}
}
},
"500": {
"description": "Internal server error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
}
}
}
}
}
}
},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ensure requestBody is marked required
Same issue in the rejection endpoint: the requestBody is missing "required": true. Add it for consistency and to prevent invalid calls.

"/v1/deployment-versions/{deploymentVersionId}/reject/environment/{environmentId}": {
  "post": {
    "parameters": [ ... ],
-   "requestBody": {
+   "requestBody": {
+     "required": true,
      "content": {
        "application/json": { ... }
      }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"/v1/deployment-versions/{deploymentVersionId}/reject/environment/{environmentId}": {
"post": {
"summary": "Reject a deployment version for an environment",
"operationId": "rejectDeploymentVersionForEnvironment",
"parameters": [
{
"name": "deploymentVersionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "environmentId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"reason": {
"type": "string"
},
"approvedAt": {
"type": "string",
"format": "date-time"
}
},
"required": [
"reason"
]
}
}
}
},
"responses": {
"200": {
"description": "Rejection record created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApprovalRecord"
}
}
}
},
"403": {
"description": "Forbidden",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
}
}
}
},
"404": {
"description": "Deployment version or environment not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
}
}
}
},
"500": {
"description": "Internal server error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
}
}
}
}
}
}
},
"/v1/deployment-versions/{deploymentVersionId}/reject/environment/{environmentId}": {
"post": {
"summary": "Reject a deployment version for an environment",
"operationId": "rejectDeploymentVersionForEnvironment",
"parameters": [
{
"name": "deploymentVersionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "environmentId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"reason": {
"type": "string"
},
"approvedAt": {
"type": "string",
"format": "date-time"
}
},
"required": [
"reason"
]
}
}
}
},
"responses": {
"200": {
"description": "Rejection record created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApprovalRecord"
}
}
}
},
"403": {
"description": "Forbidden",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
}
}
}
},
"404": {
"description": "Deployment version or environment not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
}
}
}
},
"500": {
"description": "Internal server error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
}
}
}
}
}
}
},
🤖 Prompt for AI Agents
In openapi.v1.json between lines 625 and 728, the POST endpoint for rejecting a
deployment version lacks the "required": true attribute on the requestBody
object. To fix this, add "required": true to the requestBody field to ensure the
request body is mandatory, maintaining consistency and preventing invalid calls.

Comment on lines 222 to 325
"/v1/deployment-versions/{deploymentVersionId}/approve/environment/{environmentId}": {
"post": {
"summary": "Approve a deployment version for an environment",
"operationId": "approveDeploymentVersionForEnvironment",
"parameters": [
{
"name": "deploymentVersionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "environmentId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"reason": {
"type": "string"
},
"approvedAt": {
"type": "string",
"format": "date-time"
}
},
"required": [
"reason"
]
}
}
}
},
"responses": {
"200": {
"description": "Approval record created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApprovalRecord"
}
}
}
},
"403": {
"description": "Forbidden",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
}
}
}
},
"404": {
"description": "Deployment version or environment not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
}
}
}
},
"500": {
"description": "Internal server error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
}
}
}
}
}
}
},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ensure requestBody is marked required
The new environment-scoped approval endpoint’s requestBody object is missing a "required": true flag. Without this, clients may omit the request body altogether, leading to unexpected errors. Add "required": true to align with other endpoints.

"/v1/deployment-versions/{deploymentVersionId}/approve/environment/{environmentId}": {
  "post": {
    "parameters": [ ... ],
-   "requestBody": {
+   "requestBody": {
+     "required": true,
      "content": {
        "application/json": { ... }
      }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"/v1/deployment-versions/{deploymentVersionId}/approve/environment/{environmentId}": {
"post": {
"summary": "Approve a deployment version for an environment",
"operationId": "approveDeploymentVersionForEnvironment",
"parameters": [
{
"name": "deploymentVersionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "environmentId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"reason": {
"type": "string"
},
"approvedAt": {
"type": "string",
"format": "date-time"
}
},
"required": [
"reason"
]
}
}
}
},
"responses": {
"200": {
"description": "Approval record created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApprovalRecord"
}
}
}
},
"403": {
"description": "Forbidden",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
}
}
}
},
"404": {
"description": "Deployment version or environment not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
}
}
}
},
"500": {
"description": "Internal server error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
}
}
}
}
}
}
},
"/v1/deployment-versions/{deploymentVersionId}/approve/environment/{environmentId}": {
"post": {
"summary": "Approve a deployment version for an environment",
"operationId": "approveDeploymentVersionForEnvironment",
"parameters": [
{
"name": "deploymentVersionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "environmentId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"reason": {
"type": "string"
},
"approvedAt": {
"type": "string",
"format": "date-time"
}
},
"required": [
"reason"
]
}
}
}
},
"responses": {
"200": {
"description": "Approval record created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApprovalRecord"
}
}
}
},
"403": {
"description": "Forbidden",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
}
}
}
},
"404": {
"description": "Deployment version or environment not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
}
}
}
},
"500": {
"description": "Internal server error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
}
}
}
}
}
}
}
},
🤖 Prompt for AI Agents
In openapi.v1.json between lines 222 and 325, the POST endpoint for approving a
deployment version for an environment has a requestBody defined but it is
missing the "required": true flag. To fix this, add "required": true to the
requestBody object to ensure clients must include the request body when calling
this endpoint, preventing omission and potential errors.

@adityachoudhari26 adityachoudhari26 merged commit b33fea4 into main Jun 18, 2025
10 checks passed
@adityachoudhari26 adityachoudhari26 deleted the scope-approval-to-env branch June 18, 2025 22:33
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