diff --git a/apps/web/app/routes/ws/settings/_components/work-queue/CreateWorkItemDialog.tsx b/apps/web/app/routes/ws/settings/_components/work-queue/CreateWorkItemDialog.tsx
index bf3921497..3a4537d1c 100644
--- a/apps/web/app/routes/ws/settings/_components/work-queue/CreateWorkItemDialog.tsx
+++ b/apps/web/app/routes/ws/settings/_components/work-queue/CreateWorkItemDialog.tsx
@@ -31,6 +31,7 @@ type Template =
| "environment-selector"
| "relationship-entity"
| "relationship-rule"
+ | "policy-summary"
| "advanced";
const TEMPLATES: { value: Template; label: string; description: string }[] = [
@@ -57,6 +58,12 @@ const TEMPLATES: { value: Template; label: string; description: string }[] = [
description:
"Re-evaluate relationships for all entities in the workspace. Useful after changing a rule.",
},
+ {
+ value: "policy-summary",
+ label: "Policy Summary Eval",
+ description:
+ "Re-evaluate policy summaries for an environment + version pair.",
+ },
{
value: "advanced",
label: "Advanced",
@@ -307,6 +314,63 @@ function RelationshipRuleForm({
);
}
+function PolicySummaryForm({
+ workspaceId,
+ onDone,
+}: {
+ workspaceId: string;
+ onDone: () => void;
+}) {
+ const [environmentId, setEnvironmentId] = useState("");
+ const [versionId, setVersionId] = useState("");
+ const invalidate = useInvalidateAll();
+ const mutation = trpc.reconcile.triggerPolicySummary.useMutation({
+ onSuccess: () => {
+ invalidate();
+ onDone();
+ },
+ });
+
+ return (
+
+ );
+}
+
function AdvancedForm({
workspaceId,
onDone,
@@ -579,6 +643,9 @@ export const CreateWorkItemDialog: React.FC<{
{template === "relationship-rule" && (
)}
+ {template === "policy-summary" && (
+
+ )}
{template === "advanced" && (
)}
diff --git a/apps/workspace-engine/main.go b/apps/workspace-engine/main.go
index 0be0103f4..76b36cd6b 100644
--- a/apps/workspace-engine/main.go
+++ b/apps/workspace-engine/main.go
@@ -14,6 +14,7 @@ import (
"workspace-engine/svc/controllers/environmentresourceselectoreval"
"workspace-engine/svc/controllers/jobdispatch"
"workspace-engine/svc/controllers/jobverificationmetric"
+ "workspace-engine/svc/controllers/policysummary"
"workspace-engine/svc/controllers/relationshipeval"
httpsvc "workspace-engine/svc/http"
"workspace-engine/svc/routerregistrar"
@@ -67,6 +68,7 @@ func main() {
jobverificationmetric.New(WorkerID, db.GetPool(ctx)),
relationshipeval.New(WorkerID, db.GetPool(ctx)),
desiredrelease.New(WorkerID, db.GetPool(ctx)),
+ policysummary.New(WorkerID, db.GetPool(ctx)),
)
if err := runner.Run(ctx); err != nil {
diff --git a/apps/workspace-engine/pkg/db/batch.go b/apps/workspace-engine/pkg/db/batch.go
index c44acb790..17871f922 100644
--- a/apps/workspace-engine/pkg/db/batch.go
+++ b/apps/workspace-engine/pkg/db/batch.go
@@ -205,3 +205,90 @@ func (b *UpsertChangelogEntryBatchResults) Close() error {
b.closed = true
return b.br.Close()
}
+
+const upsertPolicyRuleSummary = `-- name: UpsertPolicyRuleSummary :batchexec
+INSERT INTO policy_rule_summary (
+ id, rule_id,
+ environment_id, version_id,
+ allowed, action_required, action_type,
+ message, details,
+ satisfied_at, next_evaluation_at, evaluated_at
+)
+VALUES (
+ gen_random_uuid(), $1,
+ $2, $3,
+ $4, $5, $6,
+ $7, $8,
+ $9, $10, NOW()
+)
+ON CONFLICT (rule_id, environment_id, version_id) DO UPDATE
+SET allowed = EXCLUDED.allowed,
+ action_required = EXCLUDED.action_required,
+ action_type = EXCLUDED.action_type,
+ message = EXCLUDED.message,
+ details = EXCLUDED.details,
+ satisfied_at = EXCLUDED.satisfied_at,
+ next_evaluation_at = EXCLUDED.next_evaluation_at,
+ evaluated_at = NOW()
+`
+
+type UpsertPolicyRuleSummaryBatchResults struct {
+ br pgx.BatchResults
+ tot int
+ closed bool
+}
+
+type UpsertPolicyRuleSummaryParams struct {
+ RuleID uuid.UUID
+ EnvironmentID uuid.UUID
+ VersionID uuid.UUID
+ Allowed bool
+ ActionRequired bool
+ ActionType pgtype.Text
+ Message string
+ Details map[string]any
+ SatisfiedAt pgtype.Timestamptz
+ NextEvaluationAt pgtype.Timestamptz
+}
+
+func (q *Queries) UpsertPolicyRuleSummary(ctx context.Context, arg []UpsertPolicyRuleSummaryParams) *UpsertPolicyRuleSummaryBatchResults {
+ batch := &pgx.Batch{}
+ for _, a := range arg {
+ vals := []interface{}{
+ a.RuleID,
+ a.EnvironmentID,
+ a.VersionID,
+ a.Allowed,
+ a.ActionRequired,
+ a.ActionType,
+ a.Message,
+ a.Details,
+ a.SatisfiedAt,
+ a.NextEvaluationAt,
+ }
+ batch.Queue(upsertPolicyRuleSummary, vals...)
+ }
+ br := q.db.SendBatch(ctx, batch)
+ return &UpsertPolicyRuleSummaryBatchResults{br, len(arg), false}
+}
+
+func (b *UpsertPolicyRuleSummaryBatchResults) Exec(f func(int, error)) {
+ defer b.br.Close()
+ for t := 0; t < b.tot; t++ {
+ if b.closed {
+ if f != nil {
+ f(t, ErrBatchAlreadyClosed)
+ }
+ continue
+ }
+ _, err := b.br.Exec()
+ if f != nil {
+ f(t, err)
+ }
+ }
+}
+
+func (b *UpsertPolicyRuleSummaryBatchResults) Close() error {
+ b.closed = true
+ return b.br.Close()
+}
diff --git a/apps/workspace-engine/pkg/db/computed_resources.sql.go b/apps/workspace-engine/pkg/db/computed_resources.sql.go
index a7d169eaa..755a8e38d 100644
--- a/apps/workspace-engine/pkg/db/computed_resources.sql.go
+++ b/apps/workspace-engine/pkg/db/computed_resources.sql.go
@@ -55,6 +55,50 @@ func (q *Queries) GetReleaseTargetsForDeployment(ctx context.Context, deployment
return items, nil
}
+const getReleaseTargetsForEnvironment = `-- name: GetReleaseTargetsForEnvironment :many
+SELECT DISTINCT
+ cdr.deployment_id,
+ cer.environment_id,
+ cdr.resource_id
+FROM computed_deployment_resource cdr
+JOIN computed_environment_resource cer
+ ON cer.resource_id = cdr.resource_id
+JOIN system_deployment sd
+ ON sd.deployment_id = cdr.deployment_id
+JOIN system_environment se
+ ON se.environment_id = cer.environment_id
+ AND se.system_id = sd.system_id
+WHERE cer.environment_id = $1
+`
+
+type GetReleaseTargetsForEnvironmentRow struct {
+ DeploymentID uuid.UUID
+ EnvironmentID uuid.UUID
+ ResourceID uuid.UUID
+}
+
+// Returns all valid release targets for an environment by joining computed
+// resource tables through the system link tables.
+func (q *Queries) GetReleaseTargetsForEnvironment(ctx context.Context, environmentID uuid.UUID) ([]GetReleaseTargetsForEnvironmentRow, error) {
+ rows, err := q.db.Query(ctx, getReleaseTargetsForEnvironment, environmentID)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+ var items []GetReleaseTargetsForEnvironmentRow
+ for rows.Next() {
+ var i GetReleaseTargetsForEnvironmentRow
+ if err := rows.Scan(&i.DeploymentID, &i.EnvironmentID, &i.ResourceID); err != nil {
+ return nil, err
+ }
+ items = append(items, i)
+ }
+ if err := rows.Err(); err != nil {
+ return nil, err
+ }
+ return items, nil
+}
+
const getReleaseTargetsForResource = `-- name: GetReleaseTargetsForResource :many
SELECT DISTINCT
cdr.deployment_id,
diff --git a/apps/workspace-engine/pkg/db/convert.go b/apps/workspace-engine/pkg/db/convert.go
index 892709408..3770af7e7 100644
--- a/apps/workspace-engine/pkg/db/convert.go
+++ b/apps/workspace-engine/pkg/db/convert.go
@@ -69,6 +69,163 @@ func ToOapiResource(row GetResourceByIDRow) *oapi.Resource {
return r
}
+func ToOapiPolicyWithRules(row ListPoliciesWithRulesByWorkspaceIDRow) *oapi.Policy {
+ p := ToOapiPolicy(Policy{
+ ID: row.ID,
+ Name: row.Name,
+ Description: row.Description,
+ Selector: row.Selector,
+ Metadata: row.Metadata,
+ Priority: row.Priority,
+ Enabled: row.Enabled,
+ WorkspaceID: row.WorkspaceID,
+ CreatedAt: row.CreatedAt,
+ })
+
+ type approvalJSON struct {
+ Id string `json:"id"`
+ MinApprovals int32 `json:"minApprovals"`
+ }
+ var approvals []approvalJSON
+ _ = json.Unmarshal(row.ApprovalRules, &approvals)
+ for _, a := range approvals {
+ p.Rules = append(p.Rules, oapi.PolicyRule{
+ Id: a.Id,
+ PolicyId: p.Id,
+ AnyApproval: &oapi.AnyApprovalRule{
+ MinApprovals: a.MinApprovals,
+ },
+ })
+ }
+
+ type windowJSON struct {
+ Id string `json:"id"`
+ AllowWindow *bool `json:"allowWindow"`
+ DurationMinutes int32 `json:"durationMinutes"`
+ Rrule string `json:"rrule"`
+ Timezone *string `json:"timezone"`
+ }
+ var windows []windowJSON
+ _ = json.Unmarshal(row.DeploymentWindowRules, &windows)
+ for _, w := range windows {
+ p.Rules = append(p.Rules, oapi.PolicyRule{
+ Id: w.Id,
+ PolicyId: p.Id,
+ DeploymentWindow: &oapi.DeploymentWindowRule{
+ AllowWindow: w.AllowWindow,
+ DurationMinutes: w.DurationMinutes,
+ Rrule: w.Rrule,
+ Timezone: w.Timezone,
+ },
+ })
+ }
+
+ type dependencyJSON struct {
+ Id string `json:"id"`
+ DependsOn string `json:"dependsOn"`
+ }
+ var deps []dependencyJSON
+ _ = json.Unmarshal(row.DeploymentDependencyRules, &deps)
+ for _, d := range deps {
+ p.Rules = append(p.Rules, oapi.PolicyRule{
+ Id: d.Id,
+ PolicyId: p.Id,
+ DeploymentDependency: &oapi.DeploymentDependencyRule{
+ DependsOn: d.DependsOn,
+ },
+ })
+ }
+
+ type progressionJSON struct {
+ Id string `json:"id"`
+ DependsOnEnvironmentSelector string `json:"dependsOnEnvironmentSelector"`
+ MaximumAgeHours *int32 `json:"maximumAgeHours"`
+ MinimumSoakTimeMinutes *int32 `json:"minimumSoakTimeMinutes"`
+ MinimumSuccessPercentage *float32 `json:"minimumSuccessPercentage"`
+ SuccessStatuses *[]string `json:"successStatuses"`
+ }
+ var progs []progressionJSON
+ _ = json.Unmarshal(row.EnvironmentProgressionRules, &progs)
+ for _, pr := range progs {
+ var depSelector oapi.Selector
+ _ = json.Unmarshal([]byte(pr.DependsOnEnvironmentSelector), &depSelector)
+ rule := oapi.EnvironmentProgressionRule{
+ DependsOnEnvironmentSelector: depSelector,
+ MaximumAgeHours: pr.MaximumAgeHours,
+ MinimumSockTimeMinutes: pr.MinimumSoakTimeMinutes,
+ MinimumSuccessPercentage: pr.MinimumSuccessPercentage,
+ }
+ if pr.SuccessStatuses != nil {
+ statuses := make([]oapi.JobStatus, len(*pr.SuccessStatuses))
+ for i, s := range *pr.SuccessStatuses {
+ statuses[i] = oapi.JobStatus(s)
+ }
+ rule.SuccessStatuses = &statuses
+ }
+ p.Rules = append(p.Rules, oapi.PolicyRule{
+ Id: pr.Id,
+ PolicyId: p.Id,
+ EnvironmentProgression: &rule,
+ })
+ }
+
+ type rolloutJSON struct {
+ Id string `json:"id"`
+ RolloutType string `json:"rolloutType"`
+ TimeScaleInterval int32 `json:"timeScaleInterval"`
+ }
+ var rollouts []rolloutJSON
+ _ = json.Unmarshal(row.GradualRolloutRules, &rollouts)
+ for _, r := range rollouts {
+ p.Rules = append(p.Rules, oapi.PolicyRule{
+ Id: r.Id,
+ PolicyId: p.Id,
+ GradualRollout: &oapi.GradualRolloutRule{
+ RolloutType: oapi.GradualRolloutRuleRolloutType(r.RolloutType),
+ TimeScaleInterval: r.TimeScaleInterval,
+ },
+ })
+ }
+
+ type cooldownJSON struct {
+ Id string `json:"id"`
+ IntervalSeconds int32 `json:"intervalSeconds"`
+ }
+ var cooldowns []cooldownJSON
+ _ = json.Unmarshal(row.VersionCooldownRules, &cooldowns)
+ for _, c := range cooldowns {
+ p.Rules = append(p.Rules, oapi.PolicyRule{
+ Id: c.Id,
+ PolicyId: p.Id,
+ VersionCooldown: &oapi.VersionCooldownRule{
+ IntervalSeconds: c.IntervalSeconds,
+ },
+ })
+ }
+
+ type selectorJSON struct {
+ Id string `json:"id"`
+ Description *string `json:"description"`
+ Selector string `json:"selector"`
+ }
+ var selectors []selectorJSON
+ _ = json.Unmarshal(row.VersionSelectorRules, &selectors)
+ for _, s := range selectors {
+ var vSelector oapi.Selector
+ _ = json.Unmarshal([]byte(s.Selector), &vSelector)
+ p.Rules = append(p.Rules, oapi.PolicyRule{
+ Id: s.Id,
+ PolicyId: p.Id,
+ VersionSelector: &oapi.VersionSelectorRule{
+ Description: s.Description,
+ Selector: vSelector,
+ },
+ })
+ }
+
+ return p
+}
+
func ToOapiPolicy(row Policy) *oapi.Policy {
p := &oapi.Policy{
Id: row.ID.String(),
diff --git a/apps/workspace-engine/pkg/db/models.go b/apps/workspace-engine/pkg/db/models.go
index 74db64892..60b4a0ac2 100644
--- a/apps/workspace-engine/pkg/db/models.go
+++ b/apps/workspace-engine/pkg/db/models.go
@@ -464,6 +464,21 @@ type PolicyRuleRollback struct {
CreatedAt pgtype.Timestamptz
}
+type PolicyRuleSummary struct {
+ ID uuid.UUID
+ RuleID uuid.UUID
+ EnvironmentID uuid.UUID
+ VersionID uuid.UUID
+ Allowed bool
+ ActionRequired bool
+ ActionType pgtype.Text
+ Message string
+ Details map[string]any
+ SatisfiedAt pgtype.Timestamptz
+ NextEvaluationAt pgtype.Timestamptz
+ EvaluatedAt pgtype.Timestamptz
+}
+
type PolicyRuleVerification struct {
ID uuid.UUID
PolicyID uuid.UUID
diff --git a/apps/workspace-engine/pkg/db/policies.sql.go b/apps/workspace-engine/pkg/db/policies.sql.go
index 33832ab06..bbb503aa4 100644
--- a/apps/workspace-engine/pkg/db/policies.sql.go
+++ b/apps/workspace-engine/pkg/db/policies.sql.go
@@ -361,6 +361,77 @@ func (q *Queries) ListPoliciesByWorkspaceID(ctx context.Context, arg ListPolicie
return items, nil
}
+const listPoliciesWithRulesByWorkspaceID = `-- name: ListPoliciesWithRulesByWorkspaceID :many
+SELECT
+ p.id, p.name, p.description, p.selector, p.metadata, p.priority, p.enabled, p.workspace_id, p.created_at,
+ COALESCE((SELECT json_agg(json_build_object('id', r.id, 'minApprovals', r.min_approvals)) FROM policy_rule_any_approval r WHERE r.policy_id = p.id), '[]'::json) AS approval_rules,
+ COALESCE((SELECT json_agg(json_build_object('id', r.id, 'allowWindow', r.allow_window, 'durationMinutes', r.duration_minutes, 'rrule', r.rrule, 'timezone', r.timezone)) FROM policy_rule_deployment_window r WHERE r.policy_id = p.id), '[]'::json) AS deployment_window_rules,
+ COALESCE((SELECT json_agg(json_build_object('id', r.id, 'dependsOn', r.depends_on)) FROM policy_rule_deployment_dependency r WHERE r.policy_id = p.id), '[]'::json) AS deployment_dependency_rules,
+ COALESCE((SELECT json_agg(json_build_object('id', r.id, 'dependsOnEnvironmentSelector', r.depends_on_environment_selector, 'maximumAgeHours', r.maximum_age_hours, 'minimumSoakTimeMinutes', r.minimum_soak_time_minutes, 'minimumSuccessPercentage', r.minimum_success_percentage, 'successStatuses', r.success_statuses)) FROM policy_rule_environment_progression r WHERE r.policy_id = p.id), '[]'::json) AS environment_progression_rules,
+ COALESCE((SELECT json_agg(json_build_object('id', r.id, 'rolloutType', r.rollout_type, 'timeScaleInterval', r.time_scale_interval)) FROM policy_rule_gradual_rollout r WHERE r.policy_id = p.id), '[]'::json) AS gradual_rollout_rules,
+ COALESCE((SELECT json_agg(json_build_object('id', r.id, 'intervalSeconds', r.interval_seconds)) FROM policy_rule_version_cooldown r WHERE r.policy_id = p.id), '[]'::json) AS version_cooldown_rules,
+ COALESCE((SELECT json_agg(json_build_object('id', r.id, 'description', r.description, 'selector', r.selector)) FROM policy_rule_version_selector r WHERE r.policy_id = p.id), '[]'::json) AS version_selector_rules
+FROM policy p
+WHERE p.workspace_id = $1
+ORDER BY p.priority DESC, p.created_at DESC
+`
+
+type ListPoliciesWithRulesByWorkspaceIDRow struct {
+ ID uuid.UUID
+ Name string
+ Description pgtype.Text
+ Selector string
+ Metadata map[string]string
+ Priority int32
+ Enabled bool
+ WorkspaceID uuid.UUID
+ CreatedAt pgtype.Timestamptz
+ ApprovalRules []byte
+ DeploymentWindowRules []byte
+ DeploymentDependencyRules []byte
+ EnvironmentProgressionRules []byte
+ GradualRolloutRules []byte
+ VersionCooldownRules []byte
+ VersionSelectorRules []byte
+}
+
+func (q *Queries) ListPoliciesWithRulesByWorkspaceID(ctx context.Context, workspaceID uuid.UUID) ([]ListPoliciesWithRulesByWorkspaceIDRow, error) {
+ rows, err := q.db.Query(ctx, listPoliciesWithRulesByWorkspaceID, workspaceID)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+ var items []ListPoliciesWithRulesByWorkspaceIDRow
+ for rows.Next() {
+ var i ListPoliciesWithRulesByWorkspaceIDRow
+ if err := rows.Scan(
+ &i.ID,
+ &i.Name,
+ &i.Description,
+ &i.Selector,
+ &i.Metadata,
+ &i.Priority,
+ &i.Enabled,
+ &i.WorkspaceID,
+ &i.CreatedAt,
+ &i.ApprovalRules,
+ &i.DeploymentWindowRules,
+ &i.DeploymentDependencyRules,
+ &i.EnvironmentProgressionRules,
+ &i.GradualRolloutRules,
+ &i.VersionCooldownRules,
+ &i.VersionSelectorRules,
+ ); err != nil {
+ return nil, err
+ }
+ items = append(items, i)
+ }
+ if err := rows.Err(); err != nil {
+ return nil, err
+ }
+ return items, nil
+}
+
const listRetryRulesByPolicyID = `-- name: ListRetryRulesByPolicyID :many
SELECT id, policy_id, max_retries, backoff_seconds, backoff_strategy,
diff --git a/apps/workspace-engine/pkg/db/policy_rule_summary.sql.go b/apps/workspace-engine/pkg/db/policy_rule_summary.sql.go
new file mode 100644
index 000000000..c34053d13
--- /dev/null
+++ b/apps/workspace-engine/pkg/db/policy_rule_summary.sql.go
@@ -0,0 +1,69 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+// sqlc v1.30.0
+// source: policy_rule_summary.sql
+
+package db
+
+import (
+ "context"
+
+ "github.com/google/uuid"
+)
+
+const deletePolicyRuleSummariesByRuleID = `-- name: DeletePolicyRuleSummariesByRuleID :exec
+DELETE FROM policy_rule_summary WHERE rule_id = $1
+`
+
+func (q *Queries) DeletePolicyRuleSummariesByRuleID(ctx context.Context, ruleID uuid.UUID) error {
+ _, err := q.db.Exec(ctx, deletePolicyRuleSummariesByRuleID, ruleID)
+ return err
+}
+
+const listPolicyRuleSummariesByEnvironmentAndVersion = `-- name: ListPolicyRuleSummariesByEnvironmentAndVersion :many
+SELECT id, rule_id,
+ environment_id, version_id,
+ allowed, action_required, action_type,
+ message, details,
+ satisfied_at, next_evaluation_at, evaluated_at
+FROM policy_rule_summary
+WHERE environment_id = $1 AND version_id = $2
+`
+
+type ListPolicyRuleSummariesByEnvironmentAndVersionParams struct {
+ EnvironmentID uuid.UUID
+ VersionID uuid.UUID
+}
+
+func (q *Queries) ListPolicyRuleSummariesByEnvironmentAndVersion(ctx context.Context, arg ListPolicyRuleSummariesByEnvironmentAndVersionParams) ([]PolicyRuleSummary, error) {
+ rows, err := q.db.Query(ctx, listPolicyRuleSummariesByEnvironmentAndVersion, arg.EnvironmentID, arg.VersionID)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+ var items []PolicyRuleSummary
+ for rows.Next() {
+ var i PolicyRuleSummary
+ if err := rows.Scan(
+ &i.ID,
+ &i.RuleID,
+ &i.EnvironmentID,
+ &i.VersionID,
+ &i.Allowed,
+ &i.ActionRequired,
+ &i.ActionType,
+ &i.Message,
+ &i.Details,
+ &i.SatisfiedAt,
+ &i.NextEvaluationAt,
+ &i.EvaluatedAt,
+ ); err != nil {
+ return nil, err
+ }
+ items = append(items, i)
+ }
+ if err := rows.Err(); err != nil {
+ return nil, err
+ }
+ return items, nil
+}
diff --git a/apps/workspace-engine/pkg/db/queries/computed_resources.sql b/apps/workspace-engine/pkg/db/queries/computed_resources.sql
index c424e9b68..36d25d87b 100644
--- a/apps/workspace-engine/pkg/db/queries/computed_resources.sql
+++ b/apps/workspace-engine/pkg/db/queries/computed_resources.sql
@@ -56,6 +56,23 @@ JOIN system_environment se
AND se.system_id = sd.system_id
WHERE cdr.resource_id = @resource_id;
+-- name: GetReleaseTargetsForEnvironment :many
+-- Returns all valid release targets for an environment by joining computed
+-- resource tables through the system link tables.
+SELECT DISTINCT
+ cdr.deployment_id,
+ cer.environment_id,
+ cdr.resource_id
+FROM computed_deployment_resource cdr
+JOIN computed_environment_resource cer
+ ON cer.resource_id = cdr.resource_id
+JOIN system_deployment sd
+ ON sd.deployment_id = cdr.deployment_id
+JOIN system_environment se
+ ON se.environment_id = cer.environment_id
+ AND se.system_id = sd.system_id
+WHERE cer.environment_id = @environment_id;
+
-- name: GetReleaseTargetsForWorkspace :many
-- Returns all valid release targets for a workspace.
SELECT DISTINCT
diff --git a/apps/workspace-engine/pkg/db/queries/policies.sql b/apps/workspace-engine/pkg/db/queries/policies.sql
index 246991015..d662119b6 100644
--- a/apps/workspace-engine/pkg/db/queries/policies.sql
+++ b/apps/workspace-engine/pkg/db/queries/policies.sql
@@ -24,6 +24,20 @@ RETURNING *;
-- name: DeletePolicy :exec
DELETE FROM policy WHERE id = $1;
+-- name: ListPoliciesWithRulesByWorkspaceID :many
+SELECT
+ p.id, p.name, p.description, p.selector, p.metadata, p.priority, p.enabled, p.workspace_id, p.created_at,
+ COALESCE((SELECT json_agg(json_build_object('id', r.id, 'minApprovals', r.min_approvals)) FROM policy_rule_any_approval r WHERE r.policy_id = p.id), '[]'::json) AS approval_rules,
+ COALESCE((SELECT json_agg(json_build_object('id', r.id, 'allowWindow', r.allow_window, 'durationMinutes', r.duration_minutes, 'rrule', r.rrule, 'timezone', r.timezone)) FROM policy_rule_deployment_window r WHERE r.policy_id = p.id), '[]'::json) AS deployment_window_rules,
+ COALESCE((SELECT json_agg(json_build_object('id', r.id, 'dependsOn', r.depends_on)) FROM policy_rule_deployment_dependency r WHERE r.policy_id = p.id), '[]'::json) AS deployment_dependency_rules,
+ COALESCE((SELECT json_agg(json_build_object('id', r.id, 'dependsOnEnvironmentSelector', r.depends_on_environment_selector, 'maximumAgeHours', r.maximum_age_hours, 'minimumSoakTimeMinutes', r.minimum_soak_time_minutes, 'minimumSuccessPercentage', r.minimum_success_percentage, 'successStatuses', r.success_statuses)) FROM policy_rule_environment_progression r WHERE r.policy_id = p.id), '[]'::json) AS environment_progression_rules,
+ COALESCE((SELECT json_agg(json_build_object('id', r.id, 'rolloutType', r.rollout_type, 'timeScaleInterval', r.time_scale_interval)) FROM policy_rule_gradual_rollout r WHERE r.policy_id = p.id), '[]'::json) AS gradual_rollout_rules,
+ COALESCE((SELECT json_agg(json_build_object('id', r.id, 'intervalSeconds', r.interval_seconds)) FROM policy_rule_version_cooldown r WHERE r.policy_id = p.id), '[]'::json) AS version_cooldown_rules,
+ COALESCE((SELECT json_agg(json_build_object('id', r.id, 'description', r.description, 'selector', r.selector)) FROM policy_rule_version_selector r WHERE r.policy_id = p.id), '[]'::json) AS version_selector_rules
+FROM policy p
+WHERE p.workspace_id = $1
+ORDER BY p.priority DESC, p.created_at DESC;
+
-- ============================================================
-- policy_rule_any_approval
-- ============================================================
diff --git a/apps/workspace-engine/pkg/db/queries/policy_rule_summary.sql b/apps/workspace-engine/pkg/db/queries/policy_rule_summary.sql
new file mode 100644
index 000000000..a01b4b05b
--- /dev/null
+++ b/apps/workspace-engine/pkg/db/queries/policy_rule_summary.sql
@@ -0,0 +1,36 @@
+-- name: UpsertPolicyRuleSummary :batchexec
+INSERT INTO policy_rule_summary (
+ id, rule_id,
+ environment_id, version_id,
+ allowed, action_required, action_type,
+ message, details,
+ satisfied_at, next_evaluation_at, evaluated_at
+)
+VALUES (
+ gen_random_uuid(), $1,
+ $2, $3,
+ $4, $5, $6,
+ $7, $8,
+ $9, $10, NOW()
+)
+ON CONFLICT (rule_id, environment_id, version_id) DO UPDATE
+SET allowed = EXCLUDED.allowed,
+ action_required = EXCLUDED.action_required,
+ action_type = EXCLUDED.action_type,
+ message = EXCLUDED.message,
+ details = EXCLUDED.details,
+ satisfied_at = EXCLUDED.satisfied_at,
+ next_evaluation_at = EXCLUDED.next_evaluation_at,
+ evaluated_at = NOW();
+
+-- name: ListPolicyRuleSummariesByEnvironmentAndVersion :many
+SELECT id, rule_id,
+ environment_id, version_id,
+ allowed, action_required, action_type,
+ message, details,
+ satisfied_at, next_evaluation_at, evaluated_at
+FROM policy_rule_summary
+WHERE environment_id = $1 AND version_id = $2;
+
+-- name: DeletePolicyRuleSummariesByRuleID :exec
+DELETE FROM policy_rule_summary WHERE rule_id = $1;
diff --git a/apps/workspace-engine/pkg/db/queries/schema.sql b/apps/workspace-engine/pkg/db/queries/schema.sql
index a9884fd67..6c856675d 100644
--- a/apps/workspace-engine/pkg/db/queries/schema.sql
+++ b/apps/workspace-engine/pkg/db/queries/schema.sql
@@ -425,3 +425,21 @@ CREATE TABLE job_verification_metric_measurement (
message TEXT NOT NULL DEFAULT '',
status job_verification_status NOT NULL
);
+
+CREATE TABLE policy_rule_summary (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ rule_id UUID NOT NULL,
+ environment_id UUID NOT NULL,
+ version_id UUID NOT NULL,
+ allowed BOOLEAN NOT NULL,
+ action_required BOOLEAN NOT NULL DEFAULT false,
+ action_type TEXT,
+ message TEXT NOT NULL,
+ details JSONB NOT NULL DEFAULT '{}'::jsonb,
+ satisfied_at TIMESTAMPTZ,
+ next_evaluation_at TIMESTAMPTZ,
+ evaluated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
+);
+
+CREATE UNIQUE INDEX policy_rule_summary_scope_idx
+ ON policy_rule_summary (rule_id, environment_id, version_id);
diff --git a/apps/workspace-engine/pkg/db/sqlc.yaml b/apps/workspace-engine/pkg/db/sqlc.yaml
index 2fdbca942..a81dd32a7 100644
--- a/apps/workspace-engine/pkg/db/sqlc.yaml
+++ b/apps/workspace-engine/pkg/db/sqlc.yaml
@@ -29,6 +29,7 @@ sql:
- queries/jobs.sql
- queries/computed_relationships.sql
- queries/relationship_rules.sql
+ - queries/policy_rule_summary.sql
database:
uri: "postgresql://ctrlplane:ctrlplane@127.0.0.1:5432/ctrlplane?sslmode=disable"
gen:
@@ -124,6 +125,11 @@ sql:
go_type:
type: "map[string]string"
+ # PolicyRuleSummary
+ - column: "policy_rule_summary.details"
+ go_type:
+ type: "map[string]any"
+
# ResourceVariable
- column: "resource_variable.value"
go_type:
diff --git a/apps/workspace-engine/pkg/events/handler/jobs/jobs.go b/apps/workspace-engine/pkg/events/handler/jobs/jobs.go
index 94cbeb15f..6d7c23cdc 100644
--- a/apps/workspace-engine/pkg/events/handler/jobs/jobs.go
+++ b/apps/workspace-engine/pkg/events/handler/jobs/jobs.go
@@ -40,7 +40,6 @@ func HandleJobUpdated(
if jobUpdateEvent.FieldsToUpdate == nil || len(*jobUpdateEvent.FieldsToUpdate) == 0 {
ws.Jobs().Upsert(ctx, &jobUpdateEvent.Job)
dirtyStateForJob(ctx, ws, &jobUpdateEvent.Job)
- // Trigger actions on status change
triggerActionsOnStatusChange(ctx, ws, &jobUpdateEvent.Job, previousStatus)
return nil
}
diff --git a/apps/workspace-engine/pkg/events/handler/policies/policies.go b/apps/workspace-engine/pkg/events/handler/policies/policies.go
index db98655be..9512b26c5 100644
--- a/apps/workspace-engine/pkg/events/handler/policies/policies.go
+++ b/apps/workspace-engine/pkg/events/handler/policies/policies.go
@@ -2,14 +2,14 @@ package policies
import (
"context"
+ "encoding/json"
+
"workspace-engine/pkg/events/handler"
"workspace-engine/pkg/oapi"
"workspace-engine/pkg/workspace"
"workspace-engine/pkg/workspace/releasemanager"
"workspace-engine/pkg/workspace/releasemanager/trace"
- "encoding/json"
-
"github.com/charmbracelet/log"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
diff --git a/apps/workspace-engine/pkg/reconcile/events/policysummary.go b/apps/workspace-engine/pkg/reconcile/events/policysummary.go
new file mode 100644
index 000000000..0be0fa024
--- /dev/null
+++ b/apps/workspace-engine/pkg/reconcile/events/policysummary.go
@@ -0,0 +1,47 @@
+package events
+
+import (
+ "context"
+ "fmt"
+ "workspace-engine/pkg/reconcile"
+
+ "github.com/charmbracelet/log"
+)
+
+const PolicySummaryKind = "policy-summary"
+
+type PolicySummaryParams struct {
+ WorkspaceID string
+ EnvironmentID string
+ VersionID string
+}
+
+func (p PolicySummaryParams) ScopeID() string {
+ return fmt.Sprintf("%s:%s", p.EnvironmentID, p.VersionID)
+}
+
+func EnqueuePolicySummary(queue reconcile.Queue, ctx context.Context, params PolicySummaryParams) error {
+ return queue.Enqueue(ctx, reconcile.EnqueueParams{
+ WorkspaceID: params.WorkspaceID,
+ Kind: PolicySummaryKind,
+ ScopeType: "environment-version",
+ ScopeID: params.ScopeID(),
+ })
+}
+
+func EnqueueManyPolicySummary(queue reconcile.Queue, ctx context.Context, params []PolicySummaryParams) error {
+ if len(params) == 0 {
+ return nil
+ }
+ log.Info("enqueueing policy summary", "count", len(params))
+ items := make([]reconcile.EnqueueParams, len(params))
+ for i, p := range params {
+ items[i] = reconcile.EnqueueParams{
+ WorkspaceID: p.WorkspaceID,
+ Kind: PolicySummaryKind,
+ ScopeType: "environment-version",
+ ScopeID: p.ScopeID(),
+ }
+ }
+ return queue.EnqueueMany(ctx, items)
+}
diff --git a/apps/workspace-engine/svc/controllers/policysummary/controller.go b/apps/workspace-engine/svc/controllers/policysummary/controller.go
new file mode 100644
index 000000000..42a3454d4
--- /dev/null
+++ b/apps/workspace-engine/svc/controllers/policysummary/controller.go
@@ -0,0 +1,97 @@
+package policysummary
+
+import (
+ "context"
+ "fmt"
+ "runtime"
+ "time"
+ "workspace-engine/svc"
+
+ "github.com/charmbracelet/log"
+
+ "workspace-engine/pkg/db"
+ "workspace-engine/pkg/reconcile"
+ "workspace-engine/pkg/reconcile/events"
+ "workspace-engine/pkg/reconcile/postgres"
+
+ "github.com/jackc/pgx/v5/pgxpool"
+ "go.opentelemetry.io/otel"
+ "go.opentelemetry.io/otel/attribute"
+ "go.opentelemetry.io/otel/codes"
+)
+
+var tracer = otel.Tracer("workspace-engine/svc/controllers/policysummary")
+var _ reconcile.Processor = (*Controller)(nil)
+
+type Controller struct {
+ getter Getter
+ setter Setter
+}
+
+func (c *Controller) Process(ctx context.Context, item reconcile.Item) (reconcile.Result, error) {
+ ctx, span := tracer.Start(ctx, "policysummary.Controller.Process")
+ defer span.End()
+
+ span.SetAttributes(
+ attribute.String("item.scope_type", item.ScopeType),
+ attribute.String("item.scope_id", item.ScopeID),
+ )
+
+ result, err := Reconcile(ctx, item.WorkspaceID, item.ScopeID, c.getter, c.setter)
+ if err != nil {
+ span.RecordError(err)
+ span.SetStatus(codes.Error, err.Error())
+ return reconcile.Result{}, fmt.Errorf("reconcile policy summary: %w", err)
+ }
+
+ if result.NextReconcileAt != nil {
+ span.SetAttributes(attribute.String("next_reconcile_at", result.NextReconcileAt.Format(time.RFC3339)))
+ return reconcile.Result{RequeueAfter: time.Until(*result.NextReconcileAt)}, nil
+ }
+
+ return reconcile.Result{}, nil
+}
+
+func NewController(getter Getter, setter Setter) *Controller {
+ return &Controller{getter: getter, setter: setter}
+}
+
+func New(workerID string, pgxPool *pgxpool.Pool) svc.Service {
+ if pgxPool == nil {
+ log.Fatal("Failed to get pgx pool")
+ panic("failed to get pgx pool")
+ }
+ log.Debug(
+ "Creating policy summary reconcile worker",
+ "maxConcurrency", runtime.GOMAXPROCS(0),
+ )
+
+ nodeConfig := reconcile.NodeConfig{
+ WorkerID: workerID,
+ BatchSize: 10,
+ PollInterval: 1 * time.Second,
+ LeaseDuration: 10 * time.Second,
+ LeaseHeartbeat: 5 * time.Second,
+ MaxConcurrency: runtime.GOMAXPROCS(0),
+ MaxRetryBackoff: 10 * time.Second,
+ }
+
+ kind := events.PolicySummaryKind
+ queue := postgres.NewForKinds(pgxPool, kind)
+ queries := db.New(pgxPool)
+ controller := &Controller{
+ getter: NewPostgresGetter(queries),
+ setter: NewPostgresSetter(queries),
+ }
+ worker, err := reconcile.NewWorker(
+ kind,
+ queue,
+ controller,
+ nodeConfig,
+ )
+ if err != nil {
+ log.Fatal("Failed to create policy summary reconcile worker", "error", err)
+ }
+
+ return worker
+}
diff --git a/apps/workspace-engine/svc/controllers/policysummary/getters.go b/apps/workspace-engine/svc/controllers/policysummary/getters.go
new file mode 100644
index 000000000..62f7995d1
--- /dev/null
+++ b/apps/workspace-engine/svc/controllers/policysummary/getters.go
@@ -0,0 +1,20 @@
+package policysummary
+
+import (
+ "context"
+
+ "workspace-engine/pkg/oapi"
+ "workspace-engine/svc/controllers/policysummary/summaryeval"
+
+ "github.com/google/uuid"
+)
+
+type evalGetter = summaryeval.Getter
+
+type Getter interface {
+ evalGetter
+
+ GetVersion(ctx context.Context, versionID uuid.UUID) (*oapi.DeploymentVersion, error)
+ GetPoliciesForEnvironment(ctx context.Context, workspaceID, environmentID uuid.UUID) ([]*oapi.Policy, error)
+ GetPoliciesForDeployment(ctx context.Context, workspaceID, deploymentID uuid.UUID) ([]*oapi.Policy, error)
+}
diff --git a/apps/workspace-engine/svc/controllers/policysummary/getters_postgres.go b/apps/workspace-engine/svc/controllers/policysummary/getters_postgres.go
new file mode 100644
index 000000000..b38ee0de8
--- /dev/null
+++ b/apps/workspace-engine/svc/controllers/policysummary/getters_postgres.go
@@ -0,0 +1,111 @@
+package policysummary
+
+import (
+ "context"
+ "fmt"
+
+ "workspace-engine/pkg/db"
+ "workspace-engine/pkg/oapi"
+ "workspace-engine/pkg/selector"
+ "workspace-engine/svc/controllers/policysummary/summaryeval"
+
+ "github.com/google/uuid"
+ "go.opentelemetry.io/otel"
+ "go.opentelemetry.io/otel/attribute"
+ "go.opentelemetry.io/otel/codes"
+)
+
+var postgresGetterTracer = otel.Tracer("policysummary.getters_postgres")
+
+type summaryevalGetter = summaryeval.PostgresGetter
+
+var _ Getter = (*PostgresGetter)(nil)
+
+type PostgresGetter struct {
+ *summaryevalGetter
+ queries *db.Queries
+}
+
+func NewPostgresGetter(queries *db.Queries) *PostgresGetter {
+ return &PostgresGetter{
+ summaryevalGetter: summaryeval.NewPostgresGetter(queries),
+ queries: queries,
+ }
+}
+
+func (g *PostgresGetter) GetVersion(ctx context.Context, versionID uuid.UUID) (*oapi.DeploymentVersion, error) {
+ ver, err := g.queries.GetDeploymentVersionByID(ctx, versionID)
+ if err != nil {
+ return nil, fmt.Errorf("get version %s: %w", versionID, err)
+ }
+ return db.ToOapiDeploymentVersion(ver), nil
+}
+
+func (g *PostgresGetter) GetPoliciesForEnvironment(ctx context.Context, workspaceID, environmentID uuid.UUID) ([]*oapi.Policy, error) {
+ ctx, span := postgresGetterTracer.Start(ctx, "GetPoliciesForEnvironment")
+ defer span.End()
+
+ policyRows, err := g.queries.ListPoliciesWithRulesByWorkspaceID(ctx, workspaceID)
+ if err != nil {
+ span.RecordError(err)
+ span.SetStatus(codes.Error, "failed to list policies for workspace")
+ return nil, fmt.Errorf("list policies for workspace %s: %w", workspaceID, err)
+ }
+
+ span.SetAttributes(attribute.Int("policies_count", len(policyRows)))
+
+ allPolicies := make([]*oapi.Policy, 0, len(policyRows))
+ for _, row := range policyRows {
+ allPolicies = append(allPolicies, db.ToOapiPolicyWithRules(row))
+ }
+
+ releaseTargets, err := g.queries.GetReleaseTargetsForEnvironment(ctx, environmentID)
+ if err != nil {
+ return nil, fmt.Errorf("get release targets for environment %s: %w", environmentID, err)
+ }
+
+ span.SetAttributes(attribute.Int("release_targets_count", len(releaseTargets)))
+
+ envRow, err := g.queries.GetEnvironmentByID(ctx, environmentID)
+ if err != nil {
+ return nil, fmt.Errorf("get environment %s: %w", environmentID, err)
+ }
+ environment := db.ToOapiEnvironment(envRow)
+
+ span.SetAttributes(attribute.String("environment.id", environment.Id))
+
+ seen := make(map[string]struct{})
+ var matched []*oapi.Policy
+
+ for _, rt := range releaseTargets {
+ depRow, err := g.queries.GetDeploymentByID(ctx, rt.DeploymentID)
+ if err != nil {
+ continue
+ }
+ resRow, err := g.queries.GetResourceByID(ctx, rt.ResourceID)
+ if err != nil {
+ continue
+ }
+ deployment := db.ToOapiDeployment(depRow)
+ resource := db.ToOapiResource(resRow)
+ resolved := selector.NewResolvedReleaseTarget(environment, deployment, resource)
+
+ for _, p := range allPolicies {
+ if _, ok := seen[p.Id]; ok {
+ continue
+ }
+ if selector.MatchPolicy(ctx, p, resolved) {
+ seen[p.Id] = struct{}{}
+ matched = append(matched, p)
+ }
+ }
+ }
+
+ span.SetAttributes(attribute.Int("matched_policies_count", len(matched)))
+
+ return matched, nil
+}
+
+func (g *PostgresGetter) GetPoliciesForDeployment(ctx context.Context, workspaceID, deploymentID uuid.UUID) ([]*oapi.Policy, error) {
+ return g.GetPoliciesForEnvironment(ctx, workspaceID, deploymentID)
+}
diff --git a/apps/workspace-engine/svc/controllers/policysummary/getters_postgres_test.go b/apps/workspace-engine/svc/controllers/policysummary/getters_postgres_test.go
new file mode 100644
index 000000000..c45e9c36f
--- /dev/null
+++ b/apps/workspace-engine/svc/controllers/policysummary/getters_postgres_test.go
@@ -0,0 +1,235 @@
+package policysummary_test
+
+import (
+ "context"
+ "os"
+ "testing"
+
+ "workspace-engine/pkg/db"
+ "workspace-engine/svc/controllers/policysummary"
+
+ "github.com/google/uuid"
+ "github.com/jackc/pgx/v5/pgxpool"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+const defaultDBURL = "postgresql://ctrlplane:ctrlplane@localhost:5432/ctrlplane"
+
+func requireTestDB(t *testing.T) *pgxpool.Pool {
+ t.Helper()
+ if os.Getenv("USE_DATABASE_BACKING") == "" {
+ t.Skip("Skipping: set USE_DATABASE_BACKING=1 to run DB-backed tests")
+ }
+ if os.Getenv("POSTGRES_URL") == "" {
+ os.Setenv("POSTGRES_URL", defaultDBURL)
+ }
+ ctx := context.Background()
+ pool := db.GetPool(ctx)
+ if err := pool.Ping(ctx); err != nil {
+ t.Skipf("Database not available: %v", err)
+ }
+ return pool
+}
+
+type fixture struct {
+ pool *pgxpool.Pool
+ workspaceID uuid.UUID
+ deploymentID uuid.UUID
+ environmentID uuid.UUID
+ resourceID uuid.UUID
+ providerID uuid.UUID
+}
+
+func setupFixture(t *testing.T, pool *pgxpool.Pool) *fixture {
+ t.Helper()
+ ctx := context.Background()
+
+ f := &fixture{
+ pool: pool,
+ workspaceID: uuid.New(),
+ deploymentID: uuid.New(),
+ environmentID: uuid.New(),
+ resourceID: uuid.New(),
+ providerID: uuid.New(),
+ }
+
+ slug := "test-ps-" + f.workspaceID.String()[:8]
+ _, err := pool.Exec(ctx,
+ "INSERT INTO workspace (id, name, slug) VALUES ($1, $2, $3)",
+ f.workspaceID, "test_policy_summary", slug)
+ require.NoError(t, err)
+
+ _, err = pool.Exec(ctx,
+ "INSERT INTO resource_provider (id, name, workspace_id) VALUES ($1, $2, $3)",
+ f.providerID, "test-provider", f.workspaceID)
+ require.NoError(t, err)
+
+ _, err = pool.Exec(ctx,
+ `INSERT INTO deployment (id, name, description, resource_selector, workspace_id)
+ VALUES ($1, $2, $3, $4, $5)`,
+ f.deploymentID, "test-deploy", "", "true", f.workspaceID)
+ require.NoError(t, err)
+
+ _, err = pool.Exec(ctx,
+ `INSERT INTO environment (id, name, resource_selector, workspace_id)
+ VALUES ($1, $2, $3, $4)`,
+ f.environmentID, "test-env", "true", f.workspaceID)
+ require.NoError(t, err)
+
+ t.Cleanup(func() {
+ cleanCtx := context.Background()
+ _, _ = pool.Exec(cleanCtx, "DELETE FROM deployment_version WHERE workspace_id = $1", f.workspaceID)
+ _, _ = pool.Exec(cleanCtx, "DELETE FROM policy WHERE workspace_id = $1", f.workspaceID)
+ _, _ = pool.Exec(cleanCtx, "DELETE FROM deployment WHERE workspace_id = $1", f.workspaceID)
+ _, _ = pool.Exec(cleanCtx, "DELETE FROM environment WHERE workspace_id = $1", f.workspaceID)
+ _, _ = pool.Exec(cleanCtx, "DELETE FROM resource_provider WHERE workspace_id = $1", f.workspaceID)
+ _, _ = pool.Exec(cleanCtx, "DELETE FROM workspace WHERE id = $1", f.workspaceID)
+ })
+
+ return f
+}
+
+func TestPostgresGetter_GetEnvironment(t *testing.T) {
+ pool := requireTestDB(t)
+ f := setupFixture(t, pool)
+ ctx := context.Background()
+
+ queries := db.New(pool)
+ getter := policysummary.NewPostgresGetter(queries)
+
+ env, err := getter.GetEnvironment(ctx, f.environmentID.String())
+ require.NoError(t, err)
+ require.NotNil(t, env)
+ assert.Equal(t, f.environmentID.String(), env.Id)
+ assert.Equal(t, "test-env", env.Name)
+}
+
+func TestPostgresGetter_GetEnvironment_NotFound(t *testing.T) {
+ pool := requireTestDB(t)
+ _ = setupFixture(t, pool)
+ ctx := context.Background()
+
+ queries := db.New(pool)
+ getter := policysummary.NewPostgresGetter(queries)
+
+ _, err := getter.GetEnvironment(ctx, uuid.New().String())
+ assert.Error(t, err)
+}
+
+func TestPostgresGetter_GetDeployment(t *testing.T) {
+ pool := requireTestDB(t)
+ f := setupFixture(t, pool)
+ ctx := context.Background()
+
+ queries := db.New(pool)
+ getter := policysummary.NewPostgresGetter(queries)
+
+ dep, err := getter.GetDeployment(ctx, f.deploymentID.String())
+ require.NoError(t, err)
+ require.NotNil(t, dep)
+ assert.Equal(t, f.deploymentID.String(), dep.Id)
+ assert.Equal(t, "test-deploy", dep.Name)
+}
+
+func TestPostgresGetter_GetDeployment_NotFound(t *testing.T) {
+ pool := requireTestDB(t)
+ _ = setupFixture(t, pool)
+ ctx := context.Background()
+
+ queries := db.New(pool)
+ getter := policysummary.NewPostgresGetter(queries)
+
+ _, err := getter.GetDeployment(ctx, uuid.New().String())
+ assert.Error(t, err)
+}
+
+func TestPostgresGetter_GetVersion(t *testing.T) {
+ pool := requireTestDB(t)
+ f := setupFixture(t, pool)
+ ctx := context.Background()
+
+ versionID := uuid.New()
+ _, err := pool.Exec(ctx,
+ `INSERT INTO deployment_version (id, name, tag, deployment_id, status, workspace_id)
+ VALUES ($1, $2, $3, $4, 'ready', $5)`,
+ versionID, "v1.0.0", "v1.0.0", f.deploymentID, f.workspaceID)
+ require.NoError(t, err)
+
+ queries := db.New(pool)
+ getter := policysummary.NewPostgresGetter(queries)
+
+ ver, err := getter.GetVersion(ctx, versionID)
+ require.NoError(t, err)
+ require.NotNil(t, ver)
+ assert.Equal(t, versionID.String(), ver.Id)
+ assert.Equal(t, "v1.0.0", ver.Tag)
+}
+
+func TestPostgresGetter_GetVersion_NotFound(t *testing.T) {
+ pool := requireTestDB(t)
+ _ = setupFixture(t, pool)
+ ctx := context.Background()
+
+ queries := db.New(pool)
+ getter := policysummary.NewPostgresGetter(queries)
+
+ _, err := getter.GetVersion(ctx, uuid.New())
+ assert.Error(t, err)
+}
+
+func TestPostgresGetter_GetPoliciesForEnvironment(t *testing.T) {
+ pool := requireTestDB(t)
+ f := setupFixture(t, pool)
+ ctx := context.Background()
+
+ policyID := uuid.New()
+ _, err := pool.Exec(ctx,
+ `INSERT INTO policy (id, name, selector, priority, enabled, workspace_id)
+ VALUES ($1, $2, $3, $4, $5, $6)`,
+ policyID, "test-policy", "true", 10, true, f.workspaceID)
+ require.NoError(t, err)
+
+ queries := db.New(pool)
+ getter := policysummary.NewPostgresGetter(queries)
+
+ policies, err := getter.GetPoliciesForEnvironment(ctx, f.workspaceID, f.environmentID)
+ require.NoError(t, err)
+ require.Len(t, policies, 1)
+ assert.Equal(t, policyID.String(), policies[0].Id)
+ assert.Equal(t, "test-policy", policies[0].Name)
+}
+
+func TestPostgresGetter_GetPoliciesForDeployment(t *testing.T) {
+ pool := requireTestDB(t)
+ f := setupFixture(t, pool)
+ ctx := context.Background()
+
+ policyID := uuid.New()
+ _, err := pool.Exec(ctx,
+ `INSERT INTO policy (id, name, selector, priority, enabled, workspace_id)
+ VALUES ($1, $2, $3, $4, $5, $6)`,
+ policyID, "dep-policy", "true", 5, true, f.workspaceID)
+ require.NoError(t, err)
+
+ queries := db.New(pool)
+ getter := policysummary.NewPostgresGetter(queries)
+
+ policies, err := getter.GetPoliciesForDeployment(ctx, f.workspaceID, f.deploymentID)
+ require.NoError(t, err)
+ require.Len(t, policies, 1)
+ assert.Equal(t, policyID.String(), policies[0].Id)
+}
+
+func TestPostgresGetter_GetPoliciesEmpty(t *testing.T) {
+ pool := requireTestDB(t)
+ f := setupFixture(t, pool)
+ ctx := context.Background()
+
+ queries := db.New(pool)
+ getter := policysummary.NewPostgresGetter(queries)
+
+ policies, err := getter.GetPoliciesForEnvironment(ctx, f.workspaceID, f.environmentID)
+ require.NoError(t, err)
+ assert.Empty(t, policies)
+}
diff --git a/apps/workspace-engine/svc/controllers/policysummary/reconcile.go b/apps/workspace-engine/svc/controllers/policysummary/reconcile.go
new file mode 100644
index 000000000..e4c65fbc7
--- /dev/null
+++ b/apps/workspace-engine/svc/controllers/policysummary/reconcile.go
@@ -0,0 +1,106 @@
+package policysummary
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "workspace-engine/pkg/workspace/releasemanager/policy/evaluator"
+ "workspace-engine/svc/controllers/policysummary/summaryeval"
+
+ "github.com/google/uuid"
+ "go.opentelemetry.io/otel/attribute"
+ "go.opentelemetry.io/otel/trace"
+)
+
+type ReconcileResult struct {
+ NextReconcileAt *time.Time
+}
+
+type reconciler struct {
+ workspaceID uuid.UUID
+ getter Getter
+ setter Setter
+}
+
+func (r *reconciler) reconcile(ctx context.Context, scope *Scope) (*ReconcileResult, error) {
+ ctx, span := tracer.Start(ctx, "policysummary.reconcile")
+ defer span.End()
+
+ env, err := r.getter.GetEnvironment(ctx, scope.EnvironmentID.String())
+ if err != nil {
+ return nil, fmt.Errorf("get environment: %w", err)
+ }
+
+ version, err := r.getter.GetVersion(ctx, scope.VersionID)
+ if err != nil {
+ return nil, fmt.Errorf("get version: %w", err)
+ }
+
+ evalScope := evaluator.EvaluatorScope{
+ Environment: env,
+ Version: version,
+ }
+
+ policies, err := r.getter.GetPoliciesForEnvironment(ctx, r.workspaceID, scope.EnvironmentID)
+ span.SetAttributes(attribute.Int("policies_count", len(policies)))
+ if err != nil {
+ return nil, fmt.Errorf("get policies: %w", err)
+ }
+
+ var rows []RuleSummaryRow
+ var nextTime *time.Time
+
+ for _, p := range policies {
+ span.AddEvent("policy_rule_count", trace.WithAttributes(attribute.Int("rules_count", len(p.Rules))))
+ for _, rule := range p.Rules {
+ evals := summaryeval.RuleEvaluators(r.getter, r.workspaceID.String(), &rule)
+ span.AddEvent("found_evaluators", trace.WithAttributes(attribute.Int("evaluators_count", len(evals))))
+ for _, eval := range evals {
+ if eval == nil {
+ continue
+ }
+ if !evalScope.HasFields(eval.ScopeFields()) {
+ continue
+ }
+
+ result := eval.Evaluate(ctx, evalScope)
+ rows = append(rows, RuleSummaryRow{
+ RuleID: uuid.MustParse(rule.Id),
+ EnvironmentID: scope.EnvironmentID,
+ VersionID: scope.VersionID,
+ Evaluation: result,
+ })
+
+ if result.NextEvaluationTime != nil {
+ if nextTime == nil || result.NextEvaluationTime.Before(*nextTime) {
+ nextTime = result.NextEvaluationTime
+ }
+ }
+ }
+ }
+ }
+
+ span.SetAttributes(attribute.Int("rows_count", len(rows)))
+
+ if err := r.setter.UpsertRuleSummaries(ctx, rows); err != nil {
+ return nil, fmt.Errorf("upsert rule summaries: %w", err)
+ }
+
+ return &ReconcileResult{NextReconcileAt: nextTime}, nil
+}
+
+func Reconcile(ctx context.Context, workspaceID string, scopeID string, getter Getter, setter Setter) (*ReconcileResult, error) {
+ scope, err := ParseScope(scopeID)
+ if err != nil {
+ return nil, err
+ }
+
+ r := &reconciler{
+ workspaceID: uuid.MustParse(workspaceID),
+ getter: getter,
+ setter: setter,
+ }
+
+ return r.reconcile(ctx, scope)
+}
diff --git a/apps/workspace-engine/svc/controllers/policysummary/scope.go b/apps/workspace-engine/svc/controllers/policysummary/scope.go
new file mode 100644
index 000000000..773d5d270
--- /dev/null
+++ b/apps/workspace-engine/svc/controllers/policysummary/scope.go
@@ -0,0 +1,29 @@
+package policysummary
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/google/uuid"
+)
+
+type Scope struct {
+ EnvironmentID uuid.UUID
+ VersionID uuid.UUID
+}
+
+func ParseScope(scopeID string) (*Scope, error) {
+ parts := strings.SplitN(scopeID, ":", 2)
+ if len(parts) != 2 {
+ return nil, fmt.Errorf("invalid policy summary scope: %s", scopeID)
+ }
+ envID, err := uuid.Parse(parts[0])
+ if err != nil {
+ return nil, fmt.Errorf("parse environment id: %w", err)
+ }
+ versionID, err := uuid.Parse(parts[1])
+ if err != nil {
+ return nil, fmt.Errorf("parse version id: %w", err)
+ }
+ return &Scope{EnvironmentID: envID, VersionID: versionID}, nil
+}
diff --git a/apps/workspace-engine/svc/controllers/policysummary/setters.go b/apps/workspace-engine/svc/controllers/policysummary/setters.go
new file mode 100644
index 000000000..96d28e9f4
--- /dev/null
+++ b/apps/workspace-engine/svc/controllers/policysummary/setters.go
@@ -0,0 +1,20 @@
+package policysummary
+
+import (
+ "context"
+
+ "workspace-engine/pkg/oapi"
+
+ "github.com/google/uuid"
+)
+
+type RuleSummaryRow struct {
+ RuleID uuid.UUID
+ EnvironmentID uuid.UUID
+ VersionID uuid.UUID
+ Evaluation *oapi.RuleEvaluation
+}
+
+type Setter interface {
+ UpsertRuleSummaries(ctx context.Context, rows []RuleSummaryRow) error
+}
diff --git a/apps/workspace-engine/svc/controllers/policysummary/setters_postgres.go b/apps/workspace-engine/svc/controllers/policysummary/setters_postgres.go
new file mode 100644
index 000000000..cf0dcd096
--- /dev/null
+++ b/apps/workspace-engine/svc/controllers/policysummary/setters_postgres.go
@@ -0,0 +1,73 @@
+package policysummary
+
+import (
+ "context"
+ "fmt"
+
+ "workspace-engine/pkg/db"
+
+ "github.com/jackc/pgx/v5/pgtype"
+)
+
+var _ Setter = (*PostgresSetter)(nil)
+
+type PostgresSetter struct {
+ queries *db.Queries
+}
+
+func NewPostgresSetter(queries *db.Queries) *PostgresSetter {
+ return &PostgresSetter{queries: queries}
+}
+
+func (s *PostgresSetter) UpsertRuleSummaries(ctx context.Context, rows []RuleSummaryRow) error {
+ if len(rows) == 0 {
+ return nil
+ }
+
+ params := make([]db.UpsertPolicyRuleSummaryParams, len(rows))
+ for i, row := range rows {
+ eval := row.Evaluation
+
+ var actionType pgtype.Text
+ if eval.ActionType != nil {
+ actionType = pgtype.Text{String: string(*eval.ActionType), Valid: true}
+ }
+
+ detailsMap := map[string]any{}
+ if eval.Details != nil {
+ detailsMap = eval.Details
+ }
+
+ var satisfiedAt pgtype.Timestamptz
+ if eval.SatisfiedAt != nil {
+ satisfiedAt = pgtype.Timestamptz{Time: *eval.SatisfiedAt, Valid: true}
+ }
+
+ var nextEvalAt pgtype.Timestamptz
+ if eval.NextEvaluationTime != nil {
+ nextEvalAt = pgtype.Timestamptz{Time: *eval.NextEvaluationTime, Valid: true}
+ }
+
+ params[i] = db.UpsertPolicyRuleSummaryParams{
+ RuleID: row.RuleID,
+ EnvironmentID: row.EnvironmentID,
+ VersionID: row.VersionID,
+ Allowed: eval.Allowed,
+ ActionRequired: eval.ActionRequired,
+ ActionType: actionType,
+ Message: eval.Message,
+ Details: detailsMap,
+ SatisfiedAt: satisfiedAt,
+ NextEvaluationAt: nextEvalAt,
+ }
+ }
+
+ results := s.queries.UpsertPolicyRuleSummary(ctx, params)
+ var batchErr error
+ results.Exec(func(i int, err error) {
+ if err != nil && batchErr == nil {
+ batchErr = fmt.Errorf("upsert rule summary %d: %w", i, err)
+ }
+ })
+ return batchErr
+}
diff --git a/apps/workspace-engine/svc/controllers/policysummary/summaryeval/getter.go b/apps/workspace-engine/svc/controllers/policysummary/summaryeval/getter.go
new file mode 100644
index 000000000..6848f319f
--- /dev/null
+++ b/apps/workspace-engine/svc/controllers/policysummary/summaryeval/getter.go
@@ -0,0 +1,20 @@
+package summaryeval
+
+import (
+ "workspace-engine/pkg/workspace/releasemanager/policy/evaluator/approval"
+ "workspace-engine/pkg/workspace/releasemanager/policy/evaluator/environmentprogression"
+ "workspace-engine/pkg/workspace/releasemanager/policy/evaluator/gradualrollout"
+ "workspace-engine/pkg/workspace/releasemanager/policy/evaluator/versioncooldown"
+)
+
+type approvalGetter = approval.Getters
+type environmentProgressionGetter = environmentprogression.Getters
+type gradualRolloutGetter = gradualrollout.Getters
+type versionCooldownGetter = versioncooldown.Getters
+
+type Getter interface {
+ approvalGetter
+ environmentProgressionGetter
+ gradualRolloutGetter
+ versionCooldownGetter
+}
diff --git a/apps/workspace-engine/svc/controllers/policysummary/summaryeval/getter_postgres.go b/apps/workspace-engine/svc/controllers/policysummary/summaryeval/getter_postgres.go
new file mode 100644
index 000000000..80e4247d8
--- /dev/null
+++ b/apps/workspace-engine/svc/controllers/policysummary/summaryeval/getter_postgres.go
@@ -0,0 +1,35 @@
+package summaryeval
+
+import (
+ "context"
+ "workspace-engine/pkg/db"
+ "workspace-engine/pkg/oapi"
+ "workspace-engine/pkg/workspace/releasemanager/policy/evaluator/gradualrollout"
+ "workspace-engine/pkg/workspace/releasemanager/policy/evaluator/versioncooldown"
+)
+
+var _ Getter = (*PostgresGetter)(nil)
+
+type PostgresGetter struct {
+ gradualRolloutGetter
+ versioncooldown *versioncooldown.PostgresGetters
+}
+
+func NewPostgresGetter(queries *db.Queries) *PostgresGetter {
+ return &PostgresGetter{
+ gradualRolloutGetter: gradualrollout.NewPostgresGetters(queries),
+ versioncooldown: versioncooldown.NewPostgresGetters(queries),
+ }
+}
+
+func (g *PostgresGetter) GetJobVerificationStatus(jobID string) oapi.JobVerificationStatus {
+ return g.versioncooldown.GetJobVerificationStatus(jobID)
+}
+
+func (g *PostgresGetter) GetAllReleaseTargets(ctx context.Context, workspaceID string) ([]*oapi.ReleaseTarget, error) {
+ return g.versioncooldown.GetAllReleaseTargets(ctx, workspaceID)
+}
+
+func (g *PostgresGetter) GetJobsForReleaseTarget(releaseTarget *oapi.ReleaseTarget) map[string]*oapi.Job {
+ return g.versioncooldown.GetJobsForReleaseTarget(releaseTarget)
+}
diff --git a/apps/workspace-engine/svc/controllers/policysummary/summaryeval/getter_store.go b/apps/workspace-engine/svc/controllers/policysummary/summaryeval/getter_store.go
new file mode 100644
index 000000000..cd0451eb1
--- /dev/null
+++ b/apps/workspace-engine/svc/controllers/policysummary/summaryeval/getter_store.go
@@ -0,0 +1,35 @@
+package summaryeval
+
+import (
+ "context"
+ "workspace-engine/pkg/oapi"
+ "workspace-engine/pkg/workspace/releasemanager/policy/evaluator/gradualrollout"
+ "workspace-engine/pkg/workspace/releasemanager/policy/evaluator/versioncooldown"
+ legacystore "workspace-engine/pkg/workspace/store"
+)
+
+var _ Getter = (*StoreGetter)(nil)
+
+type StoreGetter struct {
+ gradualRolloutGetter
+ versioncooldown versioncooldown.Getters
+}
+
+func NewStoreGetter(store *legacystore.Store) *StoreGetter {
+ return &StoreGetter{
+ gradualRolloutGetter: gradualrollout.NewStoreGetters(store),
+ versioncooldown: versioncooldown.NewStoreGetters(store),
+ }
+}
+
+func (g *StoreGetter) GetJobVerificationStatus(jobID string) oapi.JobVerificationStatus {
+ return g.versioncooldown.GetJobVerificationStatus(jobID)
+}
+
+func (g *StoreGetter) GetAllReleaseTargets(ctx context.Context, workspaceID string) ([]*oapi.ReleaseTarget, error) {
+ return g.versioncooldown.GetAllReleaseTargets(ctx, workspaceID)
+}
+
+func (g *StoreGetter) GetJobsForReleaseTarget(releaseTarget *oapi.ReleaseTarget) map[string]*oapi.Job {
+ return g.versioncooldown.GetJobsForReleaseTarget(releaseTarget)
+}
diff --git a/apps/workspace-engine/svc/controllers/policysummary/summaryeval/summaryeval.go b/apps/workspace-engine/svc/controllers/policysummary/summaryeval/summaryeval.go
new file mode 100644
index 000000000..3905ea1a8
--- /dev/null
+++ b/apps/workspace-engine/svc/controllers/policysummary/summaryeval/summaryeval.go
@@ -0,0 +1,22 @@
+package summaryeval
+
+import (
+ "workspace-engine/pkg/oapi"
+ "workspace-engine/pkg/workspace/releasemanager/policy/evaluator"
+ "workspace-engine/pkg/workspace/releasemanager/policy/evaluator/approval"
+ "workspace-engine/pkg/workspace/releasemanager/policy/evaluator/deploymentwindow"
+ "workspace-engine/pkg/workspace/releasemanager/policy/evaluator/environmentprogression"
+ "workspace-engine/pkg/workspace/releasemanager/policy/evaluator/versioncooldown"
+)
+
+// RuleEvaluators returns all summary evaluators for a given policy rule.
+func RuleEvaluators(getter Getter, wsId string, rule *oapi.PolicyRule) []evaluator.Evaluator {
+ return evaluator.CollectEvaluators(
+ deploymentwindow.NewSummaryEvaluator(rule),
+ approval.NewEvaluator(getter, rule),
+ environmentprogression.NewEvaluator(getter, rule),
+ versioncooldown.NewSummaryEvaluator(getter, wsId, rule),
+ // TODO: add gradualrollout.NewSummaryEvaluator(getter, rule)
+ // once the getter-based constructor is added
+ )
+}
diff --git a/apps/workspace-engine/svc/controllers/policysummary/summaryeval/summaryeval_test.go b/apps/workspace-engine/svc/controllers/policysummary/summaryeval/summaryeval_test.go
new file mode 100644
index 000000000..8ac6c4fd4
--- /dev/null
+++ b/apps/workspace-engine/svc/controllers/policysummary/summaryeval/summaryeval_test.go
@@ -0,0 +1,193 @@
+package summaryeval
+
+import (
+ "context"
+ "testing"
+
+ "workspace-engine/pkg/oapi"
+ "workspace-engine/pkg/workspace/releasemanager/policy/evaluator"
+
+ "github.com/google/uuid"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+// ---------------------------------------------------------------------------
+// Mock getter satisfying the composite summaryeval.Getter interface
+// ---------------------------------------------------------------------------
+
+var _ Getter = (*mockGetter)(nil)
+
+type mockGetter struct{}
+
+func (m *mockGetter) GetApprovalRecords(_ context.Context, _, _ string) ([]*oapi.UserApprovalRecord, error) {
+ return nil, nil
+}
+func (m *mockGetter) GetEnvironment(_ context.Context, _ string) (*oapi.Environment, error) {
+ return nil, nil
+}
+func (m *mockGetter) GetAllEnvironments(_ context.Context, _ string) (map[string]*oapi.Environment, error) {
+ return nil, nil
+}
+func (m *mockGetter) GetDeployment(_ context.Context, _ string) (*oapi.Deployment, error) {
+ return nil, nil
+}
+func (m *mockGetter) GetAllDeployments(_ context.Context, _ string) (map[string]*oapi.Deployment, error) {
+ return nil, nil
+}
+func (m *mockGetter) GetResource(_ context.Context, _ string) (*oapi.Resource, error) {
+ return nil, nil
+}
+func (m *mockGetter) GetRelease(_ context.Context, _ string) (*oapi.Release, error) {
+ return nil, nil
+}
+func (m *mockGetter) GetSystemIDsForEnvironment(_ string) []string { return nil }
+func (m *mockGetter) GetReleaseTargetsForEnvironment(_ context.Context, _ string) ([]*oapi.ReleaseTarget, error) {
+ return nil, nil
+}
+func (m *mockGetter) GetReleaseTargetsForDeployment(_ context.Context, _ string) ([]*oapi.ReleaseTarget, error) {
+ return nil, nil
+}
+func (m *mockGetter) GetJobsForReleaseTarget(_ *oapi.ReleaseTarget) map[string]*oapi.Job {
+ return nil
+}
+func (m *mockGetter) GetAllPolicies(_ context.Context, _ string) (map[string]*oapi.Policy, error) {
+ return nil, nil
+}
+func (m *mockGetter) GetPoliciesForReleaseTarget(_ context.Context, _ *oapi.ReleaseTarget) ([]*oapi.Policy, error) {
+ return nil, nil
+}
+func (m *mockGetter) GetPolicySkips(_ context.Context, _, _, _ string) ([]*oapi.PolicySkip, error) {
+ return nil, nil
+}
+func (m *mockGetter) HasCurrentRelease(_ context.Context, _ *oapi.ReleaseTarget) (bool, error) {
+ return false, nil
+}
+func (m *mockGetter) GetReleaseTargets() ([]*oapi.ReleaseTarget, error) { return nil, nil }
+func (m *mockGetter) GetAllReleaseTargets(_ context.Context, _ string) ([]*oapi.ReleaseTarget, error) {
+ return nil, nil
+}
+func (m *mockGetter) GetJobVerificationStatus(_ string) oapi.JobVerificationStatus {
+ return oapi.JobVerificationStatusCancelled
+}
+
+// ---------------------------------------------------------------------------
+// Rule builder helpers
+// ---------------------------------------------------------------------------
+
+func ruleWithDeploymentWindow(id string) *oapi.PolicyRule {
+ return &oapi.PolicyRule{
+ Id: id,
+ DeploymentWindow: &oapi.DeploymentWindowRule{
+ Rrule: "FREQ=WEEKLY;BYDAY=MO;BYHOUR=9",
+ DurationMinutes: 60,
+ },
+ }
+}
+
+func ruleWithApproval(id string) *oapi.PolicyRule {
+ return &oapi.PolicyRule{
+ Id: id,
+ AnyApproval: &oapi.AnyApprovalRule{MinApprovals: 1},
+ }
+}
+
+func ruleWithEnvironmentProgression(id string) *oapi.PolicyRule {
+ return &oapi.PolicyRule{
+ Id: id,
+ EnvironmentProgression: &oapi.EnvironmentProgressionRule{},
+ }
+}
+
+func ruleWithVersionCooldown(id string) *oapi.PolicyRule {
+ return &oapi.PolicyRule{
+ Id: id,
+ VersionCooldown: &oapi.VersionCooldownRule{IntervalSeconds: 300},
+ }
+}
+
+func emptyRule(id string) *oapi.PolicyRule {
+ return &oapi.PolicyRule{Id: id}
+}
+
+func evalTypes(evals []evaluator.Evaluator) []string {
+ types := make([]string, len(evals))
+ for i, e := range evals {
+ types[i] = e.RuleType()
+ }
+ return types
+}
+
+// ---------------------------------------------------------------------------
+// RuleEvaluators tests
+// ---------------------------------------------------------------------------
+
+func TestRuleEvaluators(t *testing.T) {
+ getter := &mockGetter{}
+ wsId := uuid.New().String()
+
+ t.Run("returns empty for nil rule", func(t *testing.T) {
+ evals := RuleEvaluators(getter, wsId, nil)
+ assert.Empty(t, evals)
+ })
+
+ t.Run("returns empty for nil getter with non-nil rule", func(t *testing.T) {
+ evals := RuleEvaluators(nil, wsId, ruleWithApproval("r-1"))
+ // deployment window doesn't need a getter, so it may still return
+ // but approval/envprogression/cooldown need a getter and return nil
+ for _, e := range evals {
+ assert.NotEqual(t, evaluator.RuleTypeApproval, e.RuleType())
+ assert.NotEqual(t, evaluator.RuleTypeEnvironmentProgression, e.RuleType())
+ assert.NotEqual(t, evaluator.RuleTypeVersionCooldown, e.RuleType())
+ }
+ })
+
+ t.Run("returns empty for rule without relevant fields", func(t *testing.T) {
+ evals := RuleEvaluators(getter, wsId, emptyRule("r-1"))
+ assert.Empty(t, evals)
+ })
+
+ t.Run("returns deployment window evaluator", func(t *testing.T) {
+ evals := RuleEvaluators(getter, wsId, ruleWithDeploymentWindow("r-1"))
+ require.Len(t, evals, 1)
+ assert.Equal(t, evaluator.RuleTypeDeploymentWindow, evals[0].RuleType())
+ })
+
+ t.Run("returns approval evaluator", func(t *testing.T) {
+ evals := RuleEvaluators(getter, wsId, ruleWithApproval("r-1"))
+ require.Len(t, evals, 1)
+ assert.Equal(t, evaluator.RuleTypeApproval, evals[0].RuleType())
+ })
+
+ t.Run("returns environment progression evaluator", func(t *testing.T) {
+ evals := RuleEvaluators(getter, wsId, ruleWithEnvironmentProgression("r-1"))
+ require.Len(t, evals, 1)
+ assert.Equal(t, evaluator.RuleTypeEnvironmentProgression, evals[0].RuleType())
+ })
+
+ t.Run("returns version cooldown evaluator", func(t *testing.T) {
+ evals := RuleEvaluators(getter, wsId, ruleWithVersionCooldown("r-1"))
+ require.Len(t, evals, 1)
+ assert.Equal(t, evaluator.RuleTypeVersionCooldown, evals[0].RuleType())
+ })
+
+ t.Run("returns all evaluators for rule with all fields", func(t *testing.T) {
+ rule := &oapi.PolicyRule{
+ Id: "r-all",
+ AnyApproval: &oapi.AnyApprovalRule{MinApprovals: 1},
+ DeploymentWindow: &oapi.DeploymentWindowRule{
+ Rrule: "FREQ=DAILY;BYHOUR=9",
+ DurationMinutes: 60,
+ },
+ EnvironmentProgression: &oapi.EnvironmentProgressionRule{},
+ VersionCooldown: &oapi.VersionCooldownRule{IntervalSeconds: 300},
+ }
+ evals := RuleEvaluators(getter, wsId, rule)
+ types := evalTypes(evals)
+ assert.Contains(t, types, evaluator.RuleTypeDeploymentWindow)
+ assert.Contains(t, types, evaluator.RuleTypeApproval)
+ assert.Contains(t, types, evaluator.RuleTypeEnvironmentProgression)
+ assert.Contains(t, types, evaluator.RuleTypeVersionCooldown)
+ assert.Len(t, types, 4)
+ })
+}
diff --git a/packages/db/drizzle/0162_bizarre_jackal.sql b/packages/db/drizzle/0162_bizarre_jackal.sql
new file mode 100644
index 000000000..497c6e13e
--- /dev/null
+++ b/packages/db/drizzle/0162_bizarre_jackal.sql
@@ -0,0 +1,19 @@
+CREATE TABLE "policy_rule_summary" (
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
+ "rule_id" uuid NOT NULL,
+ "environment_id" uuid NOT NULL,
+ "version_id" uuid NOT NULL,
+ "allowed" boolean NOT NULL,
+ "action_required" boolean DEFAULT false NOT NULL,
+ "action_type" text,
+ "message" text NOT NULL,
+ "details" jsonb DEFAULT '{}' NOT NULL,
+ "satisfied_at" timestamp with time zone,
+ "next_evaluation_at" timestamp with time zone,
+ "evaluated_at" timestamp with time zone DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+ALTER TABLE "policy_rule_summary" ADD CONSTRAINT "policy_rule_summary_environment_id_environment_id_fk" FOREIGN KEY ("environment_id") REFERENCES "public"."environment"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "policy_rule_summary" ADD CONSTRAINT "policy_rule_summary_version_id_deployment_version_id_fk" FOREIGN KEY ("version_id") REFERENCES "public"."deployment_version"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+CREATE UNIQUE INDEX "policy_rule_summary_rule_id_environment_id_version_id_index" ON "policy_rule_summary" USING btree ("rule_id","environment_id","version_id");--> statement-breakpoint
+CREATE INDEX "policy_rule_summary_environment_id_version_id_index" ON "policy_rule_summary" USING btree ("environment_id","version_id");
\ No newline at end of file
diff --git a/packages/db/drizzle/meta/0162_snapshot.json b/packages/db/drizzle/meta/0162_snapshot.json
new file mode 100644
index 000000000..acaa4c91f
--- /dev/null
+++ b/packages/db/drizzle/meta/0162_snapshot.json
@@ -0,0 +1,5468 @@
+{
+ "id": "d464f8e2-1ddf-4304-ad5e-56e0af89c19a",
+ "prevId": "b0a123af-2a56-4139-9a3f-bc2d37bbb11b",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.account": {
+ "name": "account",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "account_id": {
+ "name": "account_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token_expires_at": {
+ "name": "access_token_expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token_expires_at": {
+ "name": "refresh_token_expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "account_userId_idx": {
+ "name": "account_userId_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "account_user_id_user_id_fk": {
+ "name": "account_user_id_user_id_fk",
+ "tableFrom": "account",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.session": {
+ "name": "session",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "session_token": {
+ "name": "session_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires": {
+ "name": "expires",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "session_userId_idx": {
+ "name": "session_userId_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "session_user_id_user_id_fk": {
+ "name": "session_user_id_user_id_fk",
+ "tableFrom": "session",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "session_session_token_unique": {
+ "name": "session_session_token_unique",
+ "nullsNotDistinct": false,
+ "columns": ["session_token"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user": {
+ "name": "user",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "email": {
+ "name": "email",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email_verified": {
+ "name": "email_verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "image": {
+ "name": "image",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "active_workspace_id": {
+ "name": "active_workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "null"
+ },
+ "password_hash": {
+ "name": "password_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "null"
+ },
+ "system_role": {
+ "name": "system_role",
+ "type": "system_role",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'user'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_active_workspace_id_workspace_id_fk": {
+ "name": "user_active_workspace_id_workspace_id_fk",
+ "tableFrom": "user",
+ "tableTo": "workspace",
+ "columnsFrom": ["active_workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_api_key": {
+ "name": "user_api_key",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "key_preview": {
+ "name": "key_preview",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "key_hash": {
+ "name": "key_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "key_prefix": {
+ "name": "key_prefix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "user_api_key_key_prefix_key_hash_index": {
+ "name": "user_api_key_key_prefix_key_hash_index",
+ "columns": [
+ {
+ "expression": "key_prefix",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "key_hash",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "user_api_key_user_id_user_id_fk": {
+ "name": "user_api_key_user_id_user_id_fk",
+ "tableFrom": "user_api_key",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.verification": {
+ "name": "verification",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "verification_identifier_idx": {
+ "name": "verification_identifier_idx",
+ "columns": [
+ {
+ "expression": "identifier",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.changelog_entry": {
+ "name": "changelog_entry",
+ "schema": "",
+ "columns": {
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "entity_type": {
+ "name": "entity_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "entity_id": {
+ "name": "entity_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "entity_data": {
+ "name": "entity_data",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "changelog_entry_workspace_id_workspace_id_fk": {
+ "name": "changelog_entry_workspace_id_workspace_id_fk",
+ "tableFrom": "changelog_entry",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "changelog_entry_workspace_id_entity_type_entity_id_pk": {
+ "name": "changelog_entry_workspace_id_entity_type_entity_id_pk",
+ "columns": ["workspace_id", "entity_type", "entity_id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.dashboard": {
+ "name": "dashboard",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "dashboard_workspace_id_workspace_id_fk": {
+ "name": "dashboard_workspace_id_workspace_id_fk",
+ "tableFrom": "dashboard",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.dashboard_widget": {
+ "name": "dashboard_widget",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "dashboard_id": {
+ "name": "dashboard_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "widget": {
+ "name": "widget",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "config": {
+ "name": "config",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "x": {
+ "name": "x",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "y": {
+ "name": "y",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "w": {
+ "name": "w",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "h": {
+ "name": "h",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "dashboard_widget_dashboard_id_dashboard_id_fk": {
+ "name": "dashboard_widget_dashboard_id_dashboard_id_fk",
+ "tableFrom": "dashboard_widget",
+ "tableTo": "dashboard",
+ "columnsFrom": ["dashboard_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.deployment_trace_span": {
+ "name": "deployment_trace_span",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "trace_id": {
+ "name": "trace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "span_id": {
+ "name": "span_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "parent_span_id": {
+ "name": "parent_span_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "start_time": {
+ "name": "start_time",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "end_time": {
+ "name": "end_time",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "release_target_key": {
+ "name": "release_target_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "release_id": {
+ "name": "release_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "job_id": {
+ "name": "job_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "parent_trace_id": {
+ "name": "parent_trace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "phase": {
+ "name": "phase",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "node_type": {
+ "name": "node_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "depth": {
+ "name": "depth",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sequence": {
+ "name": "sequence",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "attributes": {
+ "name": "attributes",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "events": {
+ "name": "events",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "deployment_trace_span_trace_span_idx": {
+ "name": "deployment_trace_span_trace_span_idx",
+ "columns": [
+ {
+ "expression": "trace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "span_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "deployment_trace_span_trace_id_idx": {
+ "name": "deployment_trace_span_trace_id_idx",
+ "columns": [
+ {
+ "expression": "trace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "deployment_trace_span_parent_span_id_idx": {
+ "name": "deployment_trace_span_parent_span_id_idx",
+ "columns": [
+ {
+ "expression": "parent_span_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "deployment_trace_span_workspace_id_idx": {
+ "name": "deployment_trace_span_workspace_id_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "deployment_trace_span_release_target_key_idx": {
+ "name": "deployment_trace_span_release_target_key_idx",
+ "columns": [
+ {
+ "expression": "release_target_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "deployment_trace_span_release_id_idx": {
+ "name": "deployment_trace_span_release_id_idx",
+ "columns": [
+ {
+ "expression": "release_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "deployment_trace_span_job_id_idx": {
+ "name": "deployment_trace_span_job_id_idx",
+ "columns": [
+ {
+ "expression": "job_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "deployment_trace_span_parent_trace_id_idx": {
+ "name": "deployment_trace_span_parent_trace_id_idx",
+ "columns": [
+ {
+ "expression": "parent_trace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "deployment_trace_span_created_at_idx": {
+ "name": "deployment_trace_span_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "deployment_trace_span_phase_idx": {
+ "name": "deployment_trace_span_phase_idx",
+ "columns": [
+ {
+ "expression": "phase",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "deployment_trace_span_node_type_idx": {
+ "name": "deployment_trace_span_node_type_idx",
+ "columns": [
+ {
+ "expression": "node_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "deployment_trace_span_status_idx": {
+ "name": "deployment_trace_span_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "deployment_trace_span_workspace_id_workspace_id_fk": {
+ "name": "deployment_trace_span_workspace_id_workspace_id_fk",
+ "tableFrom": "deployment_trace_span",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.deployment_variable": {
+ "name": "deployment_variable",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "deployment_id": {
+ "name": "deployment_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "default_value": {
+ "name": "default_value",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "deployment_variable_deployment_id_index": {
+ "name": "deployment_variable_deployment_id_index",
+ "columns": [
+ {
+ "expression": "deployment_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "deployment_variable_deployment_id_deployment_id_fk": {
+ "name": "deployment_variable_deployment_id_deployment_id_fk",
+ "tableFrom": "deployment_variable",
+ "tableTo": "deployment",
+ "columnsFrom": ["deployment_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "deployment_variable_deployment_id_key_unique": {
+ "name": "deployment_variable_deployment_id_key_unique",
+ "nullsNotDistinct": false,
+ "columns": ["deployment_id", "key"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.deployment_variable_value": {
+ "name": "deployment_variable_value",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "deployment_variable_id": {
+ "name": "deployment_variable_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "resource_selector": {
+ "name": "resource_selector",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "priority": {
+ "name": "priority",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "deployment_variable_value_deployment_variable_id_index": {
+ "name": "deployment_variable_value_deployment_variable_id_index",
+ "columns": [
+ {
+ "expression": "deployment_variable_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "deployment_variable_value_deployment_variable_id_deployment_variable_id_fk": {
+ "name": "deployment_variable_value_deployment_variable_id_deployment_variable_id_fk",
+ "tableFrom": "deployment_variable_value",
+ "tableTo": "deployment_variable",
+ "columnsFrom": ["deployment_variable_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.deployment_version": {
+ "name": "deployment_version",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tag": {
+ "name": "tag",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "config": {
+ "name": "config",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "job_agent_config": {
+ "name": "job_agent_config",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "deployment_id": {
+ "name": "deployment_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "deployment_version_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'ready'"
+ },
+ "message": {
+ "name": "message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp (3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "deployment_version_deployment_id_tag_index": {
+ "name": "deployment_version_deployment_id_tag_index",
+ "columns": [
+ {
+ "expression": "deployment_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "tag",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "deployment_version_created_at_idx": {
+ "name": "deployment_version_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "deployment_version_workspace_id_workspace_id_fk": {
+ "name": "deployment_version_workspace_id_workspace_id_fk",
+ "tableFrom": "deployment_version",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.computed_deployment_resource": {
+ "name": "computed_deployment_resource",
+ "schema": "",
+ "columns": {
+ "deployment_id": {
+ "name": "deployment_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "resource_id": {
+ "name": "resource_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "last_evaluated_at": {
+ "name": "last_evaluated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "computed_deployment_resource_deployment_id_deployment_id_fk": {
+ "name": "computed_deployment_resource_deployment_id_deployment_id_fk",
+ "tableFrom": "computed_deployment_resource",
+ "tableTo": "deployment",
+ "columnsFrom": ["deployment_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "computed_deployment_resource_resource_id_resource_id_fk": {
+ "name": "computed_deployment_resource_resource_id_resource_id_fk",
+ "tableFrom": "computed_deployment_resource",
+ "tableTo": "resource",
+ "columnsFrom": ["resource_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "computed_deployment_resource_deployment_id_resource_id_pk": {
+ "name": "computed_deployment_resource_deployment_id_resource_id_pk",
+ "columns": ["deployment_id", "resource_id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.deployment": {
+ "name": "deployment",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "job_agent_id": {
+ "name": "job_agent_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "job_agent_config": {
+ "name": "job_agent_config",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "job_agents": {
+ "name": "job_agents",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'"
+ },
+ "resource_selector": {
+ "name": "resource_selector",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'false'"
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "deployment_workspace_id_index": {
+ "name": "deployment_workspace_id_index",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "deployment_workspace_id_workspace_id_fk": {
+ "name": "deployment_workspace_id_workspace_id_fk",
+ "tableFrom": "deployment",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.computed_environment_resource": {
+ "name": "computed_environment_resource",
+ "schema": "",
+ "columns": {
+ "environment_id": {
+ "name": "environment_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "resource_id": {
+ "name": "resource_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "last_evaluated_at": {
+ "name": "last_evaluated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "computed_environment_resource_environment_id_environment_id_fk": {
+ "name": "computed_environment_resource_environment_id_environment_id_fk",
+ "tableFrom": "computed_environment_resource",
+ "tableTo": "environment",
+ "columnsFrom": ["environment_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "computed_environment_resource_resource_id_resource_id_fk": {
+ "name": "computed_environment_resource_resource_id_resource_id_fk",
+ "tableFrom": "computed_environment_resource",
+ "tableTo": "resource",
+ "columnsFrom": ["resource_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "computed_environment_resource_environment_id_resource_id_pk": {
+ "name": "computed_environment_resource_environment_id_resource_id_pk",
+ "columns": ["environment_id", "resource_id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.environment": {
+ "name": "environment",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "''"
+ },
+ "resource_selector": {
+ "name": "resource_selector",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'false'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "environment_workspace_id_index": {
+ "name": "environment_workspace_id_index",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "environment_workspace_id_workspace_id_fk": {
+ "name": "environment_workspace_id_workspace_id_fk",
+ "tableFrom": "environment",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.event": {
+ "name": "event",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "payload": {
+ "name": "payload",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "event_workspace_id_workspace_id_fk": {
+ "name": "event_workspace_id_workspace_id_fk",
+ "tableFrom": "event",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.resource": {
+ "name": "resource",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "version": {
+ "name": "version",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "config": {
+ "name": "config",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'{}'"
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "resource_identifier_workspace_id_index": {
+ "name": "resource_identifier_workspace_id_index",
+ "columns": [
+ {
+ "expression": "identifier",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "resource_workspace_id_active_idx": {
+ "name": "resource_workspace_id_active_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "resource_workspace_id_deleted_at_index": {
+ "name": "resource_workspace_id_deleted_at_index",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "deleted_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "resource_provider_id_resource_provider_id_fk": {
+ "name": "resource_provider_id_resource_provider_id_fk",
+ "tableFrom": "resource",
+ "tableTo": "resource_provider",
+ "columnsFrom": ["provider_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "resource_workspace_id_workspace_id_fk": {
+ "name": "resource_workspace_id_workspace_id_fk",
+ "tableFrom": "resource",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.resource_schema": {
+ "name": "resource_schema",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "version": {
+ "name": "version",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "json_schema": {
+ "name": "json_schema",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "resource_schema_version_kind_workspace_id_index": {
+ "name": "resource_schema_version_kind_workspace_id_index",
+ "columns": [
+ {
+ "expression": "version",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "resource_schema_workspace_id_workspace_id_fk": {
+ "name": "resource_schema_workspace_id_workspace_id_fk",
+ "tableFrom": "resource_schema",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.resource_provider": {
+ "name": "resource_provider",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ }
+ },
+ "indexes": {
+ "resource_provider_workspace_id_name_index": {
+ "name": "resource_provider_workspace_id_name_index",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "resource_provider_workspace_id_workspace_id_fk": {
+ "name": "resource_provider_workspace_id_workspace_id_fk",
+ "tableFrom": "resource_provider",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.system": {
+ "name": "system",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "system_workspace_id_workspace_id_fk": {
+ "name": "system_workspace_id_workspace_id_fk",
+ "tableFrom": "system",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.system_deployment": {
+ "name": "system_deployment",
+ "schema": "",
+ "columns": {
+ "system_id": {
+ "name": "system_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "deployment_id": {
+ "name": "deployment_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "system_deployment_system_id_system_id_fk": {
+ "name": "system_deployment_system_id_system_id_fk",
+ "tableFrom": "system_deployment",
+ "tableTo": "system",
+ "columnsFrom": ["system_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "system_deployment_deployment_id_deployment_id_fk": {
+ "name": "system_deployment_deployment_id_deployment_id_fk",
+ "tableFrom": "system_deployment",
+ "tableTo": "deployment",
+ "columnsFrom": ["deployment_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "system_deployment_system_id_deployment_id_pk": {
+ "name": "system_deployment_system_id_deployment_id_pk",
+ "columns": ["system_id", "deployment_id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.system_environment": {
+ "name": "system_environment",
+ "schema": "",
+ "columns": {
+ "system_id": {
+ "name": "system_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "system_environment_system_id_system_id_fk": {
+ "name": "system_environment_system_id_system_id_fk",
+ "tableFrom": "system_environment",
+ "tableTo": "system",
+ "columnsFrom": ["system_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "system_environment_environment_id_environment_id_fk": {
+ "name": "system_environment_environment_id_environment_id_fk",
+ "tableFrom": "system_environment",
+ "tableTo": "environment",
+ "columnsFrom": ["environment_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "system_environment_system_id_environment_id_pk": {
+ "name": "system_environment_system_id_environment_id_pk",
+ "columns": ["system_id", "environment_id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.team": {
+ "name": "team",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_workspace_id_workspace_id_fk": {
+ "name": "team_workspace_id_workspace_id_fk",
+ "tableFrom": "team",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.team_member": {
+ "name": "team_member",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "team_id": {
+ "name": "team_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "team_member_team_id_user_id_index": {
+ "name": "team_member_team_id_user_id_index",
+ "columns": [
+ {
+ "expression": "team_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "team_member_team_id_team_id_fk": {
+ "name": "team_member_team_id_team_id_fk",
+ "tableFrom": "team_member",
+ "tableTo": "team",
+ "columnsFrom": ["team_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "team_member_user_id_user_id_fk": {
+ "name": "team_member_user_id_user_id_fk",
+ "tableFrom": "team_member",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.job": {
+ "name": "job",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "job_agent_id": {
+ "name": "job_agent_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "job_agent_config": {
+ "name": "job_agent_config",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "trace_token": {
+ "name": "trace_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dispatch_context": {
+ "name": "dispatch_context",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "status": {
+ "name": "status",
+ "type": "job_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "message": {
+ "name": "message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "reason": {
+ "name": "reason",
+ "type": "job_reason",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'policy_passing'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "job_created_at_idx": {
+ "name": "job_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "job_status_idx": {
+ "name": "job_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "job_external_id_idx": {
+ "name": "job_external_id_idx",
+ "columns": [
+ {
+ "expression": "external_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "job_job_agent_id_job_agent_id_fk": {
+ "name": "job_job_agent_id_job_agent_id_fk",
+ "tableFrom": "job",
+ "tableTo": "job_agent",
+ "columnsFrom": ["job_agent_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.job_metadata": {
+ "name": "job_metadata",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "job_id": {
+ "name": "job_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "job_metadata_key_job_id_index": {
+ "name": "job_metadata_key_job_id_index",
+ "columns": [
+ {
+ "expression": "key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "job_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "job_metadata_job_id_idx": {
+ "name": "job_metadata_job_id_idx",
+ "columns": [
+ {
+ "expression": "job_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "job_metadata_job_id_job_id_fk": {
+ "name": "job_metadata_job_id_job_id_fk",
+ "tableFrom": "job_metadata",
+ "tableTo": "job",
+ "columnsFrom": ["job_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.job_variable": {
+ "name": "job_variable",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "job_id": {
+ "name": "job_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sensitive": {
+ "name": "sensitive",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ }
+ },
+ "indexes": {
+ "job_variable_job_id_key_index": {
+ "name": "job_variable_job_id_key_index",
+ "columns": [
+ {
+ "expression": "job_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "job_variable_job_id_job_id_fk": {
+ "name": "job_variable_job_id_job_id_fk",
+ "tableFrom": "job_variable",
+ "tableTo": "job",
+ "columnsFrom": ["job_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workspace": {
+ "name": "workspace",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slug": {
+ "name": "slug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "workspace_slug_unique": {
+ "name": "workspace_slug_unique",
+ "nullsNotDistinct": false,
+ "columns": ["slug"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workspace_email_domain_matching": {
+ "name": "workspace_email_domain_matching",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "domain": {
+ "name": "domain",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role_id": {
+ "name": "role_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "verified": {
+ "name": "verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "verification_code": {
+ "name": "verification_code",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "verification_email": {
+ "name": "verification_email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "workspace_email_domain_matching_workspace_id_domain_index": {
+ "name": "workspace_email_domain_matching_workspace_id_domain_index",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "domain",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "workspace_email_domain_matching_workspace_id_workspace_id_fk": {
+ "name": "workspace_email_domain_matching_workspace_id_workspace_id_fk",
+ "tableFrom": "workspace_email_domain_matching",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workspace_email_domain_matching_role_id_role_id_fk": {
+ "name": "workspace_email_domain_matching_role_id_role_id_fk",
+ "tableFrom": "workspace_email_domain_matching",
+ "tableTo": "role",
+ "columnsFrom": ["role_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workspace_invite_token": {
+ "name": "workspace_invite_token",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "role_id": {
+ "name": "role_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "workspace_invite_token_role_id_role_id_fk": {
+ "name": "workspace_invite_token_role_id_role_id_fk",
+ "tableFrom": "workspace_invite_token",
+ "tableTo": "role",
+ "columnsFrom": ["role_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workspace_invite_token_workspace_id_workspace_id_fk": {
+ "name": "workspace_invite_token_workspace_id_workspace_id_fk",
+ "tableFrom": "workspace_invite_token",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workspace_invite_token_created_by_user_id_fk": {
+ "name": "workspace_invite_token_created_by_user_id_fk",
+ "tableFrom": "workspace_invite_token",
+ "tableTo": "user",
+ "columnsFrom": ["created_by"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "workspace_invite_token_token_unique": {
+ "name": "workspace_invite_token_token_unique",
+ "nullsNotDistinct": false,
+ "columns": ["token"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.entity_role": {
+ "name": "entity_role",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "role_id": {
+ "name": "role_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "entity_type": {
+ "name": "entity_type",
+ "type": "entity_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "entity_id": {
+ "name": "entity_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope_id": {
+ "name": "scope_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope_type": {
+ "name": "scope_type",
+ "type": "scope_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "entity_role_role_id_entity_type_entity_id_scope_id_scope_type_index": {
+ "name": "entity_role_role_id_entity_type_entity_id_scope_id_scope_type_index",
+ "columns": [
+ {
+ "expression": "role_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "entity_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "entity_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "scope_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "scope_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "entity_role_role_id_role_id_fk": {
+ "name": "entity_role_role_id_role_id_fk",
+ "tableFrom": "entity_role",
+ "tableTo": "role",
+ "columnsFrom": ["role_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.role": {
+ "name": "role",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "role_workspace_id_workspace_id_fk": {
+ "name": "role_workspace_id_workspace_id_fk",
+ "tableFrom": "role",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.role_permission": {
+ "name": "role_permission",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "role_id": {
+ "name": "role_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "permission": {
+ "name": "permission",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "role_permission_role_id_permission_index": {
+ "name": "role_permission_role_id_permission_index",
+ "columns": [
+ {
+ "expression": "role_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "permission",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "role_permission_role_id_role_id_fk": {
+ "name": "role_permission_role_id_role_id_fk",
+ "tableFrom": "role_permission",
+ "tableTo": "role",
+ "columnsFrom": ["role_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.release": {
+ "name": "release",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "resource_id": {
+ "name": "resource_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "deployment_id": {
+ "name": "deployment_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "version_id": {
+ "name": "version_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "release_resource_id_environment_id_deployment_id_index": {
+ "name": "release_resource_id_environment_id_deployment_id_index",
+ "columns": [
+ {
+ "expression": "resource_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "environment_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "deployment_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "release_resource_id_resource_id_fk": {
+ "name": "release_resource_id_resource_id_fk",
+ "tableFrom": "release",
+ "tableTo": "resource",
+ "columnsFrom": ["resource_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "release_environment_id_environment_id_fk": {
+ "name": "release_environment_id_environment_id_fk",
+ "tableFrom": "release",
+ "tableTo": "environment",
+ "columnsFrom": ["environment_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "release_deployment_id_deployment_id_fk": {
+ "name": "release_deployment_id_deployment_id_fk",
+ "tableFrom": "release",
+ "tableTo": "deployment",
+ "columnsFrom": ["deployment_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "release_version_id_deployment_version_id_fk": {
+ "name": "release_version_id_deployment_version_id_fk",
+ "tableFrom": "release",
+ "tableTo": "deployment_version",
+ "columnsFrom": ["version_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.release_job": {
+ "name": "release_job",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "job_id": {
+ "name": "job_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "release_id": {
+ "name": "release_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "release_job_release_id_job_id_index": {
+ "name": "release_job_release_id_job_id_index",
+ "columns": [
+ {
+ "expression": "release_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "job_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "release_id_job_id_index": {
+ "name": "release_id_job_id_index",
+ "columns": [
+ {
+ "expression": "release_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "job_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "release_job_job_id_job_id_fk": {
+ "name": "release_job_job_id_job_id_fk",
+ "tableFrom": "release_job",
+ "tableTo": "job",
+ "columnsFrom": ["job_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "release_job_release_id_release_id_fk": {
+ "name": "release_job_release_id_release_id_fk",
+ "tableFrom": "release_job",
+ "tableTo": "release",
+ "columnsFrom": ["release_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.release_variable": {
+ "name": "release_variable",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "release_id": {
+ "name": "release_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "encrypted": {
+ "name": "encrypted",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "release_variable_release_id_key_index": {
+ "name": "release_variable_release_id_key_index",
+ "columns": [
+ {
+ "expression": "release_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "release_variable_release_id_release_id_fk": {
+ "name": "release_variable_release_id_release_id_fk",
+ "tableFrom": "release_variable",
+ "tableTo": "release",
+ "columnsFrom": ["release_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.reconcile_work_payload": {
+ "name": "reconcile_work_payload",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigint",
+ "primaryKey": true,
+ "notNull": true,
+ "identity": {
+ "type": "byDefault",
+ "name": "reconcile_work_payload_id_seq",
+ "schema": "public",
+ "increment": "1",
+ "startWith": "1",
+ "minValue": "1",
+ "maxValue": "9223372036854775807",
+ "cache": "1",
+ "cycle": false
+ }
+ },
+ "scope_ref": {
+ "name": "scope_ref",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "payload_type": {
+ "name": "payload_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "payload_key": {
+ "name": "payload_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "payload": {
+ "name": "payload",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "attempt_count": {
+ "name": "attempt_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "last_error": {
+ "name": "last_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "reconcile_work_payload_scope_ref_payload_type_payload_key_index": {
+ "name": "reconcile_work_payload_scope_ref_payload_type_payload_key_index",
+ "columns": [
+ {
+ "expression": "scope_ref",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "payload_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "payload_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "reconcile_work_payload_scope_ref_index": {
+ "name": "reconcile_work_payload_scope_ref_index",
+ "columns": [
+ {
+ "expression": "scope_ref",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "reconcile_work_payload_scope_ref_reconcile_work_scope_id_fk": {
+ "name": "reconcile_work_payload_scope_ref_reconcile_work_scope_id_fk",
+ "tableFrom": "reconcile_work_payload",
+ "tableTo": "reconcile_work_scope",
+ "columnsFrom": ["scope_ref"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.reconcile_work_scope": {
+ "name": "reconcile_work_scope",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigint",
+ "primaryKey": true,
+ "notNull": true,
+ "identity": {
+ "type": "byDefault",
+ "name": "reconcile_work_scope_id_seq",
+ "schema": "public",
+ "increment": "1",
+ "startWith": "1",
+ "minValue": "1",
+ "maxValue": "9223372036854775807",
+ "cache": "1",
+ "cycle": false
+ }
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope_type": {
+ "name": "scope_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "scope_id": {
+ "name": "scope_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "event_ts": {
+ "name": "event_ts",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "priority": {
+ "name": "priority",
+ "type": "smallint",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 100
+ },
+ "not_before": {
+ "name": "not_before",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "claimed_by": {
+ "name": "claimed_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "claimed_until": {
+ "name": "claimed_until",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "reconcile_work_scope_workspace_id_kind_scope_type_scope_id_index": {
+ "name": "reconcile_work_scope_workspace_id_kind_scope_type_scope_id_index",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "scope_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "scope_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "reconcile_work_scope_kind_not_before_priority_event_ts_claimed_until_index": {
+ "name": "reconcile_work_scope_kind_not_before_priority_event_ts_claimed_until_index",
+ "columns": [
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "not_before",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "priority",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "event_ts",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "claimed_until",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.policy": {
+ "name": "policy",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "selector": {
+ "name": "selector",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'true'"
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "priority": {
+ "name": "priority",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "policy_workspace_id_index": {
+ "name": "policy_workspace_id_index",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "policy_workspace_id_workspace_id_fk": {
+ "name": "policy_workspace_id_workspace_id_fk",
+ "tableFrom": "policy",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.policy_rule_any_approval": {
+ "name": "policy_rule_any_approval",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "policy_id": {
+ "name": "policy_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "min_approvals": {
+ "name": "min_approvals",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "policy_rule_any_approval_policy_id_policy_id_fk": {
+ "name": "policy_rule_any_approval_policy_id_policy_id_fk",
+ "tableFrom": "policy_rule_any_approval",
+ "tableTo": "policy",
+ "columnsFrom": ["policy_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.policy_rule_deployment_dependency": {
+ "name": "policy_rule_deployment_dependency",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "policy_id": {
+ "name": "policy_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "depends_on": {
+ "name": "depends_on",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "policy_rule_deployment_dependency_policy_id_policy_id_fk": {
+ "name": "policy_rule_deployment_dependency_policy_id_policy_id_fk",
+ "tableFrom": "policy_rule_deployment_dependency",
+ "tableTo": "policy",
+ "columnsFrom": ["policy_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.policy_rule_deployment_window": {
+ "name": "policy_rule_deployment_window",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "policy_id": {
+ "name": "policy_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "allow_window": {
+ "name": "allow_window",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "duration_minutes": {
+ "name": "duration_minutes",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rrule": {
+ "name": "rrule",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "timezone": {
+ "name": "timezone",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "policy_rule_deployment_window_policy_id_policy_id_fk": {
+ "name": "policy_rule_deployment_window_policy_id_policy_id_fk",
+ "tableFrom": "policy_rule_deployment_window",
+ "tableTo": "policy",
+ "columnsFrom": ["policy_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.policy_rule_environment_progression": {
+ "name": "policy_rule_environment_progression",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "policy_id": {
+ "name": "policy_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "depends_on_environment_selector": {
+ "name": "depends_on_environment_selector",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "maximum_age_hours": {
+ "name": "maximum_age_hours",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "minimum_soak_time_minutes": {
+ "name": "minimum_soak_time_minutes",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "minimum_success_percentage": {
+ "name": "minimum_success_percentage",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "success_statuses": {
+ "name": "success_statuses",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "policy_rule_environment_progression_policy_id_policy_id_fk": {
+ "name": "policy_rule_environment_progression_policy_id_policy_id_fk",
+ "tableFrom": "policy_rule_environment_progression",
+ "tableTo": "policy",
+ "columnsFrom": ["policy_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.policy_rule_gradual_rollout": {
+ "name": "policy_rule_gradual_rollout",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "policy_id": {
+ "name": "policy_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rollout_type": {
+ "name": "rollout_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "time_scale_interval": {
+ "name": "time_scale_interval",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "policy_rule_gradual_rollout_policy_id_policy_id_fk": {
+ "name": "policy_rule_gradual_rollout_policy_id_policy_id_fk",
+ "tableFrom": "policy_rule_gradual_rollout",
+ "tableTo": "policy",
+ "columnsFrom": ["policy_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.policy_rule_retry": {
+ "name": "policy_rule_retry",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "policy_id": {
+ "name": "policy_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "max_retries": {
+ "name": "max_retries",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "backoff_seconds": {
+ "name": "backoff_seconds",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "backoff_strategy": {
+ "name": "backoff_strategy",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "max_backoff_seconds": {
+ "name": "max_backoff_seconds",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "retry_on_statuses": {
+ "name": "retry_on_statuses",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "policy_rule_retry_policy_id_policy_id_fk": {
+ "name": "policy_rule_retry_policy_id_policy_id_fk",
+ "tableFrom": "policy_rule_retry",
+ "tableTo": "policy",
+ "columnsFrom": ["policy_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.policy_rule_rollback": {
+ "name": "policy_rule_rollback",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "policy_id": {
+ "name": "policy_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "on_job_statuses": {
+ "name": "on_job_statuses",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "on_verification_failure": {
+ "name": "on_verification_failure",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "policy_rule_rollback_policy_id_policy_id_fk": {
+ "name": "policy_rule_rollback_policy_id_policy_id_fk",
+ "tableFrom": "policy_rule_rollback",
+ "tableTo": "policy",
+ "columnsFrom": ["policy_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.policy_rule_verification": {
+ "name": "policy_rule_verification",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "policy_id": {
+ "name": "policy_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "metrics": {
+ "name": "metrics",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'"
+ },
+ "trigger_on": {
+ "name": "trigger_on",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "policy_rule_verification_policy_id_policy_id_fk": {
+ "name": "policy_rule_verification_policy_id_policy_id_fk",
+ "tableFrom": "policy_rule_verification",
+ "tableTo": "policy",
+ "columnsFrom": ["policy_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.policy_rule_version_cooldown": {
+ "name": "policy_rule_version_cooldown",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "policy_id": {
+ "name": "policy_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "interval_seconds": {
+ "name": "interval_seconds",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "policy_rule_version_cooldown_policy_id_policy_id_fk": {
+ "name": "policy_rule_version_cooldown_policy_id_policy_id_fk",
+ "tableFrom": "policy_rule_version_cooldown",
+ "tableTo": "policy",
+ "columnsFrom": ["policy_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.policy_rule_version_selector": {
+ "name": "policy_rule_version_selector",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "policy_id": {
+ "name": "policy_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "selector": {
+ "name": "selector",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "policy_rule_version_selector_policy_id_policy_id_fk": {
+ "name": "policy_rule_version_selector_policy_id_policy_id_fk",
+ "tableFrom": "policy_rule_version_selector",
+ "tableTo": "policy",
+ "columnsFrom": ["policy_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_approval_record": {
+ "name": "user_approval_record",
+ "schema": "",
+ "columns": {
+ "version_id": {
+ "name": "version_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "reason": {
+ "name": "reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "user_approval_record_version_id_user_id_environment_id_pk": {
+ "name": "user_approval_record_version_id_user_id_environment_id_pk",
+ "columns": ["version_id", "user_id", "environment_id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.resource_variable": {
+ "name": "resource_variable",
+ "schema": "",
+ "columns": {
+ "resource_id": {
+ "name": "resource_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "resource_variable_resource_id_resource_id_fk": {
+ "name": "resource_variable_resource_id_resource_id_fk",
+ "tableFrom": "resource_variable",
+ "tableTo": "resource",
+ "columnsFrom": ["resource_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "resource_variable_resource_id_key_pk": {
+ "name": "resource_variable_resource_id_key_pk",
+ "columns": ["resource_id", "key"]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workflow": {
+ "name": "workflow",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "inputs": {
+ "name": "inputs",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'"
+ },
+ "jobs": {
+ "name": "jobs",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'"
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "workflow_workspace_id_workspace_id_fk": {
+ "name": "workflow_workspace_id_workspace_id_fk",
+ "tableFrom": "workflow",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workflow_job": {
+ "name": "workflow_job",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "workflow_run_id": {
+ "name": "workflow_run_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ref": {
+ "name": "ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "config": {
+ "name": "config",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "index": {
+ "name": "index",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "workflow_job_workflow_run_id_workflow_run_id_fk": {
+ "name": "workflow_job_workflow_run_id_workflow_run_id_fk",
+ "tableFrom": "workflow_job",
+ "tableTo": "workflow_run",
+ "columnsFrom": ["workflow_run_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workflow_job_template": {
+ "name": "workflow_job_template",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ref": {
+ "name": "ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "config": {
+ "name": "config",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "if_condition": {
+ "name": "if_condition",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "matrix": {
+ "name": "matrix",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "workflow_job_template_workflow_id_workflow_id_fk": {
+ "name": "workflow_job_template_workflow_id_workflow_id_fk",
+ "tableFrom": "workflow_job_template",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workflow_run": {
+ "name": "workflow_run",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "inputs": {
+ "name": "inputs",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "workflow_run_workflow_id_workflow_id_fk": {
+ "name": "workflow_run_workflow_id_workflow_id_fk",
+ "tableFrom": "workflow_run",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.policy_skip": {
+ "name": "policy_skip",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "reason": {
+ "name": "reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "resource_id": {
+ "name": "resource_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rule_id": {
+ "name": "rule_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "version_id": {
+ "name": "version_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.policy_rule_summary": {
+ "name": "policy_rule_summary",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "rule_id": {
+ "name": "rule_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "version_id": {
+ "name": "version_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "allowed": {
+ "name": "allowed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "action_required": {
+ "name": "action_required",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "action_type": {
+ "name": "action_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "message": {
+ "name": "message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "details": {
+ "name": "details",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "satisfied_at": {
+ "name": "satisfied_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "next_evaluation_at": {
+ "name": "next_evaluation_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "evaluated_at": {
+ "name": "evaluated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "policy_rule_summary_rule_id_environment_id_version_id_index": {
+ "name": "policy_rule_summary_rule_id_environment_id_version_id_index",
+ "columns": [
+ {
+ "expression": "rule_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "environment_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "version_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "policy_rule_summary_environment_id_version_id_index": {
+ "name": "policy_rule_summary_environment_id_version_id_index",
+ "columns": [
+ {
+ "expression": "environment_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "version_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "policy_rule_summary_environment_id_environment_id_fk": {
+ "name": "policy_rule_summary_environment_id_environment_id_fk",
+ "tableFrom": "policy_rule_summary",
+ "tableTo": "environment",
+ "columnsFrom": ["environment_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "policy_rule_summary_version_id_deployment_version_id_fk": {
+ "name": "policy_rule_summary_version_id_deployment_version_id_fk",
+ "tableFrom": "policy_rule_summary",
+ "tableTo": "deployment_version",
+ "columnsFrom": ["version_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.job_verification_metric_measurement": {
+ "name": "job_verification_metric_measurement",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "job_verification_metric_status_id": {
+ "name": "job_verification_metric_status_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "data": {
+ "name": "data",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "measured_at": {
+ "name": "measured_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "message": {
+ "name": "message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "status": {
+ "name": "status",
+ "type": "job_verification_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "job_verification_metric_measurement_job_verification_metric_status_id_index": {
+ "name": "job_verification_metric_measurement_job_verification_metric_status_id_index",
+ "columns": [
+ {
+ "expression": "job_verification_metric_status_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "job_verification_metric_measurement_job_verification_metric_status_id_job_verification_metric_id_fk": {
+ "name": "job_verification_metric_measurement_job_verification_metric_status_id_job_verification_metric_id_fk",
+ "tableFrom": "job_verification_metric_measurement",
+ "tableTo": "job_verification_metric",
+ "columnsFrom": ["job_verification_metric_status_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.job_verification_metric": {
+ "name": "job_verification_metric",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "job_id": {
+ "name": "job_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "interval_seconds": {
+ "name": "interval_seconds",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "count": {
+ "name": "count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "success_condition": {
+ "name": "success_condition",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "success_threshold": {
+ "name": "success_threshold",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "failure_condition": {
+ "name": "failure_condition",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'false'"
+ },
+ "failure_threshold": {
+ "name": "failure_threshold",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "job_verification_metric_job_id_index": {
+ "name": "job_verification_metric_job_id_index",
+ "columns": [
+ {
+ "expression": "job_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.policy_rule_job_verification_metric": {
+ "name": "policy_rule_job_verification_metric",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "trigger_on": {
+ "name": "trigger_on",
+ "type": "job_verification_trigger_on",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'jobSuccess'"
+ },
+ "policy_id": {
+ "name": "policy_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "interval_seconds": {
+ "name": "interval_seconds",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "count": {
+ "name": "count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "success_condition": {
+ "name": "success_condition",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "success_threshold": {
+ "name": "success_threshold",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "failure_condition": {
+ "name": "failure_condition",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'false'"
+ },
+ "failure_threshold": {
+ "name": "failure_threshold",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "policy_rule_job_verification_metric_policy_id_policy_id_fk": {
+ "name": "policy_rule_job_verification_metric_policy_id_policy_id_fk",
+ "tableFrom": "policy_rule_job_verification_metric",
+ "tableTo": "policy",
+ "columnsFrom": ["policy_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.computed_entity_relationship": {
+ "name": "computed_entity_relationship",
+ "schema": "",
+ "columns": {
+ "rule_id": {
+ "name": "rule_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "from_entity_type": {
+ "name": "from_entity_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "from_entity_id": {
+ "name": "from_entity_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "to_entity_type": {
+ "name": "to_entity_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "to_entity_id": {
+ "name": "to_entity_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "last_evaluated_at": {
+ "name": "last_evaluated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "computed_entity_relationship_rule_id_relationship_rule_id_fk": {
+ "name": "computed_entity_relationship_rule_id_relationship_rule_id_fk",
+ "tableFrom": "computed_entity_relationship",
+ "tableTo": "relationship_rule",
+ "columnsFrom": ["rule_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "computed_entity_relationship_rule_id_from_entity_type_from_entity_id_to_entity_type_to_entity_id_pk": {
+ "name": "computed_entity_relationship_rule_id_from_entity_type_from_entity_id_to_entity_type_to_entity_id_pk",
+ "columns": [
+ "rule_id",
+ "from_entity_type",
+ "from_entity_id",
+ "to_entity_type",
+ "to_entity_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.relationship_rule": {
+ "name": "relationship_rule",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "reference": {
+ "name": "reference",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "cel": {
+ "name": "cel",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'{}'"
+ }
+ },
+ "indexes": {
+ "relationship_rule_workspace_id_reference_index": {
+ "name": "relationship_rule_workspace_id_reference_index",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "reference",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "relationship_rule_workspace_id_index": {
+ "name": "relationship_rule_workspace_id_index",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "relationship_rule_workspace_id_workspace_id_fk": {
+ "name": "relationship_rule_workspace_id_workspace_id_fk",
+ "tableFrom": "relationship_rule",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.job_agent": {
+ "name": "job_agent",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "config": {
+ "name": "config",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ }
+ },
+ "indexes": {
+ "job_agent_workspace_id_name_index": {
+ "name": "job_agent_workspace_id_name_index",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "job_agent_workspace_id_workspace_id_fk": {
+ "name": "job_agent_workspace_id_workspace_id_fk",
+ "tableFrom": "job_agent",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {
+ "public.system_role": {
+ "name": "system_role",
+ "schema": "public",
+ "values": ["user", "admin"]
+ },
+ "public.deployment_version_status": {
+ "name": "deployment_version_status",
+ "schema": "public",
+ "values": [
+ "unspecified",
+ "building",
+ "ready",
+ "failed",
+ "rejected",
+ "paused"
+ ]
+ },
+ "public.job_reason": {
+ "name": "job_reason",
+ "schema": "public",
+ "values": [
+ "policy_passing",
+ "policy_override",
+ "env_policy_override",
+ "config_policy_override"
+ ]
+ },
+ "public.job_status": {
+ "name": "job_status",
+ "schema": "public",
+ "values": [
+ "cancelled",
+ "skipped",
+ "in_progress",
+ "action_required",
+ "pending",
+ "failure",
+ "invalid_job_agent",
+ "invalid_integration",
+ "external_run_not_found",
+ "successful"
+ ]
+ },
+ "public.entity_type": {
+ "name": "entity_type",
+ "schema": "public",
+ "values": ["user", "team"]
+ },
+ "public.scope_type": {
+ "name": "scope_type",
+ "schema": "public",
+ "values": [
+ "deploymentVersion",
+ "resource",
+ "resourceProvider",
+ "workspace",
+ "environment",
+ "system",
+ "deployment"
+ ]
+ },
+ "public.job_verification_status": {
+ "name": "job_verification_status",
+ "schema": "public",
+ "values": ["failed", "inconclusive", "passed"]
+ },
+ "public.job_verification_trigger_on": {
+ "name": "job_verification_trigger_on",
+ "schema": "public",
+ "values": ["jobCreated", "jobStarted", "jobSuccess", "jobFailure"]
+ }
+ },
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
diff --git a/packages/db/drizzle/meta/_journal.json b/packages/db/drizzle/meta/_journal.json
index 3b6b07b04..3c891c5e3 100644
--- a/packages/db/drizzle/meta/_journal.json
+++ b/packages/db/drizzle/meta/_journal.json
@@ -1135,6 +1135,13 @@
"when": 1772693395056,
"tag": "0161_ambitious_living_mummy",
"breakpoints": true
+ },
+ {
+ "idx": 162,
+ "version": "7",
+ "when": 1772855230121,
+ "tag": "0162_bizarre_jackal",
+ "breakpoints": true
}
]
}
diff --git a/packages/db/src/reconcilers/index.ts b/packages/db/src/reconcilers/index.ts
index 81693bf79..d195ef5c6 100644
--- a/packages/db/src/reconcilers/index.ts
+++ b/packages/db/src/reconcilers/index.ts
@@ -220,6 +220,43 @@ export async function enqueueManyRelationshipEval(
);
}
+// ---------------------------------------------------------------------------
+// Policy summary
+// ---------------------------------------------------------------------------
+
+const POLICY_SUMMARY_KIND = "policy-summary";
+
+export async function enqueuePolicySummary(
+ db: Tx,
+ params: { workspaceId: string; environmentId: string; versionId: string },
+): Promise {
+ return enqueue(db, {
+ workspaceId: params.workspaceId,
+ kind: POLICY_SUMMARY_KIND,
+ scopeType: "environment-version",
+ scopeId: `${params.environmentId}:${params.versionId}`,
+ });
+}
+
+export async function enqueueManyPolicySummary(
+ db: Tx,
+ items: Array<{
+ workspaceId: string;
+ environmentId: string;
+ versionId: string;
+ }>,
+): Promise {
+ return enqueueMany(
+ db,
+ items.map((item) => ({
+ workspaceId: item.workspaceId,
+ kind: POLICY_SUMMARY_KIND,
+ scopeType: "environment-version",
+ scopeId: `${item.environmentId}:${item.versionId}`,
+ })),
+ );
+}
+
// ---------------------------------------------------------------------------
// Desired release
// ---------------------------------------------------------------------------
diff --git a/packages/db/src/schema/index.ts b/packages/db/src/schema/index.ts
index 383c58937..8d99f3c93 100644
--- a/packages/db/src/schema/index.ts
+++ b/packages/db/src/schema/index.ts
@@ -22,5 +22,6 @@ export * from "./resource-variable.js";
export * from "./deployment-variable.js";
export * from "./workflow.js";
export * from "./policy-skip.js";
+export * from "./policy-rule-summary.js";
export * from "./job-verification-metric.js";
export * from "./relationships.js";
diff --git a/packages/db/src/schema/policy-rule-summary.ts b/packages/db/src/schema/policy-rule-summary.ts
new file mode 100644
index 000000000..295308daa
--- /dev/null
+++ b/packages/db/src/schema/policy-rule-summary.ts
@@ -0,0 +1,59 @@
+import { relations } from "drizzle-orm";
+import {
+ boolean,
+ index,
+ jsonb,
+ pgTable,
+ text,
+ timestamp,
+ uniqueIndex,
+ uuid,
+} from "drizzle-orm/pg-core";
+
+import { deploymentVersion } from "./deployment-version.js";
+import { environment } from "./environment.js";
+
+export const policyRuleSummary = pgTable(
+ "policy_rule_summary",
+ {
+ id: uuid("id").primaryKey().defaultRandom(),
+ ruleId: uuid("rule_id").notNull(),
+
+ environmentId: uuid("environment_id")
+ .notNull()
+ .references(() => environment.id, { onDelete: "cascade" }),
+ versionId: uuid("version_id")
+ .notNull()
+ .references(() => deploymentVersion.id, { onDelete: "cascade" }),
+
+ allowed: boolean("allowed").notNull(),
+ actionRequired: boolean("action_required").notNull().default(false),
+ actionType: text("action_type"),
+ message: text("message").notNull(),
+ details: jsonb("details").notNull().default("{}"),
+
+ satisfiedAt: timestamp("satisfied_at", { withTimezone: true }),
+ nextEvaluationAt: timestamp("next_evaluation_at", { withTimezone: true }),
+ evaluatedAt: timestamp("evaluated_at", { withTimezone: true })
+ .notNull()
+ .defaultNow(),
+ },
+ (t) => [
+ uniqueIndex().on(t.ruleId, t.environmentId, t.versionId),
+ index().on(t.environmentId, t.versionId),
+ ],
+);
+
+export const policyRuleSummaryRelations = relations(
+ policyRuleSummary,
+ ({ one }) => ({
+ environment: one(environment, {
+ fields: [policyRuleSummary.environmentId],
+ references: [environment.id],
+ }),
+ version: one(deploymentVersion, {
+ fields: [policyRuleSummary.versionId],
+ references: [deploymentVersion.id],
+ }),
+ }),
+);
diff --git a/packages/trpc/src/routes/reconcile.ts b/packages/trpc/src/routes/reconcile.ts
index 58c840661..39ee0c5a3 100644
--- a/packages/trpc/src/routes/reconcile.ts
+++ b/packages/trpc/src/routes/reconcile.ts
@@ -15,6 +15,7 @@ import {
enqueueDeploymentSelectorEval,
enqueueEnvironmentSelectorEval,
enqueueManyRelationshipEval,
+ enqueuePolicySummary,
enqueueRelationshipEval,
} from "@ctrlplane/db/reconcilers";
import * as schema from "@ctrlplane/db/schema";
@@ -133,6 +134,16 @@ export const reconcileRouter = router({
return { enqueued: items.length };
}),
+ triggerPolicySummary: protectedProcedure
+ .input(
+ z.object({
+ workspaceId: z.string().uuid(),
+ environmentId: z.string().uuid(),
+ versionId: z.string().uuid(),
+ }),
+ )
+ .mutation(({ ctx, input }) => enqueuePolicySummary(ctx.db, input)),
+
listWorkScopes: protectedProcedure
.input(
z.object({