Skip to content

Conversation

@adityachoudhari26
Copy link
Contributor

@adityachoudhari26 adityachoudhari26 commented Jun 12, 2025

Summary by CodeRabbit

  • New Features

    • Introduced environment version rollout policies with configurable rollout strategies (linear, linear-normalized, exponential, exponential-normalized) controlling deployment timing.
    • Added API endpoint to retrieve rollout timing and position for release targets within a deployment version and environment.
    • Policies now support rollout configuration parameters, accessible and editable via API and UI.
    • Policy GET endpoints include rollout configuration data.
    • Version approval rules and rollout rules integrated for controlled rollout scheduling.
  • Bug Fixes

    • Ensured consistent handling and return of rollout and approval timestamps in API endpoints and schemas.
  • Documentation

    • Updated OpenAPI specs and API documentation to include rollout policy fields, new endpoints, and approval timestamp support.
  • Tests

    • Added end-to-end tests validating environment version rollout policies across multiple rollout strategies and approval conditions.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jun 12, 2025

Walkthrough

This change introduces a new "environment version rollout" policy rule for controlled, staged deployment rollouts across release targets. It adds database schema, API endpoints, OpenAPI documentation, backend logic, and end-to-end tests for configuring and retrieving rollout schedules. The policy system and rule engine are updated to support this rollout strategy, including approval-based rollout timing.

Changes

File(s) / Path(s) Change Summary
packages/db/src/schema/rules/environment-version-rollout.ts, SQL migration, Added schema, enum, and table for environment version rollout policy rule, with validation and type definitions.
packages/db/src/schema/policy-relations.ts, rule-relations.ts, policy.ts Integrated new rollout rule into policy relations and schemas.
packages/db/src/schema/rules/index.ts Exported new rollout rule module.
packages/rule-engine/src/db/get-applicable-policies.ts, index.ts Added logic and exports for fetching policies with rollout configuration.
packages/rule-engine/src/db/create-policy.ts, update-policy.ts Enabled creation and updating of rollout configuration in policies.
packages/rule-engine/src/types.ts Extended policy type with optional rollout configuration.
packages/rule-engine/src/manager/version-manager-rules/ Implemented rollout rule logic, approval integration, and exported rule utilities.
packages/rule-engine/src/manager/version-manager.ts, version-rule-engine.ts Updated rule engine to support asynchronous rollout rules.
packages/rule-engine/src/rules/environment-version-rollout-rule.ts Defined rollout rule strategies and filtering logic.
packages/rule-engine/src/rules/version-approval-rule.ts Extended approval records with approval timestamp.
apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/... Added GET endpoint, handler, and OpenAPI for rollout info retrieval; PATCH/POST extended for rollout/approval.
apps/webservice/src/app/api/v1/policies/[policyId]/route.ts, openapi.ts Added GET handler and OpenAPI for retrieving policy with rollout config.
apps/webservice/src/app/api/v1/policies/openapi.ts Added EnvironmentVersionRollout schema and integrated into Policy schema.
apps/webservice/src/app/api/v1/openapi.ts Removed old Policy schema.
apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/... Extended frontend form context with rollout config.
e2e/api/entity-fixtures.ts, e2e/api/schema.ts Added rollout config to policy fixtures and schemas; updated API schema and endpoints for rollout.
e2e/tests/api/policies/environment-version-rollout-policy.spec.ts, .yaml Added comprehensive E2E tests and fixtures for rollout policy scenarios.
openapi.v1.json Updated OpenAPI spec: new rollout endpoint, new Policy and rollout schemas, approval timestamp support.
packages/api/src/router/policy/evaluate.ts, router.ts Refactored to use shared rollout policy logic and include rollout relation in queries.
packages/db/drizzle/meta/_journal.json Appended migration journal entry.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant API
    participant DB
    participant RuleEngine

    User->>API: GET /v1/deployment-versions/{deploymentVersionId}/environments/{environmentId}/rollout
    API->>DB: Fetch deploymentVersion, environment, releaseTargets
    API->>DB: Fetch applicable policies (with rollout config)
    API->>RuleEngine: Compute rollout info for each releaseTarget
    RuleEngine->>DB: (If needed) Fetch approvals, release target positions
    RuleEngine-->>API: Rollout times and positions for each releaseTarget
    API-->>User: JSON array of releaseTargets with rolloutTime, rolloutPosition
Loading
sequenceDiagram
    participant Admin
    participant API
    participant DB

    Admin->>API: PATCH /v1/policies/{policyId} (with environmentVersionRollout config)
    API->>DB: Upsert environmentVersionRollout for policy
    API-->>Admin: Updated policy with rollout config
Loading

Suggested reviewers

  • jsbroks
  • zacharyblasczyk

Poem

🐇 Hop, hop, the rollout’s here,
Across the targets, far and near.
Linear, exponential, smooth and bright,
Approvals guide the rollout flight.
Times and positions all aligned,
In code and tests, the rules defined.
🎉 Rollout magic, swift and clear!


📜 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 cd18d50 and 288b549.

📒 Files selected for processing (1)
  • packages/rule-engine/src/manager/version-manager-rules/environment-version-rollout.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/rule-engine/src/manager/version-manager-rules/environment-version-rollout.ts
⏰ Context from checks skipped due to timeout of 90000ms (6)
  • GitHub Check: Typecheck
  • GitHub Check: build (linux/amd64)
  • GitHub Check: build (linux/amd64)
  • GitHub Check: Lint
  • GitHub Check: build (linux/amd64)
  • GitHub Check: build (linux/amd64)
✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • 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 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 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 for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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

🧹 Nitpick comments (4)
packages/api/src/router/policy/evaluate.ts (1)

63-64: Extra join may inflate payload

As in the rule-engine query, the rollout relation is now always selected but is not referenced in the rest of evaluate.ts. If future logic will consume it, that’s fine; if not, you can save bandwidth by omitting it here.

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

66-73: Brace-less multi-line return reduces safety

Dropping the braces is legal, but the multi-line object literals make ASI pitfalls harder to spot and reduce diff clarity. Consider restoring braces for multi-line branches to keep the style consistent with the rest of the codebase:

-if (!result.passing)
-  return {
+if (!result.passing) {
   chosenCandidate: null,
   rejectionReasons: new ConstantMap<string, string>(
     result.rejectionReason ?? "",
   ),
-  };
+}

Same for the allowedCandidates.length === 0 block.

Also applies to: 81-87, 90-94, 101-103

packages/rule-engine/src/rules/__tests__/environment-version-rollout-rule.test.ts (1)

112-114: Unnecessary toBeCloseTodifferenceInMinutes already floors

differenceInMinutes returns an integer, so toBeCloseTo(38, 1) is equivalent to toBe(38) and hides potential rounding issues.
Swap to a strict equality assertion to make the test intent clearer.

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

60-63: Environment rollout rule not wired into the rule set

getRules aggregates concurrency and approval rules but omits the new EnvironmentVersionRolloutRule.
If rollout gating is meant to be active at this level, the rule should be instantiated (probably via the helper in version-manager-rules/environment-version-rollout.ts) and added to the returned array.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 80d5d2a and 00549e2.

