Skip to content

chore: implement policy getter in svc#822

Open
adityachoudhari26 wants to merge 1 commit intomainfrom
svc-policy-db
Open

chore: implement policy getter in svc#822
adityachoudhari26 wants to merge 1 commit intomainfrom
svc-policy-db

Conversation

@adityachoudhari26
Copy link
Member

@adityachoudhari26 adityachoudhari26 commented Mar 2, 2026

Summary by CodeRabbit

  • New Features
    • Policies can now retrieve all associated governance rules together when queried by workspace, providing streamlined and unified access to complete policy configurations.
    • GetPolicies method is now fully implemented and operational, returning complete policy objects with all rule types, their configurations, and details.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 2, 2026

📝 Walkthrough

Walkthrough

Introduces a new database query ListPoliciesWithRulesByWorkspaceID that retrieves policies with their aggregated associated rules as JSON, including corresponding Go types and a conversion function. Implements the GetPolicies method in PostgresGetter to fetch and convert policies from the database to API models.

Changes

Cohort / File(s) Summary
Database Layer
apps/workspace-engine/pkg/db/queries/policies.sql, apps/workspace-engine/pkg/db/policies.sql.go
New SQL query and generated Go types for ListPoliciesWithRulesByWorkspaceID that fetches policies with aggregated rule sets (10 rule categories) as JSONB fields for a given workspace.
Conversion Logic
apps/workspace-engine/svc/controllers/desiredrelease/convert/policy.go
Conversion function PolicyWithRules that transforms database rows into oapi.Policy structures, including individual parsers for each of the 10 rule types and JSON unmarshaling with time and selector formatting.
Tests
apps/workspace-engine/svc/controllers/desiredrelease/convert/policy_test.go
Comprehensive test suite validating PolicyWithRules conversion across all rule types, edge cases (nullable fields, malformed JSON), multiple rule instances, and error handling.
Integration
apps/workspace-engine/svc/controllers/desiredrelease/getters_postgres.go
Implementation of GetPolicies method to fetch deployment, list policies with rules from database, convert each row via PolicyWithRules, and return assembled policy slice with error wrapping.

Sequence Diagram

sequenceDiagram
    participant Client
    participant PostgresGetter
    participant Database
    participant Converter
    participant API

    Client->>PostgresGetter: GetPolicies(ctx, ReleaseTarget)
    PostgresGetter->>Database: ListPoliciesWithRulesByWorkspaceID(ctx, workspaceID)
    Database-->>PostgresGetter: []ListPoliciesWithRulesByWorkspaceIDRow
    loop For each policy row
        PostgresGetter->>Converter: PolicyWithRules(row)
        Note over Converter: Parse 10 rule types from JSON
        Note over Converter: Convert to oapi.Policy structure
        Converter-->>PostgresGetter: *oapi.Policy
    end
    PostgresGetter-->>Client: []*oapi.Policy
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related PRs

  • PR #813: Introduces DB-backed policy models and queries for retrieving policies with their rule sets from the database.
  • PR #794: Consolidates selectors into a unified field and updates policy evaluation logic that directly impacts the selector conversion in this PR.
  • PR #706: Adds support for the GradualRollout policy rule type that is parsed and converted by the new conversion logic.

Poem

🐰 Hop, query, convert, and compile!
Eleven rules bundled in JSONB style,
One fetch now gathers them all in a row,
From database burrows to API flow!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'chore: implement policy getter in svc' accurately describes the main change—implementing the GetPolicies method in the service layer with database-backed functionality.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch svc-policy-db

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

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

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

🧹 Nitpick comments (3)
apps/workspace-engine/svc/controllers/desiredrelease/convert/policy.go (2)

109-109: Add a doc comment for exported PolicyWithRules.

Please add a short Go doc comment that explains intent/contract for this exported converter.

As per coding guidelines, "Write comments that explain why, document complex logic/algorithms, provide non-obvious context, include TODO/FIXME, and document exported functions/types/methods".

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

