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 ( +
{ + e.preventDefault(); + mutation.mutate({ workspaceId, environmentId, versionId }); + }} + className="flex flex-col gap-4" + > +
+ + setEnvironmentId(e.target.value)} + required + /> +
+
+ + setVersionId(e.target.value)} + required + /> +
+ + + + {mutation.error && ( +

{mutation.error.message}

+ )} +
+ ); +} + 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({