📒 Files selected for processing (16)
  • packages/api/src/router/policy/evaluate.ts (1 hunks)
  • packages/db/src/schema/policy-relations.ts (2 hunks)
  • packages/db/src/schema/rules/environment-version-rollout.ts (1 hunks)
  • packages/db/src/schema/rules/index.ts (1 hunks)
  • packages/db/src/schema/rules/rule-relations.ts (2 hunks)
  • packages/rule-engine/src/db/get-applicable-policies.ts (1 hunks)
  • packages/rule-engine/src/index.ts (1 hunks)
  • packages/rule-engine/src/manager/version-manager-rules.ts (1 hunks)
  • packages/rule-engine/src/manager/version-manager-rules/environment-version-rollout.ts (1 hunks)
  • packages/rule-engine/src/manager/version-manager-rules/index.ts (1 hunks)
  • packages/rule-engine/src/manager/version-manager-rules/version-approval.ts (1 hunks)
  • packages/rule-engine/src/manager/version-rule-engine.ts (2 hunks)
  • packages/rule-engine/src/rules/__tests__/environment-version-rollout-rule.test.ts (1 hunks)
  • packages/rule-engine/src/rules/environment-version-rollout-rule.ts (1 hunks)
  • packages/rule-engine/src/rules/version-approval-rule.ts (1 hunks)
  • packages/rule-engine/src/types.ts (1 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.

  • packages/rule-engine/src/index.ts
  • packages/rule-engine/src/types.ts
  • packages/rule-engine/src/rules/version-approval-rule.ts
  • packages/db/src/schema/rules/index.ts
  • packages/rule-engine/src/db/get-applicable-policies.ts
  • packages/api/src/router/policy/evaluate.ts
  • packages/db/src/schema/rules/rule-relations.ts
  • packages/rule-engine/src/manager/version-rule-engine.ts
  • packages/rule-engine/src/manager/version-manager-rules/index.ts
  • packages/db/src/schema/policy-relations.ts
  • packages/rule-engine/src/rules/__tests__/environment-version-rollout-rule.test.ts
  • packages/rule-engine/src/rules/environment-version-rollout-rule.ts
  • packages/rule-engine/src/manager/version-manager-rules/version-approval.ts
  • packages/db/src/schema/rules/environment-version-rollout.ts
  • packages/rule-engine/src/manager/version-manager-rules.ts
  • packages/rule-engine/src/manager/version-manager-rules/environment-version-rollout.ts
🧬 Code Graph Analysis (4)
packages/db/src/schema/rules/rule-relations.ts (2)
packages/db/src/schema/rules/environment-version-rollout.ts (1)
  • policyRuleEnvironmentVersionRollout (12-34)
packages/db/src/schema/policy.ts (1)
  • policy (42-62)
packages/db/src/schema/policy-relations.ts (1)
packages/db/src/schema/rules/environment-version-rollout.ts (1)
  • policyRuleEnvironmentVersionRollout (12-34)
packages/rule-engine/src/manager/version-manager-rules/version-approval.ts (3)
packages/rule-engine/src/types.ts (2)
  • Policy (66-74)
  • FilterRule (31-36)
packages/rule-engine/src/rules/version-approval-rule.ts (4)
  • VersionApprovalRule (28-66)
  • getAnyApprovalRecords (68-81)
  • getRoleApprovalRecords (83-96)
  • getUserApprovalRecords (98-111)
packages/rule-engine/src/manager/version-rule-engine.ts (1)
  • Version (11-16)
packages/rule-engine/src/manager/version-manager-rules/environment-version-rollout.ts (8)
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 (48-54)
packages/rule-engine/src/rules/version-approval-rule.ts (3)
  • getAnyApprovalRecords (68-81)
  • getUserApprovalRecords (98-111)
  • getRoleApprovalRecords (83-96)
packages/db/src/schema/release.ts (1)
  • releaseTarget (20-42)
packages/db/src/client.ts (1)
  • db (15-15)
packages/db/src/common.ts (1)
  • takeFirst (9-13)
packages/rule-engine/src/rules/environment-version-rollout-rule.ts (2)
  • RolloutTypeToOffsetFunction (31-37)
  • EnvironmentVersionRolloutRule (46-89)
⏰ Context from checks skipped due to timeout of 90000ms (6)
  • GitHub Check: build (linux/amd64)
  • GitHub Check: build (linux/amd64)
  • GitHub Check: Typecheck
  • GitHub Check: Lint
  • GitHub Check: build (linux/amd64)
  • GitHub Check: build (linux/amd64)
🔇 Additional comments (12)
packages/rule-engine/src/index.ts (1)

2-2: Adjusted export path for version-manager-rules index
Exporting from ./manager/version-manager-rules/index.js correctly aligns with the new consolidated exports.

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

15-15: Verify DB schema support for approvedAt field
The newly added approvedAt: Date | null in the Record type relies on a matching column in the approval record tables. Confirm that the database schema and ORM mappings include this field so that record.approvedAt is populated as expected.

packages/db/src/schema/rules/index.ts (1)

13-13: Expose environment version rollout rule in schema exports
Including export * from "./environment-version-rollout.js" correctly makes the new rollout rule schema available through the rules index.

packages/rule-engine/src/types.ts (1)

73-73: Add environmentVersionRollout to Policy type
The Policy type now includes an optional environmentVersionRollout property, matching the new rollout rule schema. Ensure consumers handle both null and non-null values.

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

1-2: Consolidate version-manager rule exports
Re-exporting both environment-version-rollout.js and version-approval.js creates a unified entry point for version manager rules.

packages/db/src/schema/rules/rule-relations.ts (1)

21-22: Environment-rollout relation wired correctly

The import and the one-to-one relation mirror the existing pattern for other rule tables and look correct:
• correct path and naming
fields/references pair matches the rollout table’s policyId FK
No further action needed.

Also applies to: 142-151

packages/rule-engine/src/db/get-applicable-policies.ts (1)

44-45: Relation eagerly loaded – double-check necessity

environmentVersionRollout is now fetched for every policy. This is fine for correctness, but the property is not consumed anywhere in this module. If the rollout data is only required downstream, keep as is; otherwise, consider deferring the load to where it is actually used to avoid unnecessary hydration on large workspaces.

packages/db/src/schema/policy-relations.ts (1)

14-15: Policy ↔︎ rollout relation added correctly

The import and one() mapping line up with the rollout table definition. Nothing else to flag.

Also applies to: 33-36

packages/db/src/schema/rules/environment-version-rollout.ts (2)

22-29: Decimal scale = 2 may lose needed precision

positionGrowthFactor and timeScaleInterval feed exponential / linear calculations.
Restricting them to two decimal places could be limiting for small factors (e.g., 0.005) or sub-minute intervals. Consider a higher scale or numeric type.


30-33: Enum value vs. string default

drizzle-orm expects a raw enum value in .default().
Passing RolloutType.Linear works only if the enum is emitted exactly as the DB value. Double-check generated SQL to ensure it doesn’t wrap the value in quotes twice.

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

1-18: Imports look good.

All imports are properly structured and used within the file.


52-84: Well-implemented position calculation logic.

The deterministic ordering using MD5 hash and window functions ensures consistent positioning across different versions. Good error handling for missing release targets.

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

🔭 Outside diff range comments (1)
e2e/api/schema.ts (1)

2998-3021: 🛠️ Refactor suggestion

Creation flow missing environmentVersionRollout

upsertPolicy request body omits the new field, forcing callers to create a policy and then PATCH it.
Add the property for parity with updatePolicy.

           concurrency?: components["schemas"]["PolicyConcurrency"];
+          environmentVersionRollout?: components["schemas"]["EnvironmentVersionRollout"];
🧹 Nitpick comments (9)
apps/webservice/src/app/api/v1/policies/[policyId]/route.ts (1)

5-7: Redundant schema import

SCHEMA and schema both resolve to the same module; keeping one alias avoids cognitive overhead and tree-shaking noise.

-import * as SCHEMA from "@ctrlplane/db/schema";
-import * as schema from "@ctrlplane/db/schema";
+import * as schema from "@ctrlplane/db/schema";

(then rename SCHEMA usages to schema).

e2e/tests/api/policies/environment-version-rollout-policy.spec.ts (1)

26-26: Consider making the timeout more robust.

The 1-second fixed timeout could be flaky in CI environments. Consider using a more reliable wait mechanism or increasing the timeout.

-await new Promise((resolve) => setTimeout(resolve, 1_000));
+// Wait for resources to be fully created
+await new Promise((resolve) => setTimeout(resolve, 2_000));
apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/environments/[environmentId]/rollout/route.ts (1)

89-117: Consider optimizing database queries.

The code makes sequential database queries that could potentially be optimized:

  1. getDeploymentVersion (line 89)
  2. getEnvironment (line 99)
  3. getReleaseTargets (line 106)
  4. getApplicablePoliciesWithoutResourceScope (line 112)

Consider using Promise.all() for independent queries to improve performance.

-const deploymentVersion = await getDeploymentVersion(
-  db,
-  deploymentVersionId,
-);
-if (deploymentVersion == null)
-  return NextResponse.json(
-    { error: "Deployment version not found" },
-    { status: NOT_FOUND },
-  );
-
-const environment = await getEnvironment(db, environmentId);
-if (environment == null)
-  return NextResponse.json(
-    { error: "Environment not found" },
-    { status: NOT_FOUND },
-  );
+const [deploymentVersion, environment] = await Promise.all([
+  getDeploymentVersion(db, deploymentVersionId),
+  getEnvironment(db, environmentId),
+]);
+
+if (deploymentVersion == null)
+  return NextResponse.json(
+    { error: "Deployment version not found" },
+    { status: NOT_FOUND },
+  );
+
+if (environment == null)
+  return NextResponse.json(
+    { error: "Environment not found" },
+    { status: NOT_FOUND },
+  );
openapi.v1.json (4)

338-366: 200 response schema for rollout data
Returning an array of ReleaseTarget objects extended with rolloutTime and rolloutPosition is appropriate. To prevent excessively large payloads, consider adding a maxItems constraint on the array (CKV_OPENAPI_21).


2255-2260: Extend updatePolicy request with rollout config
Including environmentVersionRollout in the PATCH request schema enables clients to configure rollout behavior. To support explicit removal of rollout settings, consider marking this property as nullable: true.


6197-6222: Refine EnvironmentVersionRollout schema description
The rolloutType description mentions “normalized versions” but the enum only includes "linear" and "exponential". Either update the description to match the enum or add the missing normalized enum values.


6281-6286: Expose rollout config in Policy schema
Adding environmentVersionRollout to Policy completes the schema. To indicate absence of a rollout policy, you may want to mark this property as nullable: true in the response definition.

e2e/api/schema.ts (2)

399-401: GET /v1/policies/{policyId} added – remember rate-limit headers

Endpoint wiring is fine; consider returning standard Retry-After / X-RateLimit-* headers (as with other read-heavy endpoints) for consistency.


1652-1701: Inline response type may lead to duplication

getRolloutInfo re-defines rolloutTime / rolloutPosition inline instead of extending ReleaseTarget via a named schema.
Consider promoting this to e.g. ReleaseTargetWithRollout to avoid copy-paste drift when fields evolve.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 00549e2 and 454fac4.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (22)
  • apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/environments/[environmentId]/rollout/openapi.ts (1 hunks)
  • apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/environments/[environmentId]/rollout/route.ts (1 hunks)
  • apps/webservice/src/app/api/v1/openapi.ts (0 hunks)
  • apps/webservice/src/app/api/v1/policies/[policyId]/openapi.ts (2 hunks)
  • apps/webservice/src/app/api/v1/policies/[policyId]/route.ts (1 hunks)
  • apps/webservice/src/app/api/v1/policies/openapi.ts (3 hunks)
  • e2e/api/entity-fixtures.ts (1 hunks)
  • e2e/api/schema.ts (8 hunks)
  • e2e/package.json (1 hunks)
  • e2e/tests/api/policies/environment-version-rollout-policy.spec.ts (1 hunks)
  • e2e/tests/api/policies/environment-version-rollout-policy.spec.yaml (1 hunks)
  • openapi.v1.json (6 hunks)
  • packages/api/src/router/policy/evaluate.ts (1 hunks)
  • packages/db/src/schema/policy.ts (3 hunks)
  • packages/db/src/schema/rules/environment-version-rollout.ts (1 hunks)
  • packages/rule-engine/src/db/create-policy.ts (2 hunks)
  • packages/rule-engine/src/db/get-applicable-policies.ts (3 hunks)
  • packages/rule-engine/src/db/update-policy.ts (4 hunks)
  • packages/rule-engine/src/manager/version-manager-rules.ts (2 hunks)
  • packages/rule-engine/src/manager/version-manager-rules/environment-version-rollout.ts (1 hunks)
  • packages/rule-engine/src/manager/version-manager.ts (1 hunks)
  • packages/rule-engine/src/rules/environment-version-rollout-rule.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • apps/webservice/src/app/api/v1/openapi.ts
✅ Files skipped from review due to trivial changes (3)
  • e2e/package.json
  • apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/environments/[environmentId]/rollout/openapi.ts
  • e2e/tests/api/policies/environment-version-rollout-policy.spec.yaml
🚧 Files skipped from review as they are similar to previous changes (5)
  • packages/api/src/router/policy/evaluate.ts
  • packages/rule-engine/src/manager/version-manager-rules.ts
  • packages/rule-engine/src/rules/environment-version-rollout-rule.ts
  • packages/db/src/schema/rules/environment-version-rollout.ts
  • packages/rule-engine/src/manager/version-manager-rules/environment-version-rollout.ts
🧰 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/policies/[policyId]/route.ts
  • packages/db/src/schema/policy.ts
  • apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/environments/[environmentId]/rollout/route.ts
  • e2e/api/entity-fixtures.ts
  • packages/rule-engine/src/db/create-policy.ts
  • packages/rule-engine/src/db/get-applicable-policies.ts
  • e2e/tests/api/policies/environment-version-rollout-policy.spec.ts
  • packages/rule-engine/src/db/update-policy.ts
  • packages/rule-engine/src/manager/version-manager.ts
  • apps/webservice/src/app/api/v1/policies/openapi.ts
  • apps/webservice/src/app/api/v1/policies/[policyId]/openapi.ts
  • e2e/api/schema.ts
🧬 Code Graph Analysis (4)
packages/db/src/schema/policy.ts (1)
packages/db/src/schema/rules/environment-version-rollout.ts (1)
  • createPolicyRuleEnvironmentVersionRollout (46-54)
packages/rule-engine/src/db/create-policy.ts (1)
packages/db/src/common.ts (1)
  • buildConflictUpdateColumns (30-46)
packages/rule-engine/src/db/get-applicable-policies.ts (2)
packages/db/src/client.ts (1)
  • db (15-15)
packages/db/src/common.ts (1)
  • Tx (22-22)
packages/rule-engine/src/manager/version-manager.ts (1)
packages/rule-engine/src/manager/version-manager-rules.ts (1)
  • getRules (58-76)
🪛 Checkov (3.2.334)
openapi.v1.json

[MEDIUM] 343-369: Ensure that arrays have a maximum number of items

(CKV_OPENAPI_21)

⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: build (linux/amd64)
  • GitHub Check: build (linux/amd64)
  • GitHub Check: Typecheck
  • GitHub Check: Lint
🔇 Additional comments (16)
packages/db/src/schema/policy.ts (1)

40-40: Environment-rollout rule wired in, but double-check null-delete semantics

The new environmentVersionRollout field is correctly imported and threaded through both createPolicy and updatePolicy schemas – nice.
One edge-case to verify: when a client wants to remove an existing rollout rule it will submit "environmentVersionRollout": null.
Please confirm that updatePolicyInTx interprets null as “delete the existing row” rather than attempting an UPSERT with null values (which would violate the NOT-NULL constraints in the rollout table).

If the current implementation does not cascade-delete, add explicit handling, e.g.

if (data.environmentVersionRollout === null) {
  await tx.delete(schema.policyRuleEnvironmentVersionRollout)
          .where(eq(schema.policyRuleEnvironmentVersionRollout.policyId, policyId));
}

Also applies to: 161-165, 200-204

apps/webservice/src/app/api/v1/policies/[policyId]/route.ts (1)

33-45: Verify relation names included in .with clause

concurrency is listed as a relation. If the relation factory was not added in policy-relations.ts, Drizzle will throw at runtime. Same for the new environmentVersionRollout.

Please run the route in a dev environment to ensure the query compiles.

packages/rule-engine/src/db/get-applicable-policies.ts (1)

62-63: Drizzle call name – ensure it is db.selectDistinct

Double-check that the Drizzle version you target exports selectDistinct, not selectDistinctOn or selectDistinct... variations; mismatching helper names will fail at runtime.

packages/rule-engine/src/db/create-policy.ts (1)

193-211:

✅ Verification successful

Verify the necessity of converting numeric values to strings.

The code converts positionGrowthFactor and timeScaleInterval from numbers to strings before storing them in the database. This type conversion could lead to precision loss or parsing issues when retrieving the data.

Consider storing these values as numeric types in the database if precision is important, or document why string storage is necessary.


🏁 Script executed:

#!/bin/bash
# Description: Check the schema definition for policyRuleEnvironmentVersionRollout to verify the column types

# Check the schema definition
fd -e ts "environment-version-rollout" packages/db/src/schema --exec cat {} \; | grep -A 20 "policyRuleEnvironmentVersionRollout"

Length of output: 1067


Keep numeric-to-string conversion for decimal fields

The positionGrowthFactor and timeScaleInterval columns are defined as decimal(…) in the schema and the insert builder expects string inputs for these fields. Converting the validated number to string with .toString() is therefore required and preserves full precision.

• File: packages/rule-engine/src/db/create-policy.ts
• Lines: 193–211

No changes needed.

apps/webservice/src/app/api/v1/policies/openapi.ts (1)

78-99: LGTM! Well-structured OpenAPI schema definition.

The EnvironmentVersionRollout schema is properly defined with:

  • Clear property descriptions explaining the purpose of each field
  • Dynamic enum extraction from the database schema ensuring consistency
  • All required fields properly marked
apps/webservice/src/app/api/v1/policies/[policyId]/openapi.ts (1)

12-60: Well-defined GET operation with proper error handling.

The GET operation correctly:

  • Uses UUID format for the policyId parameter
  • Defines appropriate error responses (404 for not found, 500 for server errors)
  • References the Policy schema for the response body
apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/environments/[environmentId]/rollout/route.ts (1)

130-134: Rollout position sorting implementation looks correct.

The sorting by rollout position ensures consistent ordering of release targets, which is essential for predictable rollout behavior.

packages/rule-engine/src/db/update-policy.ts (1)

207-208: Integration looks solid

Destructuring, Promise.all orchestration, and relation fetch have been updated consistently.
No further concerns here.

Also applies to: 233-233, 246-246

openapi.v1.json (4)

312-337: New rollout endpoint definition
The /v1/deployment-versions/{deploymentVersionId}/environments/{environmentId}/rollout path and its two UUID path parameters follow the established API conventions. The operationId, summary, and parameter schemas are correctly specified.


373-402: Error responses for rollout endpoint
The 404 (not found) and 500 (internal server error) responses use a consistent error-object schema matching other endpoints.


2096-2152: GET policy by ID operation
The new GET /v1/policies/{policyId} endpoint correctly returns the updated Policy schema or appropriate 404/500 errors. The path parameter and operationId align with existing patterns.


2502-2503: Use updated Policy schema in upsertPolicy response
Switching the POST /v1/policies 200 response to reference the new Policy schema ensures consistency with the GET and PATCH operations.

e2e/api/schema.ts (4)

61-77: New endpoint added – LGTM, but confirm RBAC & pagination needs

The path definition looks consistent with existing style.
Double-check that downstream route handler enforces workspace / environment access controls and, if the rollout list can grow large, whether pagination or filtering should be exposed up-front.


1377-1378: Optional rollout field on Policy – looks good

The new linkage is correctly typed and optional – ✅.


2802-2844: operations.getPolicy mirrors schema accurately – ok

No issues spotted.


2916-2917: Update body supports rollout config – nice catch

Update flow now accepts environmentVersionRollout; implementation aligns with schema.

Comment on lines +183 to 185
const ruleGetter = options?.rules ?? getRules;
const rules = await ruleGetter(policy, this.releaseTarget.id);

Copy link
Contributor

@coderabbitai coderabbitai bot Jun 12, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

rules callback typing is now stale

await ruleGetter(policy, this.releaseTarget.id) passes two args and expects a Promise, yet
VersionEvaluateOptions.rules is still typed as
(p: Policy | null) => Array<FilterRule<Version> | PreValidationRule>.

Updating the type eliminates implicit-any/unused-param noise and makes the async contract explicit:

-  rules?: (p: Policy | null) => Array<FilterRule<Version> | PreValidationRule>;
+  rules?: (
+    p: Policy | null,
+    releaseTargetId: string,
+  ) => Promise<Array<FilterRule<Version> | PreValidationRule>> | Array<FilterRule<Version> | PreValidationRule>;
🤖 Prompt for AI Agents
In packages/rule-engine/src/manager/version-manager.ts around lines 183 to 185,
the rules callback type in VersionEvaluateOptions is outdated because it
currently expects a synchronous function with one parameter, but the code calls
it with two parameters and expects a Promise. Update the
VersionEvaluateOptions.rules type signature to accept two parameters (policy and
releaseTargetId) and return a Promise of an array of FilterRule or
PreValidationRule to match the actual usage and make the async contract
explicit.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

((p: Policy | null) => Array<FilterRule | PreValidationRule>) | ((policy: Policy | null, releaseTargetId: string) => Promise<Array<FilterRule | PreValidationRule>>)

this is the inferred, type, it looks fine to me? lmk

Copy link
Contributor

Choose a reason for hiding this comment

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

@adityachoudhari26 good observation—the inferred type of the expression (options?.rules ?? getRules) is indeed the union you pasted, so the call site itself type-checks.

The only place the mismatch can bite is when a caller explicitly provides a rules implementation:

const opts: VersionEvaluateOptions = {
  // ⛔  Type '{ ... }' is not assignable ...
  rules: async (policy, releaseTargetId) => {
    /* custom logic */
    return [];
  },
};

Here the assignment fails because VersionEvaluateOptions.rules is still declared as the sync, single-arg signature returning an array, and Promise<Array<…>> isn’t assignable to that.

If you don’t expect external consumers to pass an async implementation, the current typing is harmless. Otherwise, widening the type (or adding an overload) eliminates that friction and improves IntelliSense:

type VersionEvaluateOptions = {
  rules?: (
    p: Policy | null,
    releaseTargetId: string
  ) =>
    | Array<FilterRule<Version> | PreValidationRule>
    | Promise<Array<FilterRule<Version> | PreValidationRule>>;
  // …
};

Totally your call—I just wanted to flag the potential DX hiccup.

Comment on lines 33 to 110
test("should rollout versions in a linear fashion", async ({
api,
workspace,
}) => {
const { id: workspaceId } = workspace;
const { prefix } = builder.refs;

const deployment = builder.refs.deployments.find(
(d) => d.name === `${prefix}-linear-rollout-no-approval`,
)!;

const environment = builder.refs.environments.find(
(e) => e.name === `${prefix}-linear-rollout-no-approval`,
)!;

const policyRef = builder.refs.policies.find(
(p) => p.name === `${prefix}-linear-rollout-no-approval`,
)!;
const policyResponse = await api.GET("/v1/policies/{policyId}", {
params: {
path: {
policyId: policyRef.id,
},
},
});
expect(policyResponse.response.status).toBe(200);
expect(policyResponse.data).toBeDefined();
const policy = policyResponse.data!;
expect(policy.environmentVersionRollout).toBeDefined();
const environmentVersionRollout = policy.environmentVersionRollout!;
expect(environmentVersionRollout.rolloutType).toBe("linear");

const deploymentVersionResponse = await api.POST(
"/v1/deployment-versions",
{
body: {
deploymentId: deployment.id,
tag: faker.string.alphanumeric(10),
},
},
);

expect(deploymentVersionResponse.response.status).toBe(201);
expect(deploymentVersionResponse.data).toBeDefined();
const deploymentVersion = deploymentVersionResponse.data!;

const rolloutResponse = await api.GET(
`/v1/deployment-versions/{deploymentVersionId}/environments/{environmentId}/rollout`,
{
params: {
path: {
deploymentVersionId: deploymentVersion.id,
environmentId: environment.id,
},
},
},
);

expect(rolloutResponse.response.status).toBe(200);
const releaseTargetsWithRolloutInfo = rolloutResponse.data ?? [];
expect(releaseTargetsWithRolloutInfo).toHaveLength(4);

const expectedRolloutStart = startOfMinute(
new Date(deploymentVersion.createdAt),
);

for (let i = 0; i < releaseTargetsWithRolloutInfo.length; i++) {
const releaseTarget = releaseTargetsWithRolloutInfo[i];
const expectedRolloutTime = addMinutes(
expectedRolloutStart,
environmentVersionRollout.timeScaleInterval * i,
);

expect(releaseTarget.rolloutTime).toBe(expectedRolloutTime);
expect(releaseTarget.rolloutPosition).toBe(i);
}
});
});
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