In `@apps/workspace-engine/svc/controllers/desiredrelease/convert/policy.go` at
line 109, Add a Go doc comment for the exported function PolicyWithRules that
briefly explains what the function converts and its contract: describe that
PolicyWithRules takes a db.ListPoliciesWithRulesByWorkspaceIDRow (database row
containing policy and associated rules) and returns an OpenAPI policy
representation (*oapi.Policy) or an error, including any important behaviors
(e.g., when it returns nil vs an error, whether it copies/normalizes fields, and
that it expects the row to include rules for the workspace). Place the comment
immediately above the PolicyWithRules function declaration.

134-195: Wrap parser errors with rule-type context.

Right now malformed JSON surfaces as a generic unmarshal error. Add field-specific context to speed up debugging.

🛠️ Suggested change
 import (
 	"encoding/json"
+	"fmt"
 	"time"

 	"workspace-engine/pkg/db"
 	"workspace-engine/pkg/oapi"
 )
@@
 	anyApprovals, err := parseAnyApprovalRules(row.AnyApprovalRules)
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("parse any_approval_rules: %w", err)
 	}
@@
 	depDeps, err := parseDeploymentDependencyRules(row.DeploymentDependencyRules)
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("parse deployment_dependency_rules: %w", err)
 	}
@@
 	windows, err := parseDeploymentWindowRules(row.DeploymentWindowRules)
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("parse deployment_window_rules: %w", err)
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/workspace-engine/svc/controllers/desiredrelease/convert/policy.go`
around lines 134 - 195, parseAllRules currently returns raw unmarshal errors
from each specific parser (e.g., parseAnyApprovalRules,
parseDeploymentDependencyRules, parseDeploymentWindowRules,
parseEnvironmentProgressionRules, parseGradualRolloutRules, parseRetryRules,
parseRollbackRules, parseVerificationRules, parseVersionCooldownRules,
parseVersionSelectorRules) which makes it hard to know which rule type failed;
update parseAllRules so that whenever a parser call returns an error you wrap it
with contextual information naming the rule type and source field (for example
fmt.Errorf("parseAnyApprovalRules (AnyApprovalRules): %w", err)) and propagate
that wrapped error, ensuring each error return includes the parser function name
and the corresponding row field to improve debuggability.
apps/workspace-engine/svc/controllers/desiredrelease/getters_postgres.go (1)

73-76: Add identifying context to the list-policies error.

Including deployment_id and/or workspace_id in this error message makes production triage faster.

🔧 Suggested tweak
  rows, err := q.ListPoliciesWithRulesByWorkspaceID(ctx, depRow.WorkspaceID)
  if err != nil {
-     return nil, fmt.Errorf("list policies: %w", err)
+     return nil, fmt.Errorf("list policies for workspace %s (deployment %s): %w", depRow.WorkspaceID, rt.DeploymentID, err)
  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/workspace-engine/svc/controllers/desiredrelease/getters_postgres.go`