Consider adding test coverage for edge cases and other rollout types.

The test only covers the happy path for linear rollout. Consider adding tests for:

  • Exponential rollout type
  • Normalized rollout types
  • Error scenarios (invalid policy, missing environment, etc.)
  • Edge cases (zero growth factor, negative intervals)

Would you like me to generate additional test cases to improve coverage?

🤖 Prompt for AI Agents
In e2e/tests/api/policies/environment-version-rollout-policy.spec.ts from lines
33 to 110, the current test only covers the linear rollout happy path. To
improve coverage, add new test cases for exponential and normalized rollout
types, include error handling tests for invalid policies and missing
environments, and cover edge cases such as zero growth factor and negative
intervals. Structure these tests similarly to the existing one, adjusting inputs
and assertions to reflect each scenario.

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)
e2e/api/schema.ts (1)

3003-3024: ⚠️ Potential issue

upsertPolicy request body missing environmentVersionRollout

updatePolicy already supports this field, but the create/upsert flow does not, forcing callers to perform a two-step create-then-update.

-          concurrency?: components["schemas"]["PolicyConcurrency"];
+          concurrency?: components["schemas"]["PolicyConcurrency"];
+          environmentVersionRollout?: components["schemas"]["EnvironmentVersionRollout"];

Keeping the two endpoints symmetrical prevents avoidable 400s and simplifies SDKs.

♻️ Duplicate comments (1)
e2e/api/schema.ts (1)

1348-1362: Enum now aligns with docstring – resolved

The "*-normalized" variants are present, eliminating the earlier discrepancy.

🧹 Nitpick comments (10)
e2e/tests/api/policies/environment-version-rollout-policy.spec.yaml (1)

84-84: Add trailing newline

The file is missing a trailing newline, tripping YAML lint and some Git diff viewers.
A one-byte fix keeps linters and tooling quiet.

@@
-      env: prod↵
+      env: prod
+
🧰 Tools
🪛 YAMLlint (1.37.1)

[error] 84-84: no new line character at the end of file

(new-line-at-end-of-file)

apps/webservice/src/app/api/v1/policies/[policyId]/route.ts (3)

5-8: Duplicate wildcard import obscures intent

@ctrlplane/db/schema is imported twice under different aliases (SCHEMA and schema).
Besides the cognitive overhead, it becomes easy to call the wrong constant (e.g. schema.policy vs SCHEMA.policy) and TypeScript will not help you spot the mistake.

-import * as SCHEMA from "@ctrlplane/db/schema";
-import * as schema from "@ctrlplane/db/schema";
+import * as dbSchema from "@ctrlplane/db/schema";

Then adjust the remaining references (SCHEMA → dbSchema).
This keeps a single, unambiguous alias.


65-69: Property shadowing may confuse clients

...policy, concurrency overwrites the original concurrency relation with a scalar value.
While deliberate, the silent overwrite is non-obvious and could break future refactors.

Safer options:

  1. Rename the scalar (concurrencyValue), or
  2. Strip the relation before the spread:
const { concurrency: _, ...sanitizedPolicy } = policy;
return NextResponse.json({ ...sanitizedPolicy, concurrency });

70-76: Include contextual info in error log

log.error("Failed to get policy", { error }); omits the policyId, making incident tracing harder.

-log.error("Failed to get policy", { error });
+log.error("Failed to get policy", { policyId, error });
packages/db/drizzle/0112_curved_annihilus.sql (1)

8-10: Missing index for frequent look-ups

Access patterns will likely join on policy_id.
Although the UNIQUE constraint creates an index, be explicit for clarity and future flexibility:

CREATE INDEX IF NOT EXISTS idx_env_rollout_policy_id
  ON policy_rule_environment_version_rollout(policy_id);
openapi.v1.json (4)

343-349: Bound the rollout response array
The response schema for the rollout endpoint defines an unbounded array. To prevent excessively large payloads, consider adding a maxItems constraint. For example:

type: array
items:
  allOf: [...]
maxItems: 1000
🧰 Tools
🪛 Checkov (3.2.334)

[MEDIUM] 343-369: Ensure that arrays have a maximum number of items

(CKV_OPENAPI_21)


2255-2262: Strictly define environmentVersionRollout in request body
The PATCH schema now allows an environmentVersionRollout object but doesn’t prevent unspecified properties. Add an additionalProperties: false at the same level to enforce strict validation:

   "environmentVersionRollout": {
     "$ref": "#/components/schemas/EnvironmentVersionRollout"
   },
+  "additionalProperties": false

6197-6224: Harden EnvironmentVersionRollout schema and provide example
The new EnvironmentVersionRollout schema lacks an additionalProperties constraint and an example. To improve validation and documentation, extend it as follows:

 "EnvironmentVersionRollout": {
   "type": "object",
+  "additionalProperties": false,
   "properties": {
     "positionGrowthFactor": { ... },
     ...
   },
   "required": [ ... ],
+  "example": {
+    "positionGrowthFactor": 1.2,
+    "timeScaleInterval": 3600,
+    "rolloutType": "exponential-normalized"
+  }
 }

6284-6287: Enhance documentation for environmentVersionRollout in Policy schema
The Policy schema now includes environmentVersionRollout. Consider adding a description or example under this property to clarify its usage for API consumers.

packages/db/src/schema/rules/environment-version-rollout.ts (1)

8-13: Name shadowing is confusing

rolloutType serves both as the enum factory and later as the column builder (rolloutType("rollout_type")).
Renaming the factory to rolloutTypeEnum (or similar) avoids mental context switching and improves readability.

Also applies to: 64-66

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 454fac4 and 157cfcd.

📒 Files selected for processing (13)
  • apps/webservice/src/app/api/v1/policies/[policyId]/route.ts (1 hunks)
  • apps/webservice/src/app/api/v1/policies/openapi.ts (3 hunks)
  • e2e/api/entity-fixtures.ts (1 hunks)
  • e2e/api/schema.ts (8 hunks)
  • e2e/tests/api/policies/environment-version-rollout-policy.spec.ts (1 hunks)
  • e2e/tests/api/policies/environment-version-rollout-policy.spec.yaml (1 hunks)
  • openapi.v1.json (6 hunks)
  • packages/db/drizzle/0112_curved_annihilus.sql (1 hunks)
  • packages/db/drizzle/meta/_journal.json (1 hunks)
  • packages/db/src/schema/rules/environment-version-rollout.ts (1 hunks)
  • packages/rule-engine/src/db/create-policy.ts (3 hunks)
  • packages/rule-engine/src/db/update-policy.ts (4 hunks)
  • packages/rule-engine/src/rules/environment-version-rollout-rule.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/db/drizzle/meta/_journal.json
🚧 Files skipped from review as they are similar to previous changes (6)
  • e2e/api/entity-fixtures.ts
  • apps/webservice/src/app/api/v1/policies/openapi.ts
  • packages/rule-engine/src/db/create-policy.ts
  • packages/rule-engine/src/rules/environment-version-rollout-rule.ts
  • e2e/tests/api/policies/environment-version-rollout-policy.spec.ts
  • packages/rule-engine/src/db/update-policy.ts
🧰 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/policies/[policyId]/route.ts
  • packages/db/src/schema/rules/environment-version-rollout.ts
  • e2e/api/schema.ts
🧬 Code Graph Analysis (1)
apps/webservice/src/app/api/v1/policies/[policyId]/route.ts (3)
apps/webservice/src/app/api/v1/middleware.ts (1)
  • request (24-53)
apps/webservice/src/app/api/v1/auth.ts (2)
  • authn (16-22)
  • authz (26-62)
packages/db/src/schema/policy.ts (1)
  • policy (43-63)
🪛 Checkov (3.2.334)
openapi.v1.json

[MEDIUM] 343-369: Ensure that arrays have a maximum number of items

(CKV_OPENAPI_21)

🪛 YAMLlint (1.37.1)
e2e/tests/api/policies/environment-version-rollout-policy.spec.yaml

[error] 84-84: no new line character at the end of file

(new-line-at-end-of-file)

⏰ Context from checks skipped due to timeout of 90000ms (6)
  • GitHub Check: build (linux/amd64)
  • GitHub Check: Lint
  • GitHub Check: Typecheck
  • GitHub Check: build (linux/amd64)
  • GitHub Check: build (linux/amd64)
  • GitHub Check: build (linux/amd64)
🔇 Additional comments (1)
e2e/api/schema.ts (1)

62-77: New rollout-info endpoint looks consistent

Path params, operation id (getRolloutInfo), and response schema use the recently added rolloutTime/rolloutPosition fields – matches backend contract.

Also applies to: 1655-1704

Comment on lines +47 to +58
const concurrency = policy?.concurrency?.concurrency;
const environmentVersionRollout =
policy?.environmentVersionRollout != null
? {
...policy.environmentVersionRollout,
rolloutType:
SCHEMA.dbRolloutTypeToAPIRolloutType[
policy.environmentVersionRollout.rolloutType
],
}
: null;

Copy link
Contributor

@coderabbitai coderabbitai bot Jun 12, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue

Leaking internal DB columns over the public API

environmentVersionRollout is created by spreading the DB row:

{ ...policy.environmentVersionRollout, rolloutType:  }

This forwards internal columns such as id, policyId, createdAt, etc.
Those fields are not part of the OpenAPI contract and shouldn’t be exposed.

-const environmentVersionRollout = /* … */ {
-  ...policy.environmentVersionRollout,
-  rolloutType: dbSchema.dbRolloutTypeToAPIRolloutType[…],
-};
+const { positionGrowthFactor, timeScaleInterval, rolloutType } =
+  policy.environmentVersionRollout;
+const environmentVersionRollout = {
+  positionGrowthFactor,
+  timeScaleInterval,
+  rolloutType: dbSchema.dbRolloutTypeToAPIRolloutType[rolloutType],
+};

Return only the whitelisted, documented fields.

🤖 Prompt for AI Agents
In apps/webservice/src/app/api/v1/policies/[policyId]/route.ts around lines 47
to 58, the environmentVersionRollout object is created by spreading the entire
policy.environmentVersionRollout DB row, which exposes internal fields like id,
policyId, and createdAt not meant for the public API. To fix this, explicitly
construct environmentVersionRollout by selecting and returning only the
whitelisted, documented fields defined in the OpenAPI contract instead of
spreading the entire object.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i'm fine with these other fields being returned

Copy link
Contributor

Choose a reason for hiding this comment

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

@adityachoudhari26 Understood—if you’re comfortable exposing those extra fields, we’ll leave the implementation as-is. Let me know if you’d like to revisit or tighten the response payload in the future, otherwise I’ll consider this resolved.

Comment on lines 6 to 8
"position_growth_factor" numeric DEFAULT '1' NOT NULL,
"time_scale_interval" numeric NOT NULL,
"rollout_type" "rollout_type" DEFAULT 'linear' NOT NULL,
Copy link
Contributor

@coderabbitai coderabbitai bot Jun 12, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Prefer interval over numeric for time units

time_scale_interval is declared as numeric, yet downstream code most likely treats it as a duration.
PostgreSQL’s native interval type provides richer semantics (units, arithmetic, formatting) and prevents unit confusion.

If a plain number is truly required, add a CHECK constraint to enforce positivity and document the unit (seconds? minutes?).

-- example
ALTER TABLE policy_rule_environment_version_rollout
  ADD CONSTRAINT chk_time_scale_positive
  CHECK (time_scale_interval > 0);
🤖 Prompt for AI Agents
In packages/db/drizzle/0112_curved_annihilus.sql around lines 6 to 8, the column
time_scale_interval is defined as numeric but represents a time duration. Change
its type to PostgreSQL's native interval type to leverage proper time semantics
and avoid unit confusion. If numeric must be kept, add a CHECK constraint to
ensure the value is positive and document the expected unit clearly in comments.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

we actually don't treat it as a duration, we literlaly treat it as a numeric. its used as a scaling factor in an equation

Copy link
Contributor

Choose a reason for hiding this comment

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

@adityachoudhari26 Got it—thanks for clarifying that time_scale_interval is a dimensionless scaling factor. In that case, sticking with numeric makes perfect sense.

(As a minor hardening step, you could still add a CHECK (time_scale_interval > 0) to guard against accidental negatives, but that’s entirely optional.)

Let me know if you’d like a quick patch for the check constraint; otherwise I’ll consider this resolved.


✏️ Learnings added
Learnt from: adityachoudhari26
PR: ctrlplanedev/ctrlplane#587
File: packages/db/drizzle/0112_curved_annihilus.sql:6-8
Timestamp: 2025-06-13T00:14:44.033Z
Learning: In table `policy_rule_environment_version_rollout`, column `time_scale_interval` is intentionally stored as `numeric` because it represents a dimensionless scaling factor in rollout equations, not a time duration.

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.