around lines 73 - 76, The error returned from calling
ListPoliciesWithRulesByWorkspaceID lacks identifying context; update the error
returned in the block where rows, err :=
q.ListPoliciesWithRulesByWorkspaceID(ctx, depRow.WorkspaceID) is handled to
include depRow.WorkspaceID and depRow.ID (or deployment_id) in the message—e.g.,
wrap the original error with fmt.Errorf("list policies workspace_id=%s
deployment_id=%s: %w", depRow.WorkspaceID, depRow.ID, err) so the log includes
both workspace and deployment identifiers; ensure you reference
ListPoliciesWithRulesByWorkspaceID, depRow.WorkspaceID and depRow.ID (or the
field name used for deployment id) when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/workspace-engine/pkg/db/queries/policies.sql`:
- Around line 223-242: The JSON aggregate subqueries (e.g., any_approval_rules /
policy_rule_any_approval, deployment_dependency_rules /
policy_rule_deployment_dependency, deployment_window_rules /
policy_rule_deployment_window, environment_progression_rules /
policy_rule_environment_progression, gradual_rollout_rules /
policy_rule_gradual_rollout, retry_rules / policy_rule_retry, rollback_rules /
policy_rule_rollback, verification_rules / policy_rule_verification,
version_cooldown_rules / policy_rule_version_cooldown, version_selector_rules /
policy_rule_version_selector) are using json_agg(...) without ORDER BY so their
element order is non-deterministic; fix each subquery by adding a deterministic
ORDER BY (for example ORDER BY r.id ASC or ORDER BY r.created_at ASC) inside the
SELECT ... FROM ... WHERE r.policy_id = p.id subquery so json_agg produces a
stable ordering for API responses.

---

Nitpick comments:
In `@apps/workspace-engine/svc/controllers/desiredrelease/convert/policy.go`:
- Line 109: Add a Go doc comment for the exported function PolicyWithRules that
briefly explains what the function converts and its contract: describe that
PolicyWithRules takes a db.ListPoliciesWithRulesByWorkspaceIDRow (database row
containing policy and associated rules) and returns an OpenAPI policy
representation (*oapi.Policy) or an error, including any important behaviors
(e.g., when it returns nil vs an error, whether it copies/normalizes fields, and
that it expects the row to include rules for the workspace). Place the comment
immediately above the PolicyWithRules function declaration.
- Around line 134-195: parseAllRules currently returns raw unmarshal errors from
each specific parser (e.g., parseAnyApprovalRules,
parseDeploymentDependencyRules, parseDeploymentWindowRules,
parseEnvironmentProgressionRules, parseGradualRolloutRules, parseRetryRules,
parseRollbackRules, parseVerificationRules, parseVersionCooldownRules,
parseVersionSelectorRules) which makes it hard to know which rule type failed;
update parseAllRules so that whenever a parser call returns an error you wrap it
with contextual information naming the rule type and source field (for example
fmt.Errorf("parseAnyApprovalRules (AnyApprovalRules): %w", err)) and propagate
that wrapped error, ensuring each error return includes the parser function name
and the corresponding row field to improve debuggability.

In `@apps/workspace-engine/svc/controllers/desiredrelease/getters_postgres.go`:
- Around line 73-76: The error returned from calling
ListPoliciesWithRulesByWorkspaceID lacks identifying context; update the error
returned in the block where rows, err :=
q.ListPoliciesWithRulesByWorkspaceID(ctx, depRow.WorkspaceID) is handled to
include depRow.WorkspaceID and depRow.ID (or deployment_id) in the message—e.g.,
wrap the original error with fmt.Errorf("list policies workspace_id=%s
deployment_id=%s: %w", depRow.WorkspaceID, depRow.ID, err) so the log includes
both workspace and deployment identifiers; ensure you reference
ListPoliciesWithRulesByWorkspaceID, depRow.WorkspaceID and depRow.ID (or the
field name used for deployment id) when making the change.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6e2ec4f and 18259a8.

📒 Files selected for processing (5)
  • apps/workspace-engine/pkg/db/policies.sql.go
  • apps/workspace-engine/pkg/db/queries/policies.sql
  • apps/workspace-engine/svc/controllers/desiredrelease/convert/policy.go
  • apps/workspace-engine/svc/controllers/desiredrelease/convert/policy_test.go
  • apps/workspace-engine/svc/controllers/desiredrelease/getters_postgres.go

Comment on lines +223 to +242
COALESCE((SELECT json_agg(json_build_object('id', r.id, 'policy_id', r.policy_id, 'min_approvals', r.min_approvals, 'created_at', r.created_at))
FROM policy_rule_any_approval r WHERE r.policy_id = p.id), '[]')::jsonb AS any_approval_rules,
COALESCE((SELECT json_agg(json_build_object('id', r.id, 'policy_id', r.policy_id, 'depends_on', r.depends_on, 'created_at', r.created_at))
FROM policy_rule_deployment_dependency r WHERE r.policy_id = p.id), '[]')::jsonb AS deployment_dependency_rules,
COALESCE((SELECT json_agg(json_build_object('id', r.id, 'policy_id', r.policy_id, 'allow_window', r.allow_window, 'duration_minutes', r.duration_minutes, 'rrule', r.rrule, 'timezone', r.timezone, 'created_at', r.created_at))
FROM policy_rule_deployment_window r WHERE r.policy_id = p.id), '[]')::jsonb AS deployment_window_rules,
COALESCE((SELECT json_agg(json_build_object('id', r.id, 'policy_id', r.policy_id, 'depends_on_environment_selector', r.depends_on_environment_selector, 'maximum_age_hours', r.maximum_age_hours, 'minimum_soak_time_minutes', r.minimum_soak_time_minutes, 'minimum_success_percentage', r.minimum_success_percentage, 'success_statuses', r.success_statuses, 'created_at', r.created_at))
FROM policy_rule_environment_progression r WHERE r.policy_id = p.id), '[]')::jsonb AS environment_progression_rules,
COALESCE((SELECT json_agg(json_build_object('id', r.id, 'policy_id', r.policy_id, 'rollout_type', r.rollout_type, 'time_scale_interval', r.time_scale_interval, 'created_at', r.created_at))
FROM policy_rule_gradual_rollout r WHERE r.policy_id = p.id), '[]')::jsonb AS gradual_rollout_rules,
COALESCE((SELECT json_agg(json_build_object('id', r.id, 'policy_id', r.policy_id, 'max_retries', r.max_retries, 'backoff_seconds', r.backoff_seconds, 'backoff_strategy', r.backoff_strategy, 'max_backoff_seconds', r.max_backoff_seconds, 'retry_on_statuses', r.retry_on_statuses, 'created_at', r.created_at))
FROM policy_rule_retry r WHERE r.policy_id = p.id), '[]')::jsonb AS retry_rules,
COALESCE((SELECT json_agg(json_build_object('id', r.id, 'policy_id', r.policy_id, 'on_job_statuses', r.on_job_statuses, 'on_verification_failure', r.on_verification_failure, 'created_at', r.created_at))
FROM policy_rule_rollback r WHERE r.policy_id = p.id), '[]')::jsonb AS rollback_rules,
COALESCE((SELECT json_agg(json_build_object('id', r.id, 'policy_id', r.policy_id, 'metrics', r.metrics, 'trigger_on', r.trigger_on, 'created_at', r.created_at))
FROM policy_rule_verification r WHERE r.policy_id = p.id), '[]')::jsonb AS verification_rules,
COALESCE((SELECT json_agg(json_build_object('id', r.id, 'policy_id', r.policy_id, 'interval_seconds', r.interval_seconds, 'created_at', r.created_at))
FROM policy_rule_version_cooldown r WHERE r.policy_id = p.id), '[]')::jsonb AS version_cooldown_rules,
COALESCE((SELECT json_agg(json_build_object('id', r.id, 'policy_id', r.policy_id, 'description', r.description, 'selector', r.selector, 'created_at', r.created_at))
FROM policy_rule_version_selector r WHERE r.policy_id = p.id), '[]')::jsonb AS version_selector_rules
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 | 🟡 Minor

Make aggregated rule array ordering deterministic.

Each json_agg(...) here has no ORDER BY, so element order is not guaranteed. That can make API responses unstable across runs.

💡 Suggested pattern (apply to each rule subquery)
-  COALESCE((SELECT json_agg(json_build_object('id', r.id, 'policy_id', r.policy_id, 'min_approvals', r.min_approvals, 'created_at', r.created_at))
+  COALESCE((SELECT json_agg(
+      json_build_object('id', r.id, 'policy_id', r.policy_id, 'min_approvals', r.min_approvals, 'created_at', r.created_at)
+      ORDER BY r.created_at, r.id
+    )
     FROM policy_rule_any_approval r WHERE r.policy_id = p.id), '[]')::jsonb AS any_approval_rules,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/workspace-engine/pkg/db/queries/policies.sql` around lines 223 - 242,
The JSON aggregate subqueries (e.g., any_approval_rules /
policy_rule_any_approval, deployment_dependency_rules /
policy_rule_deployment_dependency, deployment_window_rules /
policy_rule_deployment_window, environment_progression_rules /
policy_rule_environment_progression, gradual_rollout_rules /
policy_rule_gradual_rollout, retry_rules / policy_rule_retry, rollback_rules /
policy_rule_rollback, verification_rules / policy_rule_verification,
version_cooldown_rules / policy_rule_version_cooldown, version_selector_rules /
policy_rule_version_selector) are using json_agg(...) without ORDER BY so their
element order is non-deterministic; fix each subquery by adding a deterministic
ORDER BY (for example ORDER BY r.id ASC or ORDER BY r.created_at ASC) inside the
SELECT ... FROM ... WHERE r.policy_id = p.id subquery so json_agg produces a
stable ordering for API responses.

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.

1 participant