Comment on lines +312 to +316
"/v1/deployment-versions/{deploymentVersionId}/environments/{environmentId}/rollout": {
"get": {
"summary": "Get the rollout information across all release targets for a given deployment version and environment",
"operationId": "getRolloutInfo",
"parameters": [
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

Add security requirement to rollout endpoint
The new GET /v1/deployment-versions/{deploymentVersionId}/environments/{environmentId}/rollout is missing a security section. All protected API operations should declare:

security:
  - bearerAuth: []

to enforce authentication.

🤖 Prompt for AI Agents
In openapi.v1.json around lines 312 to 316, the GET endpoint for rollout is
missing a security section. Add a "security" field with the value
[{"bearerAuth": []}] to this endpoint to enforce authentication as required for
all protected API operations.

Comment on lines +2096 to +2102
"get": {
"summary": "Get a policy",
"operationId": "getPolicy",
"parameters": [
{
"name": "policyId",
"in": "path",
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

Protect GET /v1/policies/{policyId} with security
This newly added GET operation for retrieving a policy doesn’t declare a security requirement. To align with other policy endpoints, add:

security:
  - bearerAuth: []
🤖 Prompt for AI Agents
In openapi.v1.json around lines 2096 to 2102, the GET operation for
/v1/policies/{policyId} is missing a security requirement. Add a security
section with bearerAuth to this operation to protect it, matching the security
declarations of other policy endpoints. This involves inserting "security":
[{"bearerAuth": []}] within the GET operation object.

Comment on lines +79 to +85
rolloutType: z.enum([
"linear",
"exponential",
"linear-normalized",
"exponential-normalized",
]),
},
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

API ↔︎ DB enum mismatch will break inserts/updates

The table stores underscore values (linear_normalized, …) but the insert schema allows hyphenated values (linear-normalized, …).
Unless every write path converts via apiRolloutTypeToDBRolloutType before hitting the DB (and createInsertSchema is never used for direct inserts), this will raise a invalid input value for enum "rollout_type" error.

Either:

  1. Change the Zod enum to underscore variants, or
  2. Keep current enum but wrap DB writes in an explicit transform in createInsertSchema:
rolloutType: z
-  .enum(["linear", "exponential", "linear-normalized", "exponential-normalized"])
+  .enum(["linear", "exponential", "linear-normalized", "exponential-normalized"])
   .transform((v) => apiRolloutTypeToDBRolloutType[v]),

Pick one place to do the translation; having it in multiple layers invites drift.

Also applies to: 64-66, 8-13

🤖 Prompt for AI Agents
In packages/db/src/schema/rules/environment-version-rollout.ts around lines 79
to 85, the Zod enum uses hyphenated strings while the database expects
underscore-separated values, causing enum mismatch errors on inserts/updates.
Fix this by either changing the Zod enum values to use underscores to match the
DB enum or by adding a transformation in createInsertSchema that converts
hyphenated values to underscore variants before writing to the DB. Ensure this
translation is done in a single consistent place to avoid drift. Also apply this
fix to lines 64-66 and 8-13 where similar enums are defined.

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: 4

🔭 Outside diff range comments (1)
e2e/api/schema.ts (1)

3004-3027: ⚠️ Potential issue

upsertPolicy misses the new environmentVersionRollout field

Policy, updatePolicy, and getPolicy expose the optional environmentVersionRollout, but the upsertPolicy request body doesn’t allow it.
New policies therefore cannot be created with rollout settings in a single call – clients have to upsert and then patch, which is error-prone.

-          concurrency?: components["schemas"]["PolicyConcurrency"];
+          concurrency?: components["schemas"]["PolicyConcurrency"];
+          environmentVersionRollout?: components["schemas"]["EnvironmentVersionRollout"];

Add the field to keep the create and update semantics aligned.

♻️ Duplicate comments (3)
e2e/tests/api/policies/environment-version-rollout-policy.spec.ts (1)

136-137: Same flakiness comment as above – each occurrence should be updated.

Also applies to: 226-228, 318-320, 415-416, 496-497

openapi.v1.json (2)

317-325: Add security requirement to rollout endpoint
The new GET /v1/deployment-versions/{deploymentVersionId}/environments/{environmentId}/rollout is missing a security section. All protected operations should declare:

"security": [{"bearerAuth": []}],

to enforce authentication.


2101-2107: Add security requirement to getPolicy
The GET /v1/policies/{policyId} operation lacks a security section. To align with other policy endpoints, insert:

"security": [{"bearerAuth": []}],
🧹 Nitpick comments (6)
apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/approve/route.ts (1)

46-48: Guard against “Invalid Date” and future timestamps

new Date(ctx.body.approvedAt) can still yield Invalid Date if the runtime’s parser fails spectacularly or if someone bypasses validation.
Consider an explicit check and reject obviously invalid/future dates to keep data consistent:

-const approvedAt =
-  ctx.body.approvedAt != null ? new Date(ctx.body.approvedAt) : new Date();
+const approvedAtRaw = ctx.body.approvedAt ?? new Date();
+const approvedAt =
+  approvedAtRaw instanceof Date ? approvedAtRaw : new Date(approvedAtRaw);
+
+if (Number.isNaN(approvedAt.getTime()) || approvedAt.getTime() > Date.now())
+  return NextResponse.json({ error: "Invalid approvedAt value" }, { status: 400 });

Also applies to: 55-56

e2e/tests/api/policies/environment-version-rollout-policy.spec.ts (2)

292-301: toBeCloseTo on an integer loses intent

rolloutPosition should be an integer index.
Prefer strict equality to catch accidental floats:

-expect(Number(releaseTarget.rolloutPosition)).toBeCloseTo(i, 4);
+expect(Number(releaseTarget.rolloutPosition)).toBe(i);

32-120: Consider extracting helpers to reduce 400-line duplication

Every test repeats:

  1. Policy creation payload assembly
  2. Deployment-version creation
  3. Rollout retrieval
  4. Expectation loop

Factor these into utility functions (e.g., createPolicy, createDeploymentVersion, getRolloutInfo) to cut boilerplate and clarify what changes between tests.

Also applies to: 122-210, 212-302, 304-399, 401-480, 482-583

openapi.v1.json (1)

349-353: Limit response array size
The 200 response schema defines an unbounded array. Consider adding a maxItems constraint to prevent excessively large payloads:

  "schema": {
    "type": "array",
+   "maxItems": 1000,
    "items": { ... }
  }
e2e/api/schema.ts (2)

1348-1362: Specify units & add validation hints for rollout parameters

positionGrowthFactor and timeScaleInterval are plain number types without any indication of valid range or unit (seconds? minutes?).
Clients cannot reason about the expected magnitude, and nothing prevents negative or zero values which would break the maths on the rule-engine side.

   positionGrowthFactor: number;   // e.g. >= 0.1
-  timeScaleInterval: number;
+  /**
+   * Format: int32
+   * @minimum 1
+   * @description Time-scale interval in seconds.
+   */
+  timeScaleInterval: number;

Consider adding @minimum, @maximum, or at least clarifying the unit in the doc-string so generated SDKs carry that information.


1657-1707: Introduce a named schema for the rollout response for better reuse

The response body is defined as an inline anonymous array of ReleaseTarget & { rolloutTime; rolloutPosition }.
This pattern is already used by e2e tests and the node-SDK – moving it to a named component schema (e.g. RolloutInfo) will:

  • reduce duplication across specs,
  • yield cleaner generated typings,
  • make future evolution (adding fields) easier.

No functional impact, but improves maintainability of the OpenAPI spec.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 157cfcd and 5d5b9cb.

📒 Files selected for processing (6)
  • apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/approve/openapi.ts (1 hunks)
  • apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/approve/route.ts (2 hunks)
  • e2e/api/schema.ts (9 hunks)
  • e2e/tests/api/policies/environment-version-rollout-policy.spec.ts (1 hunks)
  • e2e/tests/api/policies/environment-version-rollout-policy.spec.yaml (1 hunks)
  • openapi.v1.json (7 hunks)
✅ Files skipped from review due to trivial changes (1)
  • apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/approve/openapi.ts
🧰 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]/approve/route.ts
  • e2e/tests/api/policies/environment-version-rollout-policy.spec.ts
  • e2e/api/schema.ts
🧬 Code Graph Analysis (1)
e2e/api/schema.ts (1)
packages/node-sdk/src/schema.ts (2)
  • operations (1422-4459)
  • components (892-1420)
🪛 Checkov (3.2.334)
openapi.v1.json

[MEDIUM] 348-374: Ensure that arrays have a maximum number of items

(CKV_OPENAPI_21)

⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: build (linux/amd64)
  • GitHub Check: Typecheck
  • GitHub Check: build (linux/amd64)
  • GitHub Check: build (linux/amd64)
  • GitHub Check: Lint
🔇 Additional comments (5)
e2e/tests/api/policies/environment-version-rollout-policy.spec.yaml (1)

1-105: Looks good – declarative fixture reads cleanly

No schema or structural issues spotted.

e2e/tests/api/policies/environment-version-rollout-policy.spec.ts (1)

388-394: Formula may normalise twice – double-check denominator

(1 - exp(-numReleaseTargets / positionGrowthFactor)) mixes numReleaseTargets with positionGrowthFactor.
If normalisation logic is f(i)/f(N), the denominator should mirror the numerator (exp(-N / positionGrowthFactor) is fine), but it must not also divide by numReleaseTargets earlier (that happens in the numerator).

Confirm this reflects production logic; otherwise the expected times will drift.

openapi.v1.json (2)

2260-2264: Support environment version rollout in updatePolicy
Good addition of the environmentVersionRollout property to the updatePolicy request schema. This aligns the API with the new rollout feature.


6202-6229: Define EnvironmentVersionRollout schema
The new EnvironmentVersionRollout component correctly captures rollout parameters for policies.

e2e/api/schema.ts (1)

1605-1610: Potential audit-bypass via client-supplied approvedAt

Allowing callers to set approvedAt in the approveDeploymentVersion request can let them falsify the approval timestamp (e.g. back-dating to skirt deny-windows).
Unless this is an intentional privileged capability, remove it from the request schema and let the service populate it server-side.

Do the backend handlers ignore / overwrite approvedAt? If not, please hard-enforce it on the server and drop the field from the request contract.

Comment on lines 14 to 17
const bodySchema = z.object({
reason: z.string().optional(),
approvedAt: z.string().datetime().optional(),
});
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

Validate & coerce approvedAt into a Date upfront

z.string().datetime() merely validates the ISO-8601 string but you still need to turn it into a Date.
Doing the coercion in the schema avoids repeated new Date() logic and guarantees you never store an Invalid Date.

-const bodySchema = z.object({
-  reason: z.string().optional(),
-  approvedAt: z.string().datetime().optional(),
-});
+const bodySchema = z.object({
+  reason: z.string().optional(),
+  approvedAt: z
+    .string()
+    .datetime({ offset: true })  // enforce timezone to avoid local-time ambiguities
+    .optional()
+    .transform((v) => (v ? new Date(v) : undefined)),
+});

The rest of the handler can then use ctx.body.approvedAt ?? new Date() without additional parsing.

📝 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 bodySchema = z.object({
reason: z.string().optional(),
approvedAt: z.string().datetime().optional(),
});
const bodySchema = z.object({
reason: z.string().optional(),
approvedAt: z
.string()
.datetime({ offset: true }) // enforce timezone to avoid local-time ambiguities
.optional()
.transform((v) => (v ? new Date(v) : undefined)),
});
🤖 Prompt for AI Agents
In
apps/webservice/src/app/api/v1/deployment-versions/[deploymentVersionId]/approve/route.ts
around lines 14 to 17, the schema currently validates approvedAt as an ISO-8601
string but does not convert it to a Date object. Update the zod schema to coerce
approvedAt into a Date using z.coerce.date().optional() instead of
z.string().datetime().optional(). This ensures approvedAt is always a valid Date
object, allowing the rest of the handler to use ctx.body.approvedAt ?? new
Date() without extra parsing or risk of invalid dates.

Comment on lines +46 to +48
const timeScaleInterval = faker.number.float({ min: 1, max: 100 });
const policyResponse = await api.POST("/v1/policies", {
body: {
Copy link
Contributor

@coderabbitai coderabbitai bot Jun 13, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Random floats make the suite non-deterministic & flaky

faker.number.float({ min: 1, max: 100 }) produces different (and fractional) minutes on every run, which:

  1. Hampers reproducibility when a test fails.
  2. Introduces sub-minute offsets even though rollout code most likely deals in whole minutes.

Replace with a deterministic integer (or seed faker) to stabilise the suite:

-const timeScaleInterval = faker.number.float({ min: 1, max: 100 });
+const timeScaleInterval = 10; // or faker.number.int({ min: 1, max: 100 })
📝 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 timeScaleInterval = faker.number.float({ min: 1, max: 100 });
const policyResponse = await api.POST("/v1/policies", {
body: {
const timeScaleInterval = 10; // or faker.number.int({ min: 1, max: 100 })
const policyResponse = await api.POST("/v1/policies", {
body: {
🤖 Prompt for AI Agents
In e2e/tests/api/policies/environment-version-rollout-policy.spec.ts around
lines 46 to 48, the use of faker.number.float({ min: 1, max: 100 }) generates
random fractional minute values causing test flakiness and non-determinism.
Replace this with a deterministic integer value, either by using a fixed integer
or by seeding faker to produce consistent results, ensuring the
timeScaleInterval is a whole number to match expected rollout code behavior and
improve test reproducibility.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

on the contrary I prefer using trandom numbers because the tests should pass regardless of what floats are passed in here

Copy link
Contributor

Choose a reason for hiding this comment

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

@adityachoudhari26 Got it —keeping randomness is intentional so the tests prove robustness across the full numeric range. Thanks for clarifying!

A couple of quick thoughts to keep the suite solid while still using random floats:

• Seeded randomness per test run

faker.seed(Date.now());

gives you fresh values every CI execution but still lets you reproduce a failure locally by re-using the seed printed in the logs.

• Wider tolerance window
If rollout times are rounded to the nearest minute on the server side (startOfMinute ‑> addMinutes), fractional inputs can introduce up-to-30 s skew. Bumping the assertion from diff < 50 ms to something like diff < 1_000 ms (or using differenceInMinutes === 0) prevents rare flake without sacrificing the random-input guarantee.

Let me know if you’d like me to adjust the tolerance or add seeding; otherwise I’m happy to leave the current approach as is.

Comment on lines +541 to +552
const tenMinutesFromNow = addMinutes(new Date(), 10);
const approvalResponse = await api.POST(
"/v1/deployment-versions/{deploymentVersionId}/approve",
{
params: {
path: {
deploymentVersionId: deploymentVersion.id,
},
},
body: { approvedAt: tenMinutesFromNow.toISOString() },
},
);
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

Possible race: rule engine may not evaluate before assertion

Right after sending the approval you immediately fetch rollout info.
If rollout evaluation is asynchronous, the rollout times could still be null, causing intermittent failures.

Add a poll/wait helper until rollout information is populated:

-const rolloutResponse = await api.GET(
+const rolloutResponse = await api.waitFor(() =>
+  api.GET(
     `/v1/deployment-versions/{deploymentVersionId}/environments/{environmentId}/rollout`,
     /* params */
-);
+  ),
+  { timeout: 30_000, interval: 1_000 },
+);

(or use Playwright’s expect.poll)

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In e2e/tests/api/policies/environment-version-rollout-policy.spec.ts around
lines 541 to 552, the test immediately fetches rollout info after sending
approval, which may cause intermittent failures if the rollout evaluation is
asynchronous and not completed yet. To fix this, implement a polling or waiting
mechanism that repeatedly checks the rollout information until it is populated
before proceeding with assertions. You can use a custom poll/wait helper or
Playwright’s expect.poll to wait for the rollout times to be non-null.

Comment on lines 246 to 252
"type": "string"
},
"approvedAt": {
"type": "string",
"format": "date-time",
"nullable": true
}
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

Missing security requirement for approveDeploymentVersion
The POST /v1/deployment-versions/{deploymentVersionId}/approve operation must include a security section to enforce bearer authentication.
Apply:

  "post": {
+   "security": [{"bearerAuth": []}],
    "summary": "Approve a deployment version",
    ...

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In openapi.v1.json around lines 246 to 252, the POST operation for
/v1/deployment-versions/{deploymentVersionId}/approve is missing a security
section. Add a security field specifying bearer authentication to enforce
security on this endpoint. This involves adding a "security" array with the
appropriate bearerAuth scheme to the operation object.

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: 3

♻️ Duplicate comments (1)
packages/rule-engine/src/manager/version-manager-rules/environment-version-rollout.ts (1)

46-54: Still possible runtime crash when no approvedAt found
Same issue flagged previously – latestApprovalRecord can be undefined if every record’s approvedAt is null, causing .approvedAt access to explode.

-    const latestApprovalRecord = _.chain(allApprovalRecords)
-      .filter((record) => record.approvedAt != null)
-      .maxBy((record) => record.approvedAt)
-      .value();
-
-    return latestApprovalRecord.approvedAt ?? version.createdAt;
+    const latestApprovalRecord = _.chain(allApprovalRecords)
+      .filter((r) => r.approvedAt != null)
+      .maxBy((r) => r.approvedAt)
+      .value();
+
+    return latestApprovalRecord?.approvedAt ?? version.createdAt;
🧹 Nitpick comments (1)
packages/db/drizzle/0112_steady_proteus.sql (1)

6-9: Add DB-level safeguards for invalid rollout parameters

position_growth_factor and time_scale_interval can be zero / negative, which later throws in application code. Capture this earlier with check constraints:

ALTER TABLE "policy_rule_environment_version_rollout"
  ADD CONSTRAINT position_growth_factor_positive
    CHECK (position_growth_factor > 0),
  ADD CONSTRAINT time_scale_interval_positive
    CHECK (time_scale_interval > 0);
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5d5b9cb and 801d36c.

📒 Files selected for processing (11)
  • apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/_components/PolicyFormContext.tsx (2 hunks)
  • e2e/tests/api/policies/environment-version-rollout-policy.spec.ts (1 hunks)
  • packages/api/src/router/policy/router.ts (1 hunks)
  • packages/db/drizzle/0112_steady_proteus.sql (1 hunks)
  • packages/db/drizzle/meta/_journal.json (1 hunks)
  • packages/db/src/schema/rules/environment-version-rollout.ts (1 hunks)
  • packages/rule-engine/src/db/create-policy.ts (3 hunks)
  • packages/rule-engine/src/db/get-applicable-policies.ts (3 hunks)
  • packages/rule-engine/src/db/update-policy.ts (4 hunks)
  • packages/rule-engine/src/manager/version-manager-rules/environment-version-rollout.ts (1 hunks)
  • packages/rule-engine/src/rules/environment-version-rollout-rule.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/api/src/router/policy/router.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • packages/rule-engine/src/db/get-applicable-policies.ts
  • packages/db/drizzle/meta/_journal.json
  • packages/rule-engine/src/db/create-policy.ts
  • packages/rule-engine/src/rules/environment-version-rollout-rule.ts
  • packages/rule-engine/src/db/update-policy.ts
  • packages/db/src/schema/rules/environment-version-rollout.ts
  • e2e/tests/api/policies/environment-version-rollout-policy.spec.ts
🧰 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/[workspaceSlug]/(app)/policies/[policyId]/edit/_components/PolicyFormContext.tsx
  • packages/rule-engine/src/manager/version-manager-rules/environment-version-rollout.ts
🧬 Code Graph Analysis (1)
apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/_components/PolicyFormContext.tsx (1)
packages/db/src/schema/rules/environment-version-rollout.ts (1)
  • PolicyRuleEnvironmentVersionRollout (71-72)
⏰ 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: Lint
  • GitHub Check: Typecheck
  • GitHub Check: build (linux/amd64)
🔇 Additional comments (1)
apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/_components/PolicyFormContext.tsx (1)

27-28: Addition looks good but check UpdatePolicy schema

Policy now carries environmentVersionRollout, 👍. Ensure SCHEMA.updatePolicy includes the same field; otherwise RHF type inference will complain at runtime.

Comment on lines +57 to +75
const getNumReleaseTargets = async (db: Tx, releaseTargetId: string) => {
const releaseTarget = await db
.select()
.from(schema.releaseTarget)
.where(eq(schema.releaseTarget.id, releaseTargetId))
.then(takeFirst);

return db
.select({ count: count() })
.from(schema.releaseTarget)
.where(
and(
eq(schema.releaseTarget.deploymentId, releaseTarget.deploymentId),
eq(schema.releaseTarget.environmentId, releaseTarget.environmentId),
),
)
.then(takeFirst)
.then((r) => r.count);
};
Copy link
Contributor

@coderabbitai coderabbitai bot Jun 13, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Null-check releaseTarget before dereference

releaseTarget may be null, in which case releaseTarget.deploymentId will throw. Align with the explicit error handling used elsewhere.

-  const releaseTarget = await db
-    .select()
-    .from(schema.releaseTarget)
-    .where(eq(schema.releaseTarget.id, releaseTargetId))
-    .then(takeFirst);
-
-  return db
+  const releaseTarget = await db
+    .select()
+    .from(schema.releaseTarget)
+    .where(eq(schema.releaseTarget.id, releaseTargetId))
+    .then(takeFirst);
+
+  if (releaseTarget == null)
+    throw new Error(`Release target ${releaseTargetId} not found`);
+
+  return db
📝 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 getNumReleaseTargets = async (db: Tx, releaseTargetId: string) => {
const releaseTarget = await db
.select()
.from(schema.releaseTarget)
.where(eq(schema.releaseTarget.id, releaseTargetId))
.then(takeFirst);
return db
.select({ count: count() })
.from(schema.releaseTarget)
.where(
and(
eq(schema.releaseTarget.deploymentId, releaseTarget.deploymentId),
eq(schema.releaseTarget.environmentId, releaseTarget.environmentId),
),
)
.then(takeFirst)
.then((r) => r.count);
};
const getNumReleaseTargets = async (db: Tx, releaseTargetId: string) => {
const releaseTarget = await db
.select()
.from(schema.releaseTarget)
.where(eq(schema.releaseTarget.id, releaseTargetId))
.then(takeFirst);
if (releaseTarget == null)
throw new Error(`Release target ${releaseTargetId} not found`);
return db
.select({ count: count() })
.from(schema.releaseTarget)
.where(
and(
eq(schema.releaseTarget.deploymentId, releaseTarget.deploymentId),
eq(schema.releaseTarget.environmentId, releaseTarget.environmentId),
),
)
.then(takeFirst)
.then((r) => r.count);
};
🤖 Prompt for AI Agents
In
packages/rule-engine/src/manager/version-manager-rules/environment-version-rollout.ts
around lines 57 to 75, the code dereferences releaseTarget without checking if
it is null, which can cause a runtime error. Add a null check after fetching
releaseTarget; if it is null, throw an explicit error similar to other parts of
the codebase. This prevents dereferencing null and aligns with existing error
handling practices.

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 utility function throws error if null

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—takeFirst already guarantees a non-null result (or throws), so the extra null-check I suggested is redundant. Thanks for pointing that out, and sorry for the noise! I’ll retract the suggestion.
(I had this behavior stored in my notes from an earlier PR; seems I overlooked it here.)

(\_/)
(•.•)  ?
/ >🍃   – Appreciative bunny pondering the reminder

🧠 Learnings used
Learnt from: adityachoudhari26
PR: ctrlplanedev/ctrlplane#395
File: packages/api/src/router/environment-page/resources/router.ts:40-45
Timestamp: 2025-03-24T18:46:38.894Z
Learning: The `takeFirst` utility function in the codebase (from `@ctrlplane/db`) throws an Error with message "Found non unique or inexistent value" if the result array doesn't contain exactly one element, making additional null/undefined checks unnecessary after its use.

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: 1

♻️ Duplicate comments (1)
packages/rule-engine/src/manager/version-manager-rules/environment-version-rollout.ts (1)

46-54: ⚠️ Potential issue

Possible runtime crash when no approvedAt values – optional-chaining missing

latestApprovalRecord can be undefined (e.g. all records have approvedAt = null).
Dereferencing it will throw before the nullish-coalescing fallback kicks in.

-    return latestApprovalRecord.approvedAt ?? version.createdAt;
+    return latestApprovalRecord?.approvedAt ?? version.createdAt;
🧹 Nitpick comments (3)
packages/rule-engine/src/manager/version-manager-rules/environment-version-rollout.ts (3)

40-44: Fetch approval records in parallel for ~3× faster latency

The three DB hits run sequentially; with Promise.all we can issue them concurrently and keep the code equally clear.

-    const allApprovalRecords = [
-      ...(await getAnyApprovalRecords([version.id])),
-      ...(await getUserApprovalRecords([version.id])),
-      ...(await getRoleApprovalRecords([version.id])),
-    ];
+    const [any, user, role] = await Promise.all([
+      getAnyApprovalRecords([version.id]),
+      getUserApprovalRecords([version.id]),
+      getRoleApprovalRecords([version.id]),
+    ]);
+    const allApprovalRecords = [...any, ...user, ...role];

28-35: Prefer an explicit for…of loop over async-reduce for clarity

Array.reduce with async callbacks is hard to read and accidentally easy to misuse.
The intent (“break on first failure”) is clearer with a simple loop:

let allApprovalsPassed = true;
for (const rule of versionApprovalRules) {
  const { allowedCandidates } = await rule.filter([version]);
  if (allowedCandidates.length === 0) {
    allApprovalsPassed = false;
    break;
  }
}

182-186: numReleaseTargets is recalculated per release-target – cache once

getNumReleaseTargets is already executed earlier when building the rule.
Calling it again here adds an extra query per target and can dominate rollout listing time in large deployments.
Consider passing the earlier value down or memoising the helper.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 801d36c and cd18d50.

📒 Files selected for processing (2)
  • apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/_components/PolicyFormContext.tsx (2 hunks)
  • packages/rule-engine/src/manager/version-manager-rules/environment-version-rollout.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/_components/PolicyFormContext.tsx
🧰 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.

  • packages/rule-engine/src/manager/version-manager-rules/environment-version-rollout.ts
🧠 Learnings (1)
packages/rule-engine/src/manager/version-manager-rules/environment-version-rollout.ts (1)
Learnt from: adityachoudhari26
PR: ctrlplanedev/ctrlplane#395
File: packages/api/src/router/environment-page/resources/router.ts:40-45
Timestamp: 2025-03-24T18:46:38.894Z
Learning: The `takeFirst` utility function in the codebase (from `@ctrlplane/db`) throws an Error with message "Found non unique or inexistent value" if the result array doesn't contain exactly one element, making additional null/undefined checks unnecessary after its use.
⏰ Context from checks skipped due to timeout of 90000ms (6)
  • GitHub Check: build (linux/amd64)
  • GitHub Check: build (linux/amd64)
  • GitHub Check: Typecheck
  • GitHub Check: build (linux/amd64)
  • GitHub Check: Lint
  • GitHub Check: build (linux/amd64)

@adityachoudhari26 adityachoudhari26 merged commit 0b1e064 into main Jun 13, 2025
10 checks passed
@adityachoudhari26 adityachoudhari26 deleted the grad-rollouts-v2 branch June 13, 2025 18:23
@coderabbitai coderabbitai bot mentioned this pull request Jun 20, 2025
@coderabbitai coderabbitai bot mentioned this pull request Jul 31, 2025
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