diff --git a/apps/api/src/routes/v1/workspaces/workflows.ts b/apps/api/src/routes/v1/workspaces/workflows.ts index 6c2a8804d..623841f22 100644 --- a/apps/api/src/routes/v1/workspaces/workflows.ts +++ b/apps/api/src/routes/v1/workspaces/workflows.ts @@ -26,7 +26,7 @@ function buildWorkflow( body: { name: string; inputs: Workflow["inputs"]; - jobs: Omit[]; + jobs: Omit[]; }, ): Workflow { return { @@ -34,10 +34,9 @@ function buildWorkflow( name: body.name, inputs: body.inputs, jobs: body.jobs.map((job) => ({ + ...job, id: uuidv4(), - name: job.name, - ref: job.ref, - config: job.config, + workflowId: id, })), }; } diff --git a/apps/web/app/api/openapi.ts b/apps/web/app/api/openapi.ts index d8f6814e0..4d4dc43af 100644 --- a/apps/web/app/api/openapi.ts +++ b/apps/web/app/api/openapi.ts @@ -82,109 +82,117 @@ export interface paths { patch: operations["updateWorkspace"]; trace?: never; }; - "/v1/workspaces/{workspaceId}/deployment-versions/{deploymentVersionId}/user-approval-records": { + "/v1/workspaces/{workspaceId}/deployment-variable-values/{valueId}": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - /** Upsert user approval record */ - put: operations["requestUserApprovalRecordUpsert"]; + /** + * Get deployment variable value + * @description Returns a specific variable value override by ID. + */ + get: operations["getDeploymentVariableValue"]; + /** + * Upsert deployment variable value + * @description Creates or updates a variable value override by ID. + */ + put: operations["requestDeploymentVariableValueUpsert"]; post?: never; - delete?: never; + /** + * Delete deployment variable value + * @description Deletes a variable value override by ID. + */ + delete: operations["requestDeploymentVariableValueDeletion"]; options?: never; head?: never; patch?: never; trace?: never; }; - "/v1/workspaces/{workspaceId}/deployments": { + "/v1/workspaces/{workspaceId}/deployment-variables/{variableId}": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** List deployments */ - get: operations["listDeployments"]; - put?: never; - /** Create deployment */ - post: operations["requestDeploymentCreation"]; - delete?: never; + /** + * Get deployment variable + * @description Returns a specific deployment variable by ID. + */ + get: operations["getDeploymentVariable"]; + /** + * Upsert deployment variable + * @description Creates or updates a deployment variable by ID. + */ + put: operations["requestDeploymentVariableUpdate"]; + post?: never; + /** + * Delete deployment variable + * @description Deletes a deployment variable by ID. + */ + delete: operations["requestDeploymentVariableDeletion"]; options?: never; head?: never; patch?: never; trace?: never; }; - "/v1/workspaces/{workspaceId}/deployments/{deploymentId}": { + "/v1/workspaces/{workspaceId}/deployment-versions/{deploymentVersionId}/user-approval-records": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** Get deployment */ - get: operations["getDeployment"]; - /** Upsert deployment */ - put: operations["requestDeploymentUpsert"]; + get?: never; + /** Upsert user approval record */ + put: operations["requestUserApprovalRecordUpsert"]; post?: never; - /** Delete deployment */ - delete: operations["requestDeploymentDeletion"]; + delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/v1/workspaces/{workspaceId}/deployments/{deploymentId}/variables": { + "/v1/workspaces/{workspaceId}/deployments": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * List deployment variables - * @description Returns a list of variables for a deployment, including their configured values. - */ - get: operations["listDeploymentVariables"]; + /** List deployments */ + get: operations["listDeployments"]; put?: never; - post?: never; + /** Create deployment */ + post: operations["requestDeploymentCreation"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/v1/workspaces/{workspaceId}/deployments/{deploymentId}/variables/{variableId}": { + "/v1/workspaces/{workspaceId}/deployments/{deploymentId}": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * Get deployment variable - * @description Returns a specific deployment variable by ID, including its configured values. - */ - get: operations["getDeploymentVariable"]; - /** - * Upsert deployment variable - * @description Creates or updates a deployment variable by ID. - */ - put: operations["requestDeploymentVariableUpdate"]; + /** Get deployment */ + get: operations["getDeployment"]; + /** Upsert deployment */ + put: operations["requestDeploymentUpsert"]; post?: never; - /** - * Delete deployment variable - * @description Deletes a deployment variable by ID. - */ - delete: operations["requestDeploymentVariableDeletion"]; + /** Delete deployment */ + delete: operations["requestDeploymentDeletion"]; options?: never; head?: never; patch?: never; trace?: never; }; - "/v1/workspaces/{workspaceId}/deployments/{deploymentId}/variables/{variableId}/values": { + "/v1/workspaces/{workspaceId}/deployments/{deploymentId}/variables": { parameters: { query?: never; header?: never; @@ -192,10 +200,10 @@ export interface paths { cookie?: never; }; /** - * List deployment variable values - * @description Returns a list of value overrides for a specific deployment variable. + * List deployment variables by deployment + * @description Returns a list of variables for a deployment, including their configured values. */ - get: operations["listDeploymentVariableValues"]; + get: operations["listDeploymentVariablesByDeployment"]; put?: never; post?: never; delete?: never; @@ -204,34 +212,6 @@ export interface paths { patch?: never; trace?: never; }; - "/v1/workspaces/{workspaceId}/deployments/{deploymentId}/variables/{variableId}/values/{valueId}": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get deployment variable value - * @description Returns a specific variable value override by ID. - */ - get: operations["getDeploymentVariableValue"]; - /** - * Upsert deployment variable value - * @description Creates or updates a variable value override by ID. - */ - put: operations["requestDeploymentVariableValueUpsert"]; - post?: never; - /** - * Delete deployment variable value - * @description Deletes a variable value override by ID. - */ - delete: operations["requestDeploymentVariableValueDeletion"]; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; "/v1/workspaces/{workspaceId}/deployments/{deploymentId}/versions": { parameters: { query?: never; @@ -520,6 +500,26 @@ export interface paths { patch?: never; trace?: never; }; + "/v1/workspaces/{workspaceId}/release-targets/state": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Get release target states by deployment and environment + * @description Returns paginated release target states for a given deployment and environment. + */ + post: operations["getReleaseTargetStates"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/v1/workspaces/{workspaceId}/release-targets/{releaseTargetKey}/desired-release": { parameters: { query?: never; @@ -950,10 +950,21 @@ export interface components { }; name: string; priority?: number; - rules?: components["schemas"]["PolicyRule"][]; + rules?: components["schemas"]["CreatePolicyRule"][]; /** @description CEL expression for matching release targets. Use "true" to match all targets. */ selector?: string; }; + CreatePolicyRule: { + anyApproval?: components["schemas"]["AnyApprovalRule"]; + deploymentDependency?: components["schemas"]["DeploymentDependencyRule"]; + deploymentWindow?: components["schemas"]["DeploymentWindowRule"]; + environmentProgression?: components["schemas"]["EnvironmentProgressionRule"]; + gradualRollout?: components["schemas"]["GradualRolloutRule"]; + retry?: components["schemas"]["RetryRule"]; + verification?: components["schemas"]["VerificationRule"]; + versionCooldown?: components["schemas"]["VersionCooldownRule"]; + versionSelector?: components["schemas"]["VersionSelectorRule"]; + }; CreateRelationshipRuleRequest: { description?: string; fromSelector?: components["schemas"]["Selector"]; @@ -1368,6 +1379,7 @@ export interface components { retry?: components["schemas"]["RetryRule"]; verification?: components["schemas"]["VerificationRule"]; versionCooldown?: components["schemas"]["VersionCooldownRule"]; + versionSelector?: components["schemas"]["VersionSelectorRule"]; }; PrometheusMetricProvider: { /** @@ -1493,6 +1505,10 @@ export interface components { desiredRelease?: components["schemas"]["Release"]; latestJob?: components["schemas"]["Job"]; }; + ReleaseTargetWithState: { + releaseTarget: components["schemas"]["ReleaseTarget"]; + state: components["schemas"]["ReleaseTargetState"]; + }; Resource: { config: { [key: string]: unknown; @@ -1706,10 +1722,12 @@ export interface components { }; UpsertDeploymentVariableRequest: { defaultValue?: components["schemas"]["LiteralValue"]; + deploymentId: string; description?: string; key: string; }; UpsertDeploymentVariableValueRequest: { + deploymentVariableId: string; /** Format: int64 */ priority: number; resourceSelector?: components["schemas"]["Selector"]; @@ -1759,10 +1777,24 @@ export interface components { }; name: string; priority: number; - rules: components["schemas"]["PolicyRule"][]; + rules: components["schemas"]["UpsertPolicyRule"][]; /** @description CEL expression for matching release targets. Use "true" to match all targets. */ selector: string; }; + UpsertPolicyRule: { + anyApproval?: components["schemas"]["AnyApprovalRule"]; + createdAt?: string; + deploymentDependency?: components["schemas"]["DeploymentDependencyRule"]; + deploymentWindow?: components["schemas"]["DeploymentWindowRule"]; + environmentProgression?: components["schemas"]["EnvironmentProgressionRule"]; + gradualRollout?: components["schemas"]["GradualRolloutRule"]; + id?: string; + policyId?: string; + retry?: components["schemas"]["RetryRule"]; + verification?: components["schemas"]["VerificationRule"]; + versionCooldown?: components["schemas"]["VersionCooldownRule"]; + versionSelector?: components["schemas"]["VersionSelectorRule"]; + }; UpsertRelationshipRuleRequest: { description?: string; fromSelector?: components["schemas"]["Selector"]; @@ -1885,6 +1917,11 @@ export interface components { */ intervalSeconds: number; }; + VersionSelectorRule: { + /** @description Human-readable description of what this version selector does. Example: "Only deploy v2.x versions to staging environments" */ + description?: string; + selector: components["schemas"]["Selector"]; + }; Workflow: { id: string; inputs: components["schemas"]["WorkflowInput"][]; @@ -2334,31 +2371,27 @@ export interface operations { }; }; }; - requestUserApprovalRecordUpsert: { + getDeploymentVariableValue: { parameters: { query?: never; header?: never; path: { /** @description ID of the workspace */ workspaceId: string; - /** @description ID of the deployment version */ - deploymentVersionId: string; + /** @description ID of the variable value */ + valueId: string; }; cookie?: never; }; - requestBody: { - content: { - "application/json": components["schemas"]["UpsertUserApprovalRecordRequest"]; - }; - }; + requestBody?: never; responses: { - /** @description Accepted response */ - 202: { + /** @description The requested variable value */ + 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["UserApprovalRecordRequestAccepted"]; + "application/json": components["schemas"]["DeploymentVariableValue"]; }; }; /** @description Invalid request */ @@ -2381,55 +2414,21 @@ export interface operations { }; }; }; - listDeployments: { - parameters: { - query?: { - /** @description Maximum number of items to return */ - limit?: number; - /** @description Number of items to skip */ - offset?: number; - }; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Paginated list of items */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - items: components["schemas"]["DeploymentAndSystems"][]; - /** @description Maximum number of items returned */ - limit: number; - /** @description Number of items skipped */ - offset: number; - /** @description Total number of items available */ - total: number; - }; - }; - }; - }; - }; - requestDeploymentCreation: { + requestDeploymentVariableValueUpsert: { parameters: { query?: never; header?: never; path: { /** @description ID of the workspace */ workspaceId: string; + /** @description ID of the variable value */ + valueId: string; }; cookie?: never; }; requestBody: { content: { - "application/json": components["schemas"]["CreateDeploymentRequest"]; + "application/json": components["schemas"]["UpsertDeploymentVariableValueRequest"]; }; }; responses: { @@ -2439,32 +2438,50 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["DeploymentRequestAccepted"]; + "application/json": components["schemas"]["DeploymentVariableValueRequestAccepted"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; }; }; }; }; - getDeployment: { + requestDeploymentVariableValueDeletion: { parameters: { query?: never; header?: never; path: { /** @description ID of the workspace */ workspaceId: string; - /** @description ID of the deployment */ - deploymentId: string; + /** @description ID of the variable value */ + valueId: string; }; cookie?: never; }; requestBody?: never; responses: { - /** @description OK response */ - 200: { + /** @description Accepted response */ + 202: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["DeploymentWithVariablesAndSystems"]; + "application/json": components["schemas"]["DeploymentVariableValueRequestAccepted"]; }; }; /** @description Invalid request */ @@ -2487,56 +2504,27 @@ export interface operations { }; }; }; - requestDeploymentUpsert: { + getDeploymentVariable: { parameters: { query?: never; header?: never; path: { /** @description ID of the workspace */ workspaceId: string; - /** @description ID of the deployment */ - deploymentId: string; - }; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["UpsertDeploymentRequest"]; - }; - }; - responses: { - /** @description Accepted response */ - 202: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["DeploymentRequestAccepted"]; - }; - }; - }; - }; - requestDeploymentDeletion: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description ID of the deployment */ - deploymentId: string; + /** @description ID of the deployment variable */ + variableId: string; }; cookie?: never; }; requestBody?: never; responses: { - /** @description Accepted response */ - 202: { + /** @description The requested deployment variable */ + 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["DeploymentRequestAccepted"]; + "application/json": components["schemas"]["DeploymentVariableWithValues"]; }; }; /** @description Invalid request */ @@ -2559,67 +2547,31 @@ export interface operations { }; }; }; - listDeploymentVariables: { - parameters: { - query?: { - /** @description Maximum number of items to return */ - limit?: number; - /** @description Number of items to skip */ - offset?: number; - }; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description ID of the deployment */ - deploymentId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Paginated list of items */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - items: components["schemas"]["DeploymentVariableWithValues"][]; - /** @description Maximum number of items returned */ - limit: number; - /** @description Number of items skipped */ - offset: number; - /** @description Total number of items available */ - total: number; - }; - }; - }; - }; - }; - getDeploymentVariable: { + requestDeploymentVariableUpdate: { parameters: { query?: never; header?: never; path: { /** @description ID of the workspace */ workspaceId: string; - /** @description ID of the deployment */ - deploymentId: string; /** @description ID of the deployment variable */ variableId: string; }; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["UpsertDeploymentVariableRequest"]; + }; + }; responses: { - /** @description The requested deployment variable */ - 200: { + /** @description Accepted response */ + 202: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["DeploymentVariableWithValues"]; + "application/json": components["schemas"]["DeploymentVariableRequestAccepted"]; }; }; /** @description Invalid request */ @@ -2642,25 +2594,19 @@ export interface operations { }; }; }; - requestDeploymentVariableUpdate: { + requestDeploymentVariableDeletion: { parameters: { query?: never; header?: never; path: { /** @description ID of the workspace */ workspaceId: string; - /** @description ID of the deployment */ - deploymentId: string; /** @description ID of the deployment variable */ variableId: string; }; cookie?: never; }; - requestBody: { - content: { - "application/json": components["schemas"]["UpsertDeploymentVariableRequest"]; - }; - }; + requestBody?: never; responses: { /** @description Accepted response */ 202: { @@ -2691,21 +2637,23 @@ export interface operations { }; }; }; - requestDeploymentVariableDeletion: { + requestUserApprovalRecordUpsert: { parameters: { query?: never; header?: never; path: { /** @description ID of the workspace */ workspaceId: string; - /** @description ID of the deployment */ - deploymentId: string; - /** @description ID of the deployment variable */ - variableId: string; + /** @description ID of the deployment version */ + deploymentVersionId: string; }; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["UpsertUserApprovalRecordRequest"]; + }; + }; responses: { /** @description Accepted response */ 202: { @@ -2713,7 +2661,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["DeploymentVariableRequestAccepted"]; + "application/json": components["schemas"]["UserApprovalRecordRequestAccepted"]; }; }; /** @description Invalid request */ @@ -2736,7 +2684,7 @@ export interface operations { }; }; }; - listDeploymentVariableValues: { + listDeployments: { parameters: { query?: { /** @description Maximum number of items to return */ @@ -2748,10 +2696,6 @@ export interface operations { path: { /** @description ID of the workspace */ workspaceId: string; - /** @description ID of the deployment */ - deploymentId: string; - /** @description ID of the deployment variable */ - variableId: string; }; cookie?: never; }; @@ -2764,7 +2708,7 @@ export interface operations { }; content: { "application/json": { - items: components["schemas"]["DeploymentVariableValue"][]; + items: components["schemas"]["DeploymentAndSystems"][]; /** @description Maximum number of items returned */ limit: number; /** @description Number of items skipped */ @@ -2776,7 +2720,34 @@ export interface operations { }; }; }; - getDeploymentVariableValue: { + requestDeploymentCreation: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateDeploymentRequest"]; + }; + }; + responses: { + /** @description Accepted response */ + 202: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DeploymentRequestAccepted"]; + }; + }; + }; + }; + getDeployment: { parameters: { query?: never; header?: never; @@ -2785,22 +2756,18 @@ export interface operations { workspaceId: string; /** @description ID of the deployment */ deploymentId: string; - /** @description ID of the deployment variable */ - variableId: string; - /** @description ID of the variable value */ - valueId: string; }; cookie?: never; }; requestBody?: never; responses: { - /** @description The requested variable value */ + /** @description OK response */ 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["DeploymentVariableValue"]; + "application/json": components["schemas"]["DeploymentWithVariablesAndSystems"]; }; }; /** @description Invalid request */ @@ -2823,7 +2790,7 @@ export interface operations { }; }; }; - requestDeploymentVariableValueUpsert: { + requestDeploymentUpsert: { parameters: { query?: never; header?: never; @@ -2832,16 +2799,12 @@ export interface operations { workspaceId: string; /** @description ID of the deployment */ deploymentId: string; - /** @description ID of the deployment variable */ - variableId: string; - /** @description ID of the variable value */ - valueId: string; }; cookie?: never; }; requestBody: { content: { - "application/json": components["schemas"]["UpsertDeploymentVariableValueRequest"]; + "application/json": components["schemas"]["UpsertDeploymentRequest"]; }; }; responses: { @@ -2851,30 +2814,12 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["DeploymentVariableValueRequestAccepted"]; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; + "application/json": components["schemas"]["DeploymentRequestAccepted"]; }; }; }; }; - requestDeploymentVariableValueDeletion: { + requestDeploymentDeletion: { parameters: { query?: never; header?: never; @@ -2883,10 +2828,6 @@ export interface operations { workspaceId: string; /** @description ID of the deployment */ deploymentId: string; - /** @description ID of the deployment variable */ - variableId: string; - /** @description ID of the variable value */ - valueId: string; }; cookie?: never; }; @@ -2898,7 +2839,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["DeploymentVariableValueRequestAccepted"]; + "application/json": components["schemas"]["DeploymentRequestAccepted"]; }; }; /** @description Invalid request */ @@ -2921,6 +2862,44 @@ export interface operations { }; }; }; + listDeploymentVariablesByDeployment: { + parameters: { + query?: { + /** @description Maximum number of items to return */ + limit?: number; + /** @description Number of items to skip */ + offset?: number; + }; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description ID of the deployment */ + deploymentId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Paginated list of items */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["DeploymentVariableWithValues"][]; + /** @description Maximum number of items returned */ + limit: number; + /** @description Number of items skipped */ + offset: number; + /** @description Total number of items available */ + total: number; + }; + }; + }; + }; + }; listDeploymentVersions: { parameters: { query?: { @@ -4073,6 +4052,58 @@ export interface operations { }; }; }; + getReleaseTargetStates: { + parameters: { + query?: { + /** @description Maximum number of items to return */ + limit?: number; + /** @description Number of items to skip */ + offset?: number; + }; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + deploymentId: string; + environmentId: string; + }; + }; + }; + responses: { + /** @description Paginated list of items */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["ReleaseTargetWithState"][]; + /** @description Maximum number of items returned */ + limit: number; + /** @description Number of items skipped */ + offset: number; + /** @description Total number of items available */ + total: number; + }; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; getReleaseTargetDesiredRelease: { parameters: { query?: never; diff --git a/apps/workspace-engine/pkg/concurrency/chunks.go b/apps/workspace-engine/pkg/concurrency/chunks.go index 6e1480cbd..262eeeb83 100644 --- a/apps/workspace-engine/pkg/concurrency/chunks.go +++ b/apps/workspace-engine/pkg/concurrency/chunks.go @@ -4,7 +4,6 @@ import ( "context" "runtime" - "go.opentelemetry.io/otel" "golang.org/x/sync/errgroup" ) @@ -26,8 +25,6 @@ func Chunk[T any](slice []T, chunkSize int) [][]T { return chunks } -var tracer = otel.Tracer("workspace-engine/pkg/concurrency") - type options struct { chunkSize int maxConcurrency int diff --git a/apps/workspace-engine/pkg/workspace/jobagents/argo/argoapp.go b/apps/workspace-engine/pkg/workspace/jobagents/argo/argoapp.go index ef39a2973..fe99dac50 100644 --- a/apps/workspace-engine/pkg/workspace/jobagents/argo/argoapp.go +++ b/apps/workspace-engine/pkg/workspace/jobagents/argo/argoapp.go @@ -14,7 +14,6 @@ import ( "workspace-engine/pkg/oapi" "workspace-engine/pkg/templatefuncs" "workspace-engine/pkg/workspace/jobagents/types" - "workspace-engine/pkg/workspace/releasemanager/verification" "workspace-engine/pkg/workspace/store" argocdclient "github.com/argoproj/argo-cd/v2/pkg/apiclient" @@ -32,12 +31,11 @@ var tracer = otel.Tracer("workspace-engine/jobagents/argo") var _ types.Dispatchable = &ArgoApplication{} type ArgoApplication struct { - store *store.Store - verifications *verification.Manager + store *store.Store } -func NewArgoApplication(store *store.Store, verifications *verification.Manager) *ArgoApplication { - return &ArgoApplication{store: store, verifications: verifications} +func NewArgoApplication(store *store.Store) *ArgoApplication { + return &ArgoApplication{store: store} } func (a *ArgoApplication) Type() string { @@ -78,12 +76,6 @@ func (a *ArgoApplication) Dispatch(ctx context.Context, job *oapi.Job) error { return } - verification := newArgoApplicationVerification(a.verifications, job, app.Name, serverAddr, apiKey) - if err := verification.StartVerification(asyncCtx, job); err != nil { - a.sendJobFailureEvent(job, fmt.Sprintf("failed to start verification: %s", err.Error())) - return - } - a.sendJobUpdateEvent(serverAddr, app, job) }() @@ -199,7 +191,6 @@ func isRetryableError(err error) bool { return false } errStr := err.Error() - // Check for HTTP status codes and gRPC errors that indicate transient failures return strings.Contains(errStr, "502") || strings.Contains(errStr, "503") || strings.Contains(errStr, "504") || diff --git a/apps/workspace-engine/pkg/workspace/jobagents/argo/argoapp_verifications.go b/apps/workspace-engine/pkg/workspace/jobagents/argo/argoapp_verifications.go deleted file mode 100644 index 1d3658f8e..000000000 --- a/apps/workspace-engine/pkg/workspace/jobagents/argo/argoapp_verifications.go +++ /dev/null @@ -1,80 +0,0 @@ -package argo - -import ( - "context" - "fmt" - "strings" - "workspace-engine/pkg/oapi" - "workspace-engine/pkg/workspace/releasemanager/verification" -) - -type ArgoApplicationVerification struct { - verifications *verification.Manager - - appName string - serverUrl string - apiKey string - - job *oapi.Job -} - -func newArgoApplicationVerification(verifications *verification.Manager, job *oapi.Job, appName, serverUrl, apiKey string) *ArgoApplicationVerification { - return &ArgoApplicationVerification{ - verifications: verifications, - job: job, - appName: appName, - serverUrl: serverUrl, - apiKey: apiKey, - } -} - -func (v *ArgoApplicationVerification) StartVerification(ctx context.Context, job *oapi.Job) error { - appURL := v.buildAppURL() - - provider, err := v.buildMetricProvider(appURL) - if err != nil { - return fmt.Errorf("failed to build metric provider: %w", err) - } - - metricSpec := v.buildMetricSpec(provider) - return v.verifications.StartVerification(ctx, v.job, []oapi.VerificationMetricSpec{metricSpec}) -} - -func (v *ArgoApplicationVerification) buildAppURL() string { - baseURL := v.serverUrl - if !strings.HasPrefix(baseURL, "https://") { - baseURL = "https://" + baseURL - } - return fmt.Sprintf("%s/api/v1/applications/%s", baseURL, v.appName) -} - -func (v *ArgoApplicationVerification) buildMetricProvider(appURL string) (oapi.MetricProvider, error) { - method := oapi.GET - timeout := "5s" - headers := map[string]string{ - "Authorization": fmt.Sprintf("Bearer %s", v.apiKey), - } - provider := oapi.MetricProvider{} - err := provider.FromHTTPMetricProvider(oapi.HTTPMetricProvider{ - Url: appURL, - Method: &method, - Timeout: &timeout, - Headers: &headers, - Type: oapi.Http, - }) - return provider, err -} - -func (v *ArgoApplicationVerification) buildMetricSpec(provider oapi.MetricProvider) oapi.VerificationMetricSpec { - successThreshold := 1 - failureCondition := "result.statusCode != 200 || result.json.status.health.status == 'Degraded' || result.json.status.health.status == 'Missing'" - return oapi.VerificationMetricSpec{ - Name: fmt.Sprintf("%s-argocd-application-health", v.appName), - IntervalSeconds: 60, - Count: 10, - SuccessThreshold: &successThreshold, - SuccessCondition: "result.statusCode == 200 && result.json.status.sync.status == 'Synced' && result.json.status.health.status == 'Healthy'", - FailureCondition: &failureCondition, - Provider: provider, - } -} diff --git a/apps/workspace-engine/pkg/workspace/jobagents/registry.go b/apps/workspace-engine/pkg/workspace/jobagents/registry.go index a1c6c897d..0f6391dbf 100644 --- a/apps/workspace-engine/pkg/workspace/jobagents/registry.go +++ b/apps/workspace-engine/pkg/workspace/jobagents/registry.go @@ -10,7 +10,6 @@ import ( "workspace-engine/pkg/workspace/jobagents/terraformcloud" "workspace-engine/pkg/workspace/jobagents/testrunner" "workspace-engine/pkg/workspace/jobagents/types" - "workspace-engine/pkg/workspace/releasemanager/verification" "workspace-engine/pkg/workspace/store" ) @@ -19,13 +18,13 @@ type Registry struct { store *store.Store } -func NewRegistry(store *store.Store, verifications *verification.Manager) *Registry { +func NewRegistry(store *store.Store) *Registry { r := &Registry{} r.dispatchers = make(map[string]types.Dispatchable) r.store = store r.Register(testrunner.New(store)) - r.Register(argo.NewArgoApplication(store, verifications)) + r.Register(argo.NewArgoApplication(store)) r.Register(terraformcloud.NewTFE(store)) r.Register(github.NewGithubAction(store)) diff --git a/apps/workspace-engine/pkg/workspace/releasemanager/action/rollback/hooks.go b/apps/workspace-engine/pkg/workspace/releasemanager/action/rollback/hooks.go deleted file mode 100644 index 8628fd222..000000000 --- a/apps/workspace-engine/pkg/workspace/releasemanager/action/rollback/hooks.go +++ /dev/null @@ -1,159 +0,0 @@ -package rollback - -import ( - "context" - "time" - "workspace-engine/pkg/oapi" - "workspace-engine/pkg/workspace/jobagents" - "workspace-engine/pkg/workspace/releasemanager/verification" - "workspace-engine/pkg/workspace/store" - - "github.com/google/uuid" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" -) - -var hookTracer = otel.Tracer("RollbackHooks") - -type RollbackHooks struct { - store *store.Store - jobAgentRegistry *jobagents.Registry -} - -var _ verification.VerificationHooks = &RollbackHooks{} - -func NewRollbackHooks(store *store.Store, jobAgentRegistry *jobagents.Registry) *RollbackHooks { - return &RollbackHooks{ - store: store, - jobAgentRegistry: jobAgentRegistry, - } -} - -func (h *RollbackHooks) OnVerificationStarted(ctx context.Context, verification *oapi.JobVerification) error { - return nil -} - -func (h *RollbackHooks) OnMeasurementTaken(ctx context.Context, verification *oapi.JobVerification, metricIndex int, measurement *oapi.VerificationMeasurement) error { - return nil -} - -func (h *RollbackHooks) OnMetricComplete(ctx context.Context, verification *oapi.JobVerification, metricIndex int) error { - return nil -} - -func (h *RollbackHooks) OnVerificationComplete(ctx context.Context, verificationResult *oapi.JobVerification) error { - ctx, span := hookTracer.Start(ctx, "RollbackHooks.OnVerificationComplete") - defer span.End() - - span.SetAttributes( - attribute.String("verification.id", verificationResult.Id), - attribute.String("verification.job_id", verificationResult.JobId), - ) - - status := verificationResult.Status() - span.SetAttributes(attribute.String("verification.status", string(status))) - - if status != oapi.JobVerificationStatusFailed { - span.SetStatus(codes.Ok, "verification did not fail") - return nil - } - - job, ok := h.store.Jobs.Get(verificationResult.JobId) - if !ok { - span.SetStatus(codes.Error, "job not found") - return nil - } - - release, ok := h.store.Releases.Get(job.ReleaseId) - if !ok { - span.SetStatus(codes.Error, "release not found") - return nil - } - - span.SetAttributes( - attribute.String("release.id", release.ID()), - attribute.String("release_target.key", release.ReleaseTarget.Key()), - ) - - policies, err := h.store.ReleaseTargets.GetPolicies(ctx, &release.ReleaseTarget) - if err != nil { - span.RecordError(err) - span.SetStatus(codes.Error, "failed to get policies") - return nil - } - - if !h.shouldRollbackOnVerificationFailure(policies) { - span.SetAttributes(attribute.Bool("rollback_applicable", false)) - span.SetStatus(codes.Ok, "no applicable rollback policy for verification failure") - return nil - } - - span.SetAttributes(attribute.Bool("rollback_applicable", true)) - - currentRelease, lastSuccessfulJob, err := h.store.ReleaseTargets.GetCurrentRelease(ctx, &release.ReleaseTarget) - if err != nil { - span.AddEvent("No previous release to roll back to") - span.SetStatus(codes.Ok, "no previous release available") - return nil - } - - // Don't rollback to the same release - if currentRelease.ID() == release.ID() { - span.AddEvent("Current release is the same as failed release, no rollback needed") - span.SetStatus(codes.Ok, "already on current release") - return nil - } - - span.SetAttributes( - attribute.String("rollback_to_release.id", currentRelease.ID()), - attribute.String("rollback_to_version.id", currentRelease.Version.Id), - attribute.String("rollback_to_version.tag", currentRelease.Version.Tag), - ) - - now := time.Now() - newJob := oapi.Job{ - Id: uuid.New().String(), - ReleaseId: lastSuccessfulJob.ReleaseId, - JobAgentId: lastSuccessfulJob.JobAgentId, - JobAgentConfig: lastSuccessfulJob.JobAgentConfig, - Status: oapi.JobStatusPending, - CreatedAt: now, - UpdatedAt: now, - } - - h.store.Jobs.Upsert(ctx, &newJob) - - if err := h.jobAgentRegistry.Dispatch(ctx, &newJob); err != nil { - span.RecordError(err) - span.SetStatus(codes.Error, "rollback execution failed") - return err - } - - span.SetStatus(codes.Ok, "rollback executed successfully") - return nil -} - -func (h *RollbackHooks) OnVerificationStopped(ctx context.Context, verification *oapi.JobVerification) error { - return nil -} - -func (h *RollbackHooks) shouldRollbackOnVerificationFailure(policies []*oapi.Policy) bool { - for _, policy := range policies { - if !policy.Enabled { - continue - } - - for _, rule := range policy.Rules { - if rule.Rollback == nil { - continue - } - - if rule.Rollback.OnVerificationFailure != nil && *rule.Rollback.OnVerificationFailure { - return true - } - } - } - - return false -} diff --git a/apps/workspace-engine/pkg/workspace/releasemanager/action/verification/starter.go b/apps/workspace-engine/pkg/workspace/releasemanager/action/verification/starter.go new file mode 100644 index 000000000..2a69fd9ac --- /dev/null +++ b/apps/workspace-engine/pkg/workspace/releasemanager/action/verification/starter.go @@ -0,0 +1,82 @@ +package verification + +import ( + "context" + "fmt" + + "workspace-engine/pkg/db" + "workspace-engine/pkg/oapi" + "workspace-engine/pkg/reconcile" + + "github.com/google/uuid" + "github.com/jackc/pgx/v5/pgtype" +) + +// VerificationStarter inserts verification metric rows into the database +// and enqueues them for processing by the reconciler. +type VerificationStarter interface { + StartVerification(ctx context.Context, job *oapi.Job, specs []oapi.VerificationMetricSpec) error +} + +// DBVerificationStarter implements VerificationStarter using Postgres and +// the reconcile work queue. +type DBVerificationStarter struct { + WorkspaceID string + Queue reconcile.Queue +} + +func (s *DBVerificationStarter) StartVerification(ctx context.Context, job *oapi.Job, specs []oapi.VerificationMetricSpec) error { + if len(specs) == 0 { + return nil + } + + queries := db.GetQueries(ctx) + jobUUID := uuid.MustParse(job.Id) + + for _, spec := range specs { + providerJSON, err := spec.Provider.MarshalJSON() + if err != nil { + return fmt.Errorf("marshal provider for metric %q: %w", spec.Name, err) + } + + metric, err := queries.InsertJobVerificationMetric(ctx, db.InsertJobVerificationMetricParams{ + JobID: jobUUID, + Name: spec.Name, + Provider: providerJSON, + IntervalSeconds: spec.IntervalSeconds, + Count: int32(spec.Count), + SuccessCondition: spec.SuccessCondition, + SuccessThreshold: toPgInt4(spec.SuccessThreshold), + FailureCondition: toPgText(spec.FailureCondition), + FailureThreshold: toPgInt4(spec.FailureThreshold), + }) + if err != nil { + return fmt.Errorf("insert verification metric %q: %w", spec.Name, err) + } + + if err := s.Queue.Enqueue(ctx, reconcile.EnqueueParams{ + WorkspaceID: s.WorkspaceID, + Kind: "job-verification-metric", + ScopeType: "job-verification-metric", + ScopeID: metric.ID.String(), + }); err != nil { + return fmt.Errorf("enqueue verification metric %q: %w", spec.Name, err) + } + } + + return nil +} + +func toPgInt4(v *int) pgtype.Int4 { + if v == nil { + return pgtype.Int4{} + } + return pgtype.Int4{Int32: int32(*v), Valid: true} +} + +func toPgText(v *string) pgtype.Text { + if v == nil { + return pgtype.Text{} + } + return pgtype.Text{String: *v, Valid: true} +} diff --git a/apps/workspace-engine/pkg/workspace/releasemanager/action/verification/verification.go b/apps/workspace-engine/pkg/workspace/releasemanager/action/verification/verification.go index fcaa3fcfc..2a6330429 100644 --- a/apps/workspace-engine/pkg/workspace/releasemanager/action/verification/verification.go +++ b/apps/workspace-engine/pkg/workspace/releasemanager/action/verification/verification.go @@ -4,7 +4,6 @@ import ( "context" "workspace-engine/pkg/oapi" "workspace-engine/pkg/workspace/releasemanager/action" - "workspace-engine/pkg/workspace/releasemanager/verification" "github.com/charmbracelet/log" "go.opentelemetry.io/otel" @@ -16,13 +15,13 @@ var tracer = otel.Tracer("workspace/releasemanager/action/verification") // VerificationAction creates verifications based on policy rules type VerificationAction struct { - verificationManager *verification.Manager + starter VerificationStarter } // NewVerificationAction creates a new verification action -func NewVerificationAction(manager *verification.Manager) *VerificationAction { +func NewVerificationAction(starter VerificationStarter) *VerificationAction { return &VerificationAction{ - verificationManager: manager, + starter: starter, } } @@ -46,7 +45,6 @@ func (v *VerificationAction) Execute( attribute.String("release.id", actx.Release.ID()), attribute.String("job.id", actx.Job.Id)) - // Extract all verification metrics from matching policies metrics := v.extractVerificationMetrics(trigger, actx.Policies) if len(metrics) == 0 { span.SetAttributes(attribute.Int("metric_count", 0)) @@ -56,8 +54,7 @@ func (v *VerificationAction) Execute( span.SetAttributes(attribute.Int("metric_count", len(metrics))) - // Create verification synchronously - if err := v.verificationManager.StartVerification(ctx, actx.Job, metrics); err != nil { + if err := v.starter.StartVerification(ctx, actx.Job, metrics); err != nil { span.RecordError(err) span.SetStatus(codes.Error, "failed to create verification") log.Error("Failed to create verification", @@ -84,7 +81,7 @@ func (v *VerificationAction) extractVerificationMetrics( policies []*oapi.Policy, ) []oapi.VerificationMetricSpec { var allMetrics []oapi.VerificationMetricSpec - seen := make(map[string]bool) // Deduplicate by metric name + seen := make(map[string]bool) for _, policy := range policies { if !policy.Enabled { @@ -97,12 +94,10 @@ func (v *VerificationAction) extractVerificationMetrics( continue } - // Check if this verification rule matches the trigger if v.getTriggerFromRule(verificationRule) != trigger { continue } - // Add metrics (deduplicate by name) for _, metric := range verificationRule.Metrics { if !seen[metric.Name] { allMetrics = append(allMetrics, metric) @@ -123,7 +118,7 @@ func (v *VerificationAction) getVerificationRule(rule *oapi.PolicyRule) *oapi.Ve // getTriggerFromRule determines the trigger for a verification rule func (v *VerificationAction) getTriggerFromRule(rule *oapi.VerificationRule) action.ActionTrigger { if rule.TriggerOn == nil { - return action.TriggerJobSuccess // Default + return action.TriggerJobSuccess } switch *rule.TriggerOn { diff --git a/apps/workspace-engine/pkg/workspace/releasemanager/action/verification/verification_test.go b/apps/workspace-engine/pkg/workspace/releasemanager/action/verification/verification_test.go index 92e3c368a..bd84d8e5f 100644 --- a/apps/workspace-engine/pkg/workspace/releasemanager/action/verification/verification_test.go +++ b/apps/workspace-engine/pkg/workspace/releasemanager/action/verification/verification_test.go @@ -2,135 +2,73 @@ package verification_test import ( "context" + "sync" "testing" "time" "workspace-engine/pkg/oapi" - "workspace-engine/pkg/statechange" "workspace-engine/pkg/workspace/releasemanager/action" verificationaction "workspace-engine/pkg/workspace/releasemanager/action/verification" - "workspace-engine/pkg/workspace/releasemanager/verification" - "workspace-engine/pkg/workspace/store" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func newTestStore() *store.Store { - wsId := uuid.New().String() - changeset := statechange.NewChangeSet[any]() - return store.New(wsId, changeset) +type starterCall struct { + JobID string + Specs []oapi.VerificationMetricSpec } -func createTestRelease(s *store.Store, ctx context.Context) *oapi.Release { - // Create system - systemId := uuid.New().String() - system := &oapi.System{ - Id: systemId, - Name: "test-system", - } - _ = s.Systems.Upsert(ctx, system) - - // Create resource - resourceId := uuid.New().String() - resource := &oapi.Resource{ - Id: resourceId, - Name: "test-resource", - Kind: "kubernetes", - Identifier: "test-res-1", - CreatedAt: time.Now(), - } - _, _ = s.Resources.Upsert(ctx, resource) - - // Create environment - environmentId := uuid.New().String() - environment := &oapi.Environment{ - Id: environmentId, - Name: "test-env", - } - selector := &oapi.Selector{} - _ = selector.FromCelSelector(oapi.CelSelector{Cel: "true"}) - environment.ResourceSelector = selector - _ = s.Environments.Upsert(ctx, environment) - - // Create deployment - deploymentId := uuid.New().String() - deployment := &oapi.Deployment{ - Id: deploymentId, - Name: "test-deployment", - Slug: "test-deployment", - } - deploymentSelector := &oapi.Selector{} - _ = deploymentSelector.FromCelSelector(oapi.CelSelector{Cel: "true"}) - deployment.ResourceSelector = deploymentSelector - _ = s.Deployments.Upsert(ctx, deployment) - - // Create version - versionId := uuid.New().String() - version := &oapi.DeploymentVersion{ - Id: versionId, - Tag: "v1.0.0", - DeploymentId: deploymentId, - CreatedAt: time.Now(), - } - s.DeploymentVersions.Upsert(ctx, versionId, version) - - // Create release target - releaseTarget := &oapi.ReleaseTarget{ - ResourceId: resourceId, - EnvironmentId: environmentId, - DeploymentId: deploymentId, - } - _ = s.ReleaseTargets.Upsert(ctx, releaseTarget) - - // Create release - release := &oapi.Release{ - ReleaseTarget: *releaseTarget, - Version: *version, - Variables: map[string]oapi.LiteralValue{}, - } - _ = s.Releases.Upsert(ctx, release) - - return release +type mockStarter struct { + mu sync.Mutex + calls []starterCall + err error } -func TestVerificationAction_Name(t *testing.T) { - s := newTestStore() - verificationMgr := verification.NewManager(s) - action := verificationaction.NewVerificationAction(verificationMgr) +func (m *mockStarter) StartVerification(_ context.Context, job *oapi.Job, specs []oapi.VerificationMetricSpec) error { + m.mu.Lock() + defer m.mu.Unlock() + m.calls = append(m.calls, starterCall{JobID: job.Id, Specs: specs}) + return m.err +} - assert.Equal(t, "verification", action.Name()) +func (m *mockStarter) callCount() int { + m.mu.Lock() + defer m.mu.Unlock() + return len(m.calls) } -func TestVerificationAction_Execute_NoMetrics(t *testing.T) { - ctx := context.Background() - s := newTestStore() - verificationMgr := verification.NewManager(s) - vAction := verificationaction.NewVerificationAction(verificationMgr) +func (m *mockStarter) lastCall() starterCall { + m.mu.Lock() + defer m.mu.Unlock() + return m.calls[len(m.calls)-1] +} - release := createTestRelease(s, ctx) +func newRelease() *oapi.Release { + return &oapi.Release{ + ReleaseTarget: oapi.ReleaseTarget{ + ResourceId: uuid.New().String(), + EnvironmentId: uuid.New().String(), + DeploymentId: uuid.New().String(), + }, + Version: oapi.DeploymentVersion{ + Id: uuid.New().String(), + Tag: "v1.0.0", + DeploymentId: uuid.New().String(), + CreatedAt: time.Now(), + }, + Variables: map[string]oapi.LiteralValue{}, + } +} - job := &oapi.Job{ +func newJob(releaseID string) *oapi.Job { + return &oapi.Job{ Id: uuid.New().String(), - ReleaseId: release.ID(), + ReleaseId: releaseID, Status: oapi.JobStatusSuccessful, CreatedAt: time.Now(), } - - actx := action.ActionContext{ - Job: job, - Release: release, - Policies: []*oapi.Policy{}, - } - - // Should not error when no metrics - err := vAction.Execute(ctx, action.TriggerJobSuccess, actx) - require.NoError(t, err) - - // No verification should be created - verifications := s.JobVerifications.GetByJobId(job.Id) - assert.Empty(t, verifications) } func createPolicyWithVerification(metrics []oapi.VerificationMetricSpec, triggerOn *oapi.VerificationRuleTriggerOn) *oapi.Policy { @@ -156,6 +94,19 @@ func createPolicyWithVerification(metrics []oapi.VerificationMetricSpec, trigger } } +func createPolicyWithRules(rules []oapi.PolicyRule) *oapi.Policy { + return &oapi.Policy{ + Id: uuid.New().String(), + Name: "test-policy", + Enabled: true, + Priority: 1, + CreatedAt: time.Now().Format(time.RFC3339), + Selector: "true", + Rules: rules, + Metadata: map[string]string{}, + } +} + func createTestMetric(name string) oapi.VerificationMetricSpec { provider := oapi.MetricProvider{} _ = provider.FromSleepMetricProvider(oapi.SleepMetricProvider{ @@ -171,22 +122,39 @@ func createTestMetric(name string) oapi.VerificationMetricSpec { } } -func TestVerificationAction_Execute_CreatesVerification(t *testing.T) { +func TestVerificationAction_Name(t *testing.T) { + starter := &mockStarter{} + a := verificationaction.NewVerificationAction(starter) + assert.Equal(t, "verification", a.Name()) +} + +func TestVerificationAction_Execute_NoMetrics(t *testing.T) { ctx := context.Background() - s := newTestStore() - verificationMgr := verification.NewManager(s) - vAction := verificationaction.NewVerificationAction(verificationMgr) + starter := &mockStarter{} + vAction := verificationaction.NewVerificationAction(starter) - release := createTestRelease(s, ctx) + release := newRelease() + job := newJob(release.ID()) - job := &oapi.Job{ - Id: uuid.New().String(), - ReleaseId: release.ID(), - Status: oapi.JobStatusSuccessful, - CreatedAt: time.Now(), + actx := action.ActionContext{ + Job: job, + Release: release, + Policies: []*oapi.Policy{}, } - // Create a policy with verification rule + err := vAction.Execute(ctx, action.TriggerJobSuccess, actx) + require.NoError(t, err) + assert.Equal(t, 0, starter.callCount()) +} + +func TestVerificationAction_Execute_CreatesVerification(t *testing.T) { + ctx := context.Background() + starter := &mockStarter{} + vAction := verificationaction.NewVerificationAction(starter) + + release := newRelease() + job := newJob(release.ID()) + triggerOn := oapi.JobSuccess policy := createPolicyWithVerification( []oapi.VerificationMetricSpec{createTestMetric("health-check")}, @@ -199,46 +167,24 @@ func TestVerificationAction_Execute_CreatesVerification(t *testing.T) { Policies: []*oapi.Policy{policy}, } - // Execute the action err := vAction.Execute(ctx, action.TriggerJobSuccess, actx) require.NoError(t, err) - // Verification should be created - verifications := s.JobVerifications.GetByJobId(job.Id) - require.Len(t, verifications, 1) - v := verifications[0] - assert.Equal(t, 1, len(v.Metrics)) - assert.Equal(t, "health-check", v.Metrics[0].Name) - - // Verification should be in running status (scheduler started) - assert.Equal(t, oapi.JobVerificationStatusRunning, v.Status(), "verification should be in running status") - - // Verification should be linked to the job - assert.Equal(t, job.Id, v.JobId) - - // Verification ID should be set - assert.NotEmpty(t, v.Id) - - // CreatedAt should be set - assert.False(t, v.CreatedAt.IsZero()) + assert.Equal(t, 1, starter.callCount()) + call := starter.lastCall() + assert.Equal(t, job.Id, call.JobID) + require.Len(t, call.Specs, 1) + assert.Equal(t, "health-check", call.Specs[0].Name) } func TestVerificationAction_Execute_SkipsDisabledPolicy(t *testing.T) { ctx := context.Background() - s := newTestStore() - verificationMgr := verification.NewManager(s) - vAction := verificationaction.NewVerificationAction(verificationMgr) - - release := createTestRelease(s, ctx) + starter := &mockStarter{} + vAction := verificationaction.NewVerificationAction(starter) - job := &oapi.Job{ - Id: uuid.New().String(), - ReleaseId: release.ID(), - Status: oapi.JobStatusSuccessful, - CreatedAt: time.Now(), - } + release := newRelease() + job := newJob(release.ID()) - // Create a disabled policy with verification rule triggerOn := oapi.JobSuccess policy := createPolicyWithVerification( []oapi.VerificationMetricSpec{createTestMetric("health-check")}, @@ -252,31 +198,19 @@ func TestVerificationAction_Execute_SkipsDisabledPolicy(t *testing.T) { Policies: []*oapi.Policy{policy}, } - // Execute the action err := vAction.Execute(ctx, action.TriggerJobSuccess, actx) require.NoError(t, err) - - // No verification should be created - verifications := s.JobVerifications.GetByJobId(job.Id) - assert.Empty(t, verifications) + assert.Equal(t, 0, starter.callCount()) } func TestVerificationAction_Execute_SkipsWrongTrigger(t *testing.T) { ctx := context.Background() - s := newTestStore() - verificationMgr := verification.NewManager(s) - vAction := verificationaction.NewVerificationAction(verificationMgr) - - release := createTestRelease(s, ctx) + starter := &mockStarter{} + vAction := verificationaction.NewVerificationAction(starter) - job := &oapi.Job{ - Id: uuid.New().String(), - ReleaseId: release.ID(), - Status: oapi.JobStatusSuccessful, - CreatedAt: time.Now(), - } + release := newRelease() + job := newJob(release.ID()) - // Create a policy that triggers on jobFailure, not jobSuccess triggerOn := oapi.JobFailure policy := createPolicyWithVerification( []oapi.VerificationMetricSpec{createTestMetric("health-check")}, @@ -289,34 +223,22 @@ func TestVerificationAction_Execute_SkipsWrongTrigger(t *testing.T) { Policies: []*oapi.Policy{policy}, } - // Execute with TriggerJobSuccess (mismatched trigger) err := vAction.Execute(ctx, action.TriggerJobSuccess, actx) require.NoError(t, err) - - // No verification should be created due to trigger mismatch - verifications := s.JobVerifications.GetByJobId(job.Id) - assert.Empty(t, verifications) + assert.Equal(t, 0, starter.callCount()) } func TestVerificationAction_Execute_DefaultsTriggerToJobSuccess(t *testing.T) { ctx := context.Background() - s := newTestStore() - verificationMgr := verification.NewManager(s) - vAction := verificationaction.NewVerificationAction(verificationMgr) - - release := createTestRelease(s, ctx) + starter := &mockStarter{} + vAction := verificationaction.NewVerificationAction(starter) - job := &oapi.Job{ - Id: uuid.New().String(), - ReleaseId: release.ID(), - Status: oapi.JobStatusSuccessful, - CreatedAt: time.Now(), - } + release := newRelease() + job := newJob(release.ID()) - // Create a policy with no triggerOn specified (should default to jobSuccess) policy := createPolicyWithVerification( []oapi.VerificationMetricSpec{createTestMetric("health-check")}, - nil, // nil triggerOn + nil, ) actx := action.ActionContext{ @@ -325,33 +247,20 @@ func TestVerificationAction_Execute_DefaultsTriggerToJobSuccess(t *testing.T) { Policies: []*oapi.Policy{policy}, } - // Execute with TriggerJobSuccess err := vAction.Execute(ctx, action.TriggerJobSuccess, actx) require.NoError(t, err) - - // Verification should be created (default trigger matches) - verifications := s.JobVerifications.GetByJobId(job.Id) - require.Len(t, verifications, 1) - v := verifications[0] - assert.Equal(t, 1, len(v.Metrics)) + assert.Equal(t, 1, starter.callCount()) + assert.Len(t, starter.lastCall().Specs, 1) } func TestVerificationAction_Execute_DeduplicatesMetrics(t *testing.T) { ctx := context.Background() - s := newTestStore() - verificationMgr := verification.NewManager(s) - vAction := verificationaction.NewVerificationAction(verificationMgr) - - release := createTestRelease(s, ctx) + starter := &mockStarter{} + vAction := verificationaction.NewVerificationAction(starter) - job := &oapi.Job{ - Id: uuid.New().String(), - ReleaseId: release.ID(), - Status: oapi.JobStatusSuccessful, - CreatedAt: time.Now(), - } + release := newRelease() + job := newJob(release.ID()) - // Create two policies with overlapping metric names triggerOn := oapi.JobSuccess policy1 := createPolicyWithVerification( []oapi.VerificationMetricSpec{ @@ -362,7 +271,7 @@ func TestVerificationAction_Execute_DeduplicatesMetrics(t *testing.T) { ) policy2 := createPolicyWithVerification( []oapi.VerificationMetricSpec{ - createTestMetric("health-check"), // duplicate + createTestMetric("health-check"), createTestMetric("error-rate"), }, &triggerOn, @@ -374,44 +283,31 @@ func TestVerificationAction_Execute_DeduplicatesMetrics(t *testing.T) { Policies: []*oapi.Policy{policy1, policy2}, } - // Execute the action err := vAction.Execute(ctx, action.TriggerJobSuccess, actx) require.NoError(t, err) - // Verification should be created with deduplicated metrics - verifications := s.JobVerifications.GetByJobId(job.Id) - require.Len(t, verifications, 1) - v := verifications[0] - assert.Equal(t, 3, len(v.Metrics)) // health-check, latency-check, error-rate (deduplicated) + assert.Equal(t, 1, starter.callCount()) + call := starter.lastCall() + assert.Len(t, call.Specs, 3) - // Check names are unique names := make(map[string]bool) - for _, m := range v.Metrics { - names[m.Name] = true + for _, s := range call.Specs { + names[s.Name] = true } assert.True(t, names["health-check"]) assert.True(t, names["latency-check"]) assert.True(t, names["error-rate"]) } -// Tests for all trigger types - func TestVerificationAction_Execute_TriggerJobCreated(t *testing.T) { ctx := context.Background() - s := newTestStore() - verificationMgr := verification.NewManager(s) - vAction := verificationaction.NewVerificationAction(verificationMgr) - - release := createTestRelease(s, ctx) + starter := &mockStarter{} + vAction := verificationaction.NewVerificationAction(starter) - job := &oapi.Job{ - Id: uuid.New().String(), - ReleaseId: release.ID(), - Status: oapi.JobStatusPending, - CreatedAt: time.Now(), - } + release := newRelease() + job := newJob(release.ID()) + job.Status = oapi.JobStatusPending - // Create a policy that triggers on jobCreated triggerOn := oapi.JobCreated policy := createPolicyWithVerification( []oapi.VerificationMetricSpec{createTestMetric("startup-check")}, @@ -424,35 +320,21 @@ func TestVerificationAction_Execute_TriggerJobCreated(t *testing.T) { Policies: []*oapi.Policy{policy}, } - // Execute with TriggerJobCreated err := vAction.Execute(ctx, action.TriggerJobCreated, actx) require.NoError(t, err) - - // Verification should be created and running - verifications := s.JobVerifications.GetByJobId(job.Id) - require.Len(t, verifications, 1) - v := verifications[0] - assert.Equal(t, 1, len(v.Metrics)) - assert.Equal(t, "startup-check", v.Metrics[0].Name) - assert.Equal(t, oapi.JobVerificationStatusRunning, v.Status(), "verification should be running") + assert.Equal(t, 1, starter.callCount()) + assert.Equal(t, "startup-check", starter.lastCall().Specs[0].Name) } func TestVerificationAction_Execute_TriggerJobStarted(t *testing.T) { ctx := context.Background() - s := newTestStore() - verificationMgr := verification.NewManager(s) - vAction := verificationaction.NewVerificationAction(verificationMgr) + starter := &mockStarter{} + vAction := verificationaction.NewVerificationAction(starter) - release := createTestRelease(s, ctx) + release := newRelease() + job := newJob(release.ID()) + job.Status = oapi.JobStatusInProgress - job := &oapi.Job{ - Id: uuid.New().String(), - ReleaseId: release.ID(), - Status: oapi.JobStatusInProgress, - CreatedAt: time.Now(), - } - - // Create a policy that triggers on jobStarted triggerOn := oapi.JobStarted policy := createPolicyWithVerification( []oapi.VerificationMetricSpec{createTestMetric("progress-check")}, @@ -465,35 +347,21 @@ func TestVerificationAction_Execute_TriggerJobStarted(t *testing.T) { Policies: []*oapi.Policy{policy}, } - // Execute with TriggerJobStarted err := vAction.Execute(ctx, action.TriggerJobStarted, actx) require.NoError(t, err) - - // Verification should be created and running - verifications := s.JobVerifications.GetByJobId(job.Id) - require.Len(t, verifications, 1) - v := verifications[0] - assert.Equal(t, 1, len(v.Metrics)) - assert.Equal(t, "progress-check", v.Metrics[0].Name) - assert.Equal(t, oapi.JobVerificationStatusRunning, v.Status(), "verification should be running") + assert.Equal(t, 1, starter.callCount()) + assert.Equal(t, "progress-check", starter.lastCall().Specs[0].Name) } func TestVerificationAction_Execute_TriggerJobFailure(t *testing.T) { ctx := context.Background() - s := newTestStore() - verificationMgr := verification.NewManager(s) - vAction := verificationaction.NewVerificationAction(verificationMgr) + starter := &mockStarter{} + vAction := verificationaction.NewVerificationAction(starter) - release := createTestRelease(s, ctx) + release := newRelease() + job := newJob(release.ID()) + job.Status = oapi.JobStatusFailure - job := &oapi.Job{ - Id: uuid.New().String(), - ReleaseId: release.ID(), - Status: oapi.JobStatusFailure, - CreatedAt: time.Now(), - } - - // Create a policy that triggers on jobFailure triggerOn := oapi.JobFailure policy := createPolicyWithVerification( []oapi.VerificationMetricSpec{createTestMetric("failure-analysis")}, @@ -506,51 +374,20 @@ func TestVerificationAction_Execute_TriggerJobFailure(t *testing.T) { Policies: []*oapi.Policy{policy}, } - // Execute with TriggerJobFailure err := vAction.Execute(ctx, action.TriggerJobFailure, actx) require.NoError(t, err) - - // Verification should be created and running - verifications := s.JobVerifications.GetByJobId(job.Id) - require.Len(t, verifications, 1) - v := verifications[0] - assert.Equal(t, 1, len(v.Metrics)) - assert.Equal(t, "failure-analysis", v.Metrics[0].Name) - assert.Equal(t, oapi.JobVerificationStatusRunning, v.Status(), "verification should be running") + assert.Equal(t, 1, starter.callCount()) + assert.Equal(t, "failure-analysis", starter.lastCall().Specs[0].Name) } -// Helper function for creating policies with custom rules -func createPolicyWithRules(rules []oapi.PolicyRule) *oapi.Policy { - return &oapi.Policy{ - Id: uuid.New().String(), - Name: "test-policy", - Enabled: true, - Priority: 1, - CreatedAt: time.Now().Format(time.RFC3339), - Selector: "true", - Rules: rules, - Metadata: map[string]string{}, - } -} - -// Test for mixed rule types - func TestVerificationAction_Execute_PolicyWithMixedRules(t *testing.T) { ctx := context.Background() - s := newTestStore() - verificationMgr := verification.NewManager(s) - vAction := verificationaction.NewVerificationAction(verificationMgr) + starter := &mockStarter{} + vAction := verificationaction.NewVerificationAction(starter) - release := createTestRelease(s, ctx) + release := newRelease() + job := newJob(release.ID()) - job := &oapi.Job{ - Id: uuid.New().String(), - ReleaseId: release.ID(), - Status: oapi.JobStatusSuccessful, - CreatedAt: time.Now(), - } - - // Create a policy with both verification and approval rules triggerOn := oapi.JobSuccess policy := createPolicyWithRules([]oapi.PolicyRule{ { @@ -578,36 +415,21 @@ func TestVerificationAction_Execute_PolicyWithMixedRules(t *testing.T) { Policies: []*oapi.Policy{policy}, } - // Execute the action err := vAction.Execute(ctx, action.TriggerJobSuccess, actx) require.NoError(t, err) - - // Verification should be created (approval rule should be ignored by verification action) - verifications := s.JobVerifications.GetByJobId(job.Id) - require.Len(t, verifications, 1) - v := verifications[0] - assert.Equal(t, 1, len(v.Metrics)) - assert.Equal(t, "health-check", v.Metrics[0].Name) + assert.Equal(t, 1, starter.callCount()) + assert.Len(t, starter.lastCall().Specs, 1) + assert.Equal(t, "health-check", starter.lastCall().Specs[0].Name) } -// Test for multiple verification rules per policy - func TestVerificationAction_Execute_MultipleVerificationRulesInPolicy(t *testing.T) { ctx := context.Background() - s := newTestStore() - verificationMgr := verification.NewManager(s) - vAction := verificationaction.NewVerificationAction(verificationMgr) - - release := createTestRelease(s, ctx) + starter := &mockStarter{} + vAction := verificationaction.NewVerificationAction(starter) - job := &oapi.Job{ - Id: uuid.New().String(), - ReleaseId: release.ID(), - Status: oapi.JobStatusSuccessful, - CreatedAt: time.Now(), - } + release := newRelease() + job := newJob(release.ID()) - // Create a policy with multiple verification rules triggerOn := oapi.JobSuccess policy := createPolicyWithRules([]oapi.PolicyRule{ { @@ -639,50 +461,36 @@ func TestVerificationAction_Execute_MultipleVerificationRulesInPolicy(t *testing Policies: []*oapi.Policy{policy}, } - // Execute the action err := vAction.Execute(ctx, action.TriggerJobSuccess, actx) require.NoError(t, err) - // Verification should be created with metrics from all rules - verifications := s.JobVerifications.GetByJobId(job.Id) - require.Len(t, verifications, 1) - v := verifications[0] - assert.Equal(t, 3, len(v.Metrics)) + assert.Equal(t, 1, starter.callCount()) + call := starter.lastCall() + assert.Len(t, call.Specs, 3) - // Check all metrics are present names := make(map[string]bool) - for _, m := range v.Metrics { - names[m.Name] = true + for _, s := range call.Specs { + names[s.Name] = true } assert.True(t, names["health-check"]) assert.True(t, names["latency-check"]) assert.True(t, names["error-rate"]) } -// Edge case tests - func TestVerificationAction_Execute_NilVerificationRule(t *testing.T) { ctx := context.Background() - s := newTestStore() - verificationMgr := verification.NewManager(s) - vAction := verificationaction.NewVerificationAction(verificationMgr) - - release := createTestRelease(s, ctx) + starter := &mockStarter{} + vAction := verificationaction.NewVerificationAction(starter) - job := &oapi.Job{ - Id: uuid.New().String(), - ReleaseId: release.ID(), - Status: oapi.JobStatusSuccessful, - CreatedAt: time.Now(), - } + release := newRelease() + job := newJob(release.ID()) - // Create a policy with a rule that has no verification (nil Verification field) policy := createPolicyWithRules([]oapi.PolicyRule{ { Id: uuid.New().String(), PolicyId: uuid.New().String(), CreatedAt: time.Now().Format(time.RFC3339), - Verification: nil, // No verification rule + Verification: nil, }, }) @@ -692,31 +500,19 @@ func TestVerificationAction_Execute_NilVerificationRule(t *testing.T) { Policies: []*oapi.Policy{policy}, } - // Execute the action err := vAction.Execute(ctx, action.TriggerJobSuccess, actx) require.NoError(t, err) - - // No verification should be created - verifications := s.JobVerifications.GetByJobId(job.Id) - assert.Empty(t, verifications) + assert.Equal(t, 0, starter.callCount()) } func TestVerificationAction_Execute_EmptyMetricsArray(t *testing.T) { ctx := context.Background() - s := newTestStore() - verificationMgr := verification.NewManager(s) - vAction := verificationaction.NewVerificationAction(verificationMgr) - - release := createTestRelease(s, ctx) + starter := &mockStarter{} + vAction := verificationaction.NewVerificationAction(starter) - job := &oapi.Job{ - Id: uuid.New().String(), - ReleaseId: release.ID(), - Status: oapi.JobStatusSuccessful, - CreatedAt: time.Now(), - } + release := newRelease() + job := newJob(release.ID()) - // Create a policy with verification rule but empty metrics triggerOn := oapi.JobSuccess policy := createPolicyWithRules([]oapi.PolicyRule{ { @@ -724,7 +520,7 @@ func TestVerificationAction_Execute_EmptyMetricsArray(t *testing.T) { PolicyId: uuid.New().String(), CreatedAt: time.Now().Format(time.RFC3339), Verification: &oapi.VerificationRule{ - Metrics: []oapi.VerificationMetricSpec{}, // Empty metrics + Metrics: []oapi.VerificationMetricSpec{}, TriggerOn: &triggerOn, }, }, @@ -736,62 +532,39 @@ func TestVerificationAction_Execute_EmptyMetricsArray(t *testing.T) { Policies: []*oapi.Policy{policy}, } - // Execute the action err := vAction.Execute(ctx, action.TriggerJobSuccess, actx) require.NoError(t, err) - - // No verification should be created (no metrics) - verifications := s.JobVerifications.GetByJobId(job.Id) - assert.Empty(t, verifications) + assert.Equal(t, 0, starter.callCount()) } func TestVerificationAction_Execute_NilPoliciesSlice(t *testing.T) { ctx := context.Background() - s := newTestStore() - verificationMgr := verification.NewManager(s) - vAction := verificationaction.NewVerificationAction(verificationMgr) - - release := createTestRelease(s, ctx) + starter := &mockStarter{} + vAction := verificationaction.NewVerificationAction(starter) - job := &oapi.Job{ - Id: uuid.New().String(), - ReleaseId: release.ID(), - Status: oapi.JobStatusSuccessful, - CreatedAt: time.Now(), - } + release := newRelease() + job := newJob(release.ID()) actx := action.ActionContext{ Job: job, Release: release, - Policies: nil, // nil policies + Policies: nil, } - // Execute the action - should not panic err := vAction.Execute(ctx, action.TriggerJobSuccess, actx) require.NoError(t, err) - - // No verification should be created - verifications := s.JobVerifications.GetByJobId(job.Id) - assert.Empty(t, verifications) + assert.Equal(t, 0, starter.callCount()) } func TestVerificationAction_Execute_PolicyWithNoRules(t *testing.T) { ctx := context.Background() - s := newTestStore() - verificationMgr := verification.NewManager(s) - vAction := verificationaction.NewVerificationAction(verificationMgr) - - release := createTestRelease(s, ctx) + starter := &mockStarter{} + vAction := verificationaction.NewVerificationAction(starter) - job := &oapi.Job{ - Id: uuid.New().String(), - ReleaseId: release.ID(), - Status: oapi.JobStatusSuccessful, - CreatedAt: time.Now(), - } + release := newRelease() + job := newJob(release.ID()) - // Create a policy with no rules - policy := createPolicyWithRules([]oapi.PolicyRule{}) // Empty rules + policy := createPolicyWithRules([]oapi.PolicyRule{}) actx := action.ActionContext{ Job: job, @@ -799,33 +572,19 @@ func TestVerificationAction_Execute_PolicyWithNoRules(t *testing.T) { Policies: []*oapi.Policy{policy}, } - // Execute the action err := vAction.Execute(ctx, action.TriggerJobSuccess, actx) require.NoError(t, err) - - // No verification should be created - verifications := s.JobVerifications.GetByJobId(job.Id) - assert.Empty(t, verifications) + assert.Equal(t, 0, starter.callCount()) } -// Verification lifecycle and metric spec tests - -func TestVerificationAction_Execute_VerificationIsRunningWithCorrectMetricSpecs(t *testing.T) { +func TestVerificationAction_Execute_MetricSpecsAreCorrectlyPassed(t *testing.T) { ctx := context.Background() - s := newTestStore() - verificationMgr := verification.NewManager(s) - vAction := verificationaction.NewVerificationAction(verificationMgr) - - release := createTestRelease(s, ctx) + starter := &mockStarter{} + vAction := verificationaction.NewVerificationAction(starter) - job := &oapi.Job{ - Id: uuid.New().String(), - ReleaseId: release.ID(), - Status: oapi.JobStatusSuccessful, - CreatedAt: time.Now(), - } + release := newRelease() + job := newJob(release.ID()) - // Create a metric with specific configuration provider := oapi.MetricProvider{} _ = provider.FromSleepMetricProvider(oapi.SleepMetricProvider{ Type: oapi.Sleep, @@ -853,48 +612,38 @@ func TestVerificationAction_Execute_VerificationIsRunningWithCorrectMetricSpecs( Policies: []*oapi.Policy{policy}, } - // Execute the action err := vAction.Execute(ctx, action.TriggerJobSuccess, actx) require.NoError(t, err) - // Verification should exist and be running - verifications := s.JobVerifications.GetByJobId(job.Id) - require.Len(t, verifications, 1, "verification should exist") - v := verifications[0] - assert.Equal(t, oapi.JobVerificationStatusRunning, v.Status(), "verification should be in running status") - - // Verify metric specifications are correctly transferred - require.Equal(t, 1, len(v.Metrics), "should have one metric") - metricStatus := v.Metrics[0] - assert.Equal(t, "detailed-health-check", metricStatus.Name) - assert.EqualValues(t, 60, metricStatus.IntervalSeconds) - assert.EqualValues(t, 10, metricStatus.Count) - assert.Equal(t, "result.statusCode == 200", metricStatus.SuccessCondition) - require.NotNil(t, metricStatus.FailureThreshold) - assert.Equal(t, 2, *metricStatus.FailureThreshold) - - // Measurements should be empty initially (verification just started) - assert.Empty(t, metricStatus.Measurements, "measurements should be empty initially") + assert.Equal(t, 1, starter.callCount()) + call := starter.lastCall() + assert.Equal(t, job.Id, call.JobID) + require.Len(t, call.Specs, 1) + + spec := call.Specs[0] + assert.Equal(t, "detailed-health-check", spec.Name) + assert.EqualValues(t, 60, spec.IntervalSeconds) + assert.EqualValues(t, 10, spec.Count) + assert.Equal(t, "result.statusCode == 200", spec.SuccessCondition) + require.NotNil(t, spec.FailureThreshold) + assert.Equal(t, 2, *spec.FailureThreshold) } -func TestVerificationAction_Execute_VerificationRecordHasCorrectReleaseLink(t *testing.T) { +func TestVerificationAction_Execute_MultipleMetrics(t *testing.T) { ctx := context.Background() - s := newTestStore() - verificationMgr := verification.NewManager(s) - vAction := verificationaction.NewVerificationAction(verificationMgr) - - release := createTestRelease(s, ctx) + starter := &mockStarter{} + vAction := verificationaction.NewVerificationAction(starter) - job := &oapi.Job{ - Id: uuid.New().String(), - ReleaseId: release.ID(), - Status: oapi.JobStatusSuccessful, - CreatedAt: time.Now(), - } + release := newRelease() + job := newJob(release.ID()) triggerOn := oapi.JobSuccess policy := createPolicyWithVerification( - []oapi.VerificationMetricSpec{createTestMetric("health-check")}, + []oapi.VerificationMetricSpec{ + createTestMetric("health-check"), + createTestMetric("latency-check"), + createTestMetric("error-rate"), + }, &triggerOn, ) @@ -904,48 +653,24 @@ func TestVerificationAction_Execute_VerificationRecordHasCorrectReleaseLink(t *t Policies: []*oapi.Policy{policy}, } - // Execute the action err := vAction.Execute(ctx, action.TriggerJobSuccess, actx) require.NoError(t, err) - // Verification should be correctly linked to the job - verifications := s.JobVerifications.GetByJobId(job.Id) - require.Len(t, verifications, 1) - v := verifications[0] - - // Verify the job link is correct - assert.Equal(t, job.Id, v.JobId, "verification should be linked to correct job") - - // Verification should also be retrievable by its own ID - vById, existsById := s.JobVerifications.Get(v.Id) - require.True(t, existsById, "verification should be retrievable by ID") - assert.Equal(t, v.Id, vById.Id) - assert.Equal(t, job.Id, vById.JobId) + assert.Equal(t, 1, starter.callCount()) + assert.Len(t, starter.lastCall().Specs, 3) } -func TestVerificationAction_Execute_MultipleMetricsAllRunning(t *testing.T) { +func TestVerificationAction_Execute_StarterError(t *testing.T) { ctx := context.Background() - s := newTestStore() - verificationMgr := verification.NewManager(s) - vAction := verificationaction.NewVerificationAction(verificationMgr) - - release := createTestRelease(s, ctx) + starter := &mockStarter{err: assert.AnError} + vAction := verificationaction.NewVerificationAction(starter) - job := &oapi.Job{ - Id: uuid.New().String(), - ReleaseId: release.ID(), - Status: oapi.JobStatusSuccessful, - CreatedAt: time.Now(), - } + release := newRelease() + job := newJob(release.ID()) - // Create multiple metrics triggerOn := oapi.JobSuccess policy := createPolicyWithVerification( - []oapi.VerificationMetricSpec{ - createTestMetric("health-check"), - createTestMetric("latency-check"), - createTestMetric("error-rate"), - }, + []oapi.VerificationMetricSpec{createTestMetric("health-check")}, &triggerOn, ) @@ -955,21 +680,7 @@ func TestVerificationAction_Execute_MultipleMetricsAllRunning(t *testing.T) { Policies: []*oapi.Policy{policy}, } - // Execute the action err := vAction.Execute(ctx, action.TriggerJobSuccess, actx) - require.NoError(t, err) - - // Verification should be running - verifications := s.JobVerifications.GetByJobId(job.Id) - require.Len(t, verifications, 1) - v := verifications[0] - assert.Equal(t, oapi.JobVerificationStatusRunning, v.Status()) - - // All metrics should be present - require.Equal(t, 3, len(v.Metrics)) - - // Each metric should have empty measurements (just started) - for _, m := range v.Metrics { - assert.Empty(t, m.Measurements, "metric %s should have empty measurements initially", m.Name) - } + assert.Error(t, err) + assert.Equal(t, 1, starter.callCount()) } diff --git a/apps/workspace-engine/pkg/workspace/releasemanager/deployment/executor_test.go b/apps/workspace-engine/pkg/workspace/releasemanager/deployment/executor_test.go index 96eeb28e2..0c1c57c48 100644 --- a/apps/workspace-engine/pkg/workspace/releasemanager/deployment/executor_test.go +++ b/apps/workspace-engine/pkg/workspace/releasemanager/deployment/executor_test.go @@ -8,7 +8,6 @@ import ( "workspace-engine/pkg/oapi" "workspace-engine/pkg/statechange" "workspace-engine/pkg/workspace/jobagents" - "workspace-engine/pkg/workspace/releasemanager/verification" "workspace-engine/pkg/workspace/store" "github.com/google/uuid" @@ -22,8 +21,7 @@ func setupTestExecutor(t *testing.T) (*Executor, *store.Store) { t.Helper() cs := statechange.NewChangeSet[any]() testStore := store.New("test-workspace", cs) - testVerification := verification.NewManager(testStore) - testJobAgentRegistry := jobagents.NewRegistry(testStore, testVerification) + testJobAgentRegistry := jobagents.NewRegistry(testStore) executor := NewExecutor(testStore, testJobAgentRegistry) return executor, testStore } diff --git a/apps/workspace-engine/pkg/workspace/releasemanager/manager.go b/apps/workspace-engine/pkg/workspace/releasemanager/manager.go index 65cf9980c..2a6e7bbba 100644 --- a/apps/workspace-engine/pkg/workspace/releasemanager/manager.go +++ b/apps/workspace-engine/pkg/workspace/releasemanager/manager.go @@ -9,12 +9,10 @@ import ( "workspace-engine/pkg/oapi" "workspace-engine/pkg/statechange" "workspace-engine/pkg/workspace/jobagents" - "workspace-engine/pkg/workspace/releasemanager/action/rollback" "workspace-engine/pkg/workspace/releasemanager/deployment" "workspace-engine/pkg/workspace/releasemanager/policy" "workspace-engine/pkg/workspace/releasemanager/trace" "workspace-engine/pkg/workspace/releasemanager/variables" - "workspace-engine/pkg/workspace/releasemanager/verification" "workspace-engine/pkg/workspace/releasemanager/versions" "workspace-engine/pkg/workspace/store" @@ -28,13 +26,12 @@ import ( // Manager handles the business logic for release target changes and deployment decisions. // It coordinates the state index, eligibility checking, and execution to manage release targets. type Manager struct { - store *store.Store - planner *deployment.Planner - eligibility *deployment.JobEligibilityChecker - executor *deployment.Executor - verification *verification.Manager - traceStore PersistenceStore - stateIndex *StateIndex + store *store.Store + planner *deployment.Planner + eligibility *deployment.JobEligibilityChecker + executor *deployment.Executor + traceStore PersistenceStore + stateIndex *StateIndex } var tracer = otel.Tracer("workspace/releasemanager") @@ -44,7 +41,7 @@ type PersistenceStore = trace.PersistenceStore // New creates a new release manager for a workspace. // traceStore must not be nil - panics if not provided. -func New(store *store.Store, traceStore PersistenceStore, verificationManager *verification.Manager, jobAgentRegistry *jobagents.Registry) *Manager { +func New(store *store.Store, traceStore PersistenceStore, jobAgentRegistry *jobagents.Registry) *Manager { if traceStore == nil { panic("traceStore cannot be nil - deployment tracing is mandatory") } @@ -58,19 +55,13 @@ func New(store *store.Store, traceStore PersistenceStore, verificationManager *v executor := deployment.NewExecutor(store, jobAgentRegistry) stateIndex := NewStateIndex(store, planner) - releaseManagerHooks := newReleaseManagerVerificationHooks(store, stateIndex) - rollbackHooks := rollback.NewRollbackHooks(store, jobAgentRegistry) - compositeHooks := verification.NewCompositeHooks(releaseManagerHooks, rollbackHooks) - verificationManager.SetHooks(compositeHooks) - return &Manager{ - store: store, - planner: planner, - eligibility: eligibility, - executor: executor, - verification: verificationManager, - traceStore: traceStore, - stateIndex: stateIndex, + store: store, + planner: planner, + eligibility: eligibility, + executor: executor, + traceStore: traceStore, + stateIndex: stateIndex, } } @@ -83,10 +74,6 @@ type targetState struct { isDelete bool } -func (m *Manager) VerificationManager() *verification.Manager { - return m.verification -} - // ProcessChanges handles detected changes to release targets (WRITES TO STORE). // Reconciles added/tainted targets (triggers deployments) and removes deleted targets. // Returns a map of cancelled jobs (for removed targets). @@ -526,12 +513,6 @@ func (m *Manager) Restore(ctx context.Context) error { ctx, span := tracer.Start(ctx, "ReleaseManager.Restore") defer span.End() - if err := m.verification.Restore(ctx); err != nil { - span.RecordError(err) - span.SetStatus(codes.Error, "failed to restore verifications") - log.Error("failed to restore verifications", "error", err.Error()) - } - // Pre-compute the state index for every release target that was restored // from persistence. This avoids per-request lazy registration and ensures // the first API read is fast. diff --git a/apps/workspace-engine/pkg/workspace/releasemanager/manager_test.go b/apps/workspace-engine/pkg/workspace/releasemanager/manager_test.go index 5cff571f7..191236993 100644 --- a/apps/workspace-engine/pkg/workspace/releasemanager/manager_test.go +++ b/apps/workspace-engine/pkg/workspace/releasemanager/manager_test.go @@ -8,7 +8,6 @@ import ( "workspace-engine/pkg/statechange" "workspace-engine/pkg/workspace/jobagents" "workspace-engine/pkg/workspace/releasemanager/trace/spanstore" - "workspace-engine/pkg/workspace/releasemanager/verification" "workspace-engine/pkg/workspace/store" "github.com/google/uuid" @@ -22,9 +21,8 @@ func setupTestManager(t *testing.T) (*Manager, *store.Store) { cs := statechange.NewChangeSet[any]() testStore := store.New("test-workspace", cs) traceStore := spanstore.NewInMemoryStore() - verificationManager := verification.NewManager(testStore) - jobAgentRegistry := jobagents.NewRegistry(testStore, verificationManager) - manager := New(testStore, traceStore, verificationManager, jobAgentRegistry) + jobAgentRegistry := jobagents.NewRegistry(testStore) + manager := New(testStore, traceStore, jobAgentRegistry) return manager, testStore } diff --git a/apps/workspace-engine/pkg/workspace/releasemanager/verification/executor.go b/apps/workspace-engine/pkg/workspace/releasemanager/verification/executor.go deleted file mode 100644 index 5da2cb9af..000000000 --- a/apps/workspace-engine/pkg/workspace/releasemanager/verification/executor.go +++ /dev/null @@ -1,80 +0,0 @@ -package verification - -import ( - "context" - "fmt" - "workspace-engine/pkg/oapi" - "workspace-engine/pkg/workspace/releasemanager/verification/metrics" - "workspace-engine/pkg/workspace/releasemanager/verification/metrics/provider" - "workspace-engine/pkg/workspace/store" - - "github.com/charmbracelet/log" -) - -// MeasurementExecutor handles taking measurements for verification metrics. -// It is responsible for building provider context and executing the measurement, -// but not for storing results (that's the recorder's job). -type MeasurementExecutor struct { - store *store.Store -} - -// NewMeasurementExecutor creates a new measurement executor -func NewMeasurementExecutor(store *store.Store) *MeasurementExecutor { - return &MeasurementExecutor{store: store} -} - -// Execute takes a single measurement for the given metric. -// The metric is passed directly - no store lookup required. -// Returns the measurement result or an error if measurement failed. -func (e *MeasurementExecutor) Execute( - ctx context.Context, - metric *oapi.VerificationMetricStatus, - releaseID string, -) (oapi.VerificationMeasurement, error) { - log.Debug("Executing measurement", - "metric_name", metric.Name, - "release_id", releaseID) - - // Build provider context from the release - providerCtx, err := e.BuildProviderContext(releaseID) - if err != nil { - return oapi.VerificationMeasurement{}, fmt.Errorf("failed to build provider context: %w", err) - } - - // Take measurement using the Measure function - return metrics.Measure(ctx, metric, providerCtx) -} - -// BuildProviderContext creates the context needed for metric providers. -// It gathers release, resource, environment, deployment, and variables from the store. -func (e *MeasurementExecutor) BuildProviderContext(releaseID string) (*provider.ProviderContext, error) { - release, ok := e.store.Releases.Get(releaseID) - if !ok { - return nil, fmt.Errorf("release not found: %s", releaseID) - } - - // Get the resource - resource, _ := e.store.Resources.Get(release.ReleaseTarget.ResourceId) - - // Get the environment - environment, _ := e.store.Environments.Get(release.ReleaseTarget.EnvironmentId) - - // Get the deployment - deployment, _ := e.store.Deployments.Get(release.Version.DeploymentId) - - // Get variables from release - variables := make(map[string]any) - for k, v := range release.Variables { - variables[k] = v - } - - return &provider.ProviderContext{ - Release: release, - Resource: resource, - Environment: environment, - Version: &release.Version, - Target: &release.ReleaseTarget, - Deployment: deployment, - Variables: variables, - }, nil -} diff --git a/apps/workspace-engine/pkg/workspace/releasemanager/verification/executor_test.go b/apps/workspace-engine/pkg/workspace/releasemanager/verification/executor_test.go deleted file mode 100644 index 77e2b6ff8..000000000 --- a/apps/workspace-engine/pkg/workspace/releasemanager/verification/executor_test.go +++ /dev/null @@ -1,215 +0,0 @@ -package verification - -import ( - "context" - "net/http" - "net/http/httptest" - "testing" - "time" - "workspace-engine/pkg/oapi" - - "github.com/google/uuid" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestNewMeasurementExecutor(t *testing.T) { - s := newTestStore() - executor := NewMeasurementExecutor(s) - - assert.NotNil(t, executor) - assert.NotNil(t, executor.store) -} - -func TestExecutor_Execute_Success(t *testing.T) { - ctx := context.Background() - s := newTestStore() - executor := NewMeasurementExecutor(s) - - // Create a test HTTP server that returns 200 - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(`{"status": "healthy"}`)) - })) - defer server.Close() - - // Create release - release := createTestRelease(s, ctx) - metric := createHTTPMetricStatus(server.URL) - - // Execute measurement with direct objects - measurement, err := executor.Execute(ctx, &metric, release.ID()) - - require.NoError(t, err) - assert.Equal(t, oapi.Passed, measurement.Status) - assert.NotNil(t, measurement.Data) - assert.NotZero(t, measurement.MeasuredAt) -} - -func TestExecutor_Execute_ReleaseNotFound(t *testing.T) { - ctx := context.Background() - s := newTestStore() - executor := NewMeasurementExecutor(s) - - metric := createHTTPMetricStatus("http://example.com/health") - nonExistentReleaseID := "non-existent-release-id" - - _, err := executor.Execute(ctx, &metric, nonExistentReleaseID) - - assert.Error(t, err) - assert.Contains(t, err.Error(), "failed to build provider context") - assert.Contains(t, err.Error(), "release not found") -} - -func TestExecutor_Execute_FailedMeasurement(t *testing.T) { - ctx := context.Background() - s := newTestStore() - executor := NewMeasurementExecutor(s) - - // Create a test HTTP server that returns 500 - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusInternalServerError) - _, _ = w.Write([]byte(`{"error": "internal server error"}`)) - })) - defer server.Close() - - release := createTestRelease(s, ctx) - metric := createHTTPMetricStatus(server.URL) - - // Execute measurement - should not error but should fail - // (success condition not met, no failure condition = binary pass/fail) - measurement, err := executor.Execute(ctx, &metric, release.ID()) - - require.NoError(t, err) - assert.Equal(t, oapi.Failed, measurement.Status) - assert.NotNil(t, measurement.Data) -} - -func TestExecutor_Execute_ContextCancellation(t *testing.T) { - s := newTestStore() - executor := NewMeasurementExecutor(s) - - // Create a server that delays response - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - time.Sleep(5 * time.Second) - w.WriteHeader(http.StatusOK) - })) - defer server.Close() - - ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) - defer cancel() - - release := createTestRelease(s, ctx) - metric := createHTTPMetricStatus(server.URL) - - // Execute should fail due to context timeout - _, err := executor.Execute(ctx, &metric, release.ID()) - - assert.Error(t, err) -} - -func TestExecutor_Execute_WithTemplatedURL(t *testing.T) { - ctx := context.Background() - s := newTestStore() - executor := NewMeasurementExecutor(s) - - // Create a test server - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - })) - defer server.Close() - - release := createTestRelease(s, ctx) - - // Create metric with templated URL (templates use release context) - metric := createHTTPMetricStatus(server.URL + "/{{.release.version.tag}}") - - // Execute - template should be resolved - measurement, err := executor.Execute(ctx, &metric, release.ID()) - - require.NoError(t, err) - assert.Equal(t, oapi.Passed, measurement.Status) -} - -func TestExecutor_BuildProviderContext_Success(t *testing.T) { - ctx := context.Background() - s := newTestStore() - executor := NewMeasurementExecutor(s) - - release := createTestRelease(s, ctx) - - providerCtx, err := executor.BuildProviderContext(release.ID()) - - require.NoError(t, err) - require.NotNil(t, providerCtx) - assert.Equal(t, release, providerCtx.Release) - assert.NotNil(t, providerCtx.Resource) - assert.NotNil(t, providerCtx.Environment) - assert.NotNil(t, providerCtx.Version) - assert.NotNil(t, providerCtx.Target) - assert.NotNil(t, providerCtx.Deployment) - assert.NotNil(t, providerCtx.Variables) -} - -func TestExecutor_BuildProviderContext_ReleaseNotFound(t *testing.T) { - s := newTestStore() - executor := NewMeasurementExecutor(s) - - nonExistentID := uuid.New().String() - providerCtx, err := executor.BuildProviderContext(nonExistentID) - - assert.Error(t, err) - assert.Nil(t, providerCtx) - assert.Contains(t, err.Error(), "release not found") -} - -func TestExecutor_BuildProviderContext_WithVariables(t *testing.T) { - ctx := context.Background() - s := newTestStore() - executor := NewMeasurementExecutor(s) - - release := createTestRelease(s, ctx) - - // Add variables to release - envVal := oapi.LiteralValue{} - _ = envVal.FromStringValue("production") - versionVal := oapi.LiteralValue{} - _ = versionVal.FromStringValue("1.2.3") - release.Variables = map[string]oapi.LiteralValue{ - "env": envVal, - "version": versionVal, - } - _ = s.Releases.Upsert(ctx, release) - - providerCtx, err := executor.BuildProviderContext(release.ID()) - - require.NoError(t, err) - require.NotNil(t, providerCtx) - assert.Equal(t, 2, len(providerCtx.Variables)) - assert.Contains(t, providerCtx.Variables, "env") - assert.Contains(t, providerCtx.Variables, "version") -} - -// Helper function to create an HTTP metric status -func createHTTPMetricStatus(url string) oapi.VerificationMetricStatus { - method := oapi.GET - timeout := "5s" - httpProvider := oapi.HTTPMetricProvider{ - Url: url, - Method: &method, - Type: oapi.Http, - Timeout: &timeout, - } - provider := oapi.MetricProvider{} - _ = provider.FromHTTPMetricProvider(httpProvider) - - return oapi.VerificationMetricStatus{ - Name: "test-metric", - IntervalSeconds: 60, - Count: 5, - SuccessCondition: "result.statusCode == 200", - FailureThreshold: ptr(2), - Provider: provider, - Measurements: []oapi.VerificationMeasurement{}, - } -} diff --git a/apps/workspace-engine/pkg/workspace/releasemanager/verification/hooks.go b/apps/workspace-engine/pkg/workspace/releasemanager/verification/hooks.go deleted file mode 100644 index 25a7eeb48..000000000 --- a/apps/workspace-engine/pkg/workspace/releasemanager/verification/hooks.go +++ /dev/null @@ -1,114 +0,0 @@ -package verification - -import ( - "context" - "workspace-engine/pkg/oapi" -) - -type VerificationHooks interface { - // OnVerificationStarted is called when a verification is created and started - OnVerificationStarted(ctx context.Context, verification *oapi.JobVerification) error - - // OnMeasurementTaken is called after each metric measurement - OnMeasurementTaken(ctx context.Context, verification *oapi.JobVerification, metricIndex int, measurement *oapi.VerificationMeasurement) error - - // OnMetricComplete is called when a metric finishes (reached count or failure limit) - OnMetricComplete(ctx context.Context, verification *oapi.JobVerification, metricIndex int) error - - // OnVerificationComplete is called when all metrics complete (passed/failed/cancelled) - OnVerificationComplete(ctx context.Context, verification *oapi.JobVerification) error - - // OnVerificationStopped is called when a verification is manually stopped - OnVerificationStopped(ctx context.Context, verification *oapi.JobVerification) error -} - -type defaultHooks struct { -} - -// OnMeasurementTaken implements VerificationHooks. -func (h *defaultHooks) OnMeasurementTaken(ctx context.Context, verification *oapi.JobVerification, metricIndex int, measurement *oapi.VerificationMeasurement) error { - return nil -} - -// OnMetricComplete implements VerificationHooks. -func (h *defaultHooks) OnMetricComplete(ctx context.Context, verification *oapi.JobVerification, metricIndex int) error { - return nil -} - -// OnVerificationStarted implements VerificationHooks. -func (h *defaultHooks) OnVerificationStarted(ctx context.Context, verification *oapi.JobVerification) error { - return nil -} - -func (h *defaultHooks) OnVerificationComplete(ctx context.Context, verification *oapi.JobVerification) error { - return nil -} - -func (h *defaultHooks) OnVerificationStopped(ctx context.Context, verification *oapi.JobVerification) error { - return nil -} - -func DefaultHooks() VerificationHooks { - return &defaultHooks{} -} - -// CompositeHooks combines multiple VerificationHooks implementations. -// All hooks are called in order; errors are logged but don't stop subsequent hooks. -type CompositeHooks struct { - hooks []VerificationHooks -} - -// NewCompositeHooks creates a new CompositeHooks that runs all provided hooks. -func NewCompositeHooks(hooks ...VerificationHooks) *CompositeHooks { - return &CompositeHooks{hooks: hooks} -} - -// OnVerificationStarted calls OnVerificationStarted on all hooks. -func (c *CompositeHooks) OnVerificationStarted(ctx context.Context, verification *oapi.JobVerification) (e error) { - for _, h := range c.hooks { - if err := h.OnVerificationStarted(ctx, verification); err != nil { - e = err - } - } - return -} - -// OnMeasurementTaken calls OnMeasurementTaken on all hooks. -func (c *CompositeHooks) OnMeasurementTaken(ctx context.Context, verification *oapi.JobVerification, metricIndex int, measurement *oapi.VerificationMeasurement) (e error) { - for _, h := range c.hooks { - if err := h.OnMeasurementTaken(ctx, verification, metricIndex, measurement); err != nil { - e = err - } - } - return -} - -// OnMetricComplete calls OnMetricComplete on all hooks. -func (c *CompositeHooks) OnMetricComplete(ctx context.Context, verification *oapi.JobVerification, metricIndex int) (e error) { - for _, h := range c.hooks { - if err := h.OnMetricComplete(ctx, verification, metricIndex); err != nil { - e = err - } - } - return -} - -// OnVerificationComplete calls OnVerificationComplete on all hooks. -func (c *CompositeHooks) OnVerificationComplete(ctx context.Context, verification *oapi.JobVerification) (e error) { - for _, h := range c.hooks { - if err := h.OnVerificationComplete(ctx, verification); err != nil { - e = err - } - } - return -} - -// OnVerificationStopped calls OnVerificationStopped on all hooks. -func (c *CompositeHooks) OnVerificationStopped(ctx context.Context, verification *oapi.JobVerification) (e error) { - for _, h := range c.hooks { - if err := h.OnVerificationStopped(ctx, verification); err != nil { - e = err - } - } - return -} diff --git a/apps/workspace-engine/pkg/workspace/releasemanager/verification/manager.go b/apps/workspace-engine/pkg/workspace/releasemanager/verification/manager.go deleted file mode 100644 index 7b3179c9e..000000000 --- a/apps/workspace-engine/pkg/workspace/releasemanager/verification/manager.go +++ /dev/null @@ -1,185 +0,0 @@ -package verification - -import ( - "context" - "fmt" - "time" - "workspace-engine/pkg/oapi" - "workspace-engine/pkg/workspace/store" - - "github.com/charmbracelet/log" - "github.com/google/uuid" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" - "go.opentelemetry.io/otel/trace" -) - -var tracer = otel.Tracer("workspace/releasemanager/verification") - -// Manager handles post-deployment verification of jobs -// It uses a scheduler to run verifications from the store -type Manager struct { - store *store.Store - scheduler *scheduler - hooks VerificationHooks -} - -func NewManager(store *store.Store, opts ...ManagerOption) *Manager { - m := &Manager{ - store: store, - hooks: DefaultHooks(), - } - for _, opt := range opts { - opt(m) - } - - m.scheduler = newScheduler(store, m.hooks) - return m -} - -func (m *Manager) SetHooks(hooks VerificationHooks) { - m.hooks = hooks - m.scheduler = newScheduler(m.store, hooks) -} - -type ManagerOption func(*Manager) - -func WithHooks(hooks VerificationHooks) ManagerOption { - return func(m *Manager) { - m.hooks = hooks - } -} - -// OnLoad restarts goroutines for any unfinished verifications -// This should be called when the application starts to resume any in-progress verifications -func (m *Manager) Restore(ctx context.Context) error { - ctx, span := tracer.Start(ctx, "VerificationManager.Restore") - defer span.End() - - // Get all verifications from the store - allVerifications := m.store.JobVerifications.Items() - - restarted := 0 - for _, verification := range allVerifications { - // Check if verification is not finished - status := verification.Status() - if status != oapi.JobVerificationStatusPassed && - status != oapi.JobVerificationStatusFailed && - status != oapi.JobVerificationStatusCancelled { - // Restart the verification goroutines - m.scheduler.StartVerification(ctx, verification.Id) - restarted++ - log.Info("Restarted verification on load", - "verification_id", verification.Id, - "job_id", verification.JobId, - "status", status, - "metric_count", len(verification.Metrics)) - } - } - - span.SetAttributes(attribute.Int("restarted_count", restarted)) - span.SetStatus(codes.Ok, "verifications loaded") - - if restarted > 0 { - log.Info("Restarted verifications on load", "count", restarted) - } - - return nil -} - -// StartVerification creates a new verification and starts goroutines to run measurements -func (m *Manager) StartVerification( - ctx context.Context, - job *oapi.Job, - metrics []oapi.VerificationMetricSpec, -) error { - ctx, span := tracer.Start(ctx, "StartVerification", - trace.WithAttributes( - attribute.String("job.id", job.Id), - attribute.String("release.id", job.ReleaseId), - )) - defer span.End() - - // Require metric configuration - if len(metrics) == 0 { - return fmt.Errorf("at least one metric configuration is required for verification") - } - - // Convert VerificationMetricSpecs to VerificationMetricStatus with empty measurements - metricStatuses := make([]oapi.VerificationMetricStatus, len(metrics)) - for i, metric := range metrics { - metricStatuses[i] = oapi.VerificationMetricStatus{ - Name: metric.Name, - IntervalSeconds: metric.IntervalSeconds, - Count: metric.Count, - SuccessCondition: metric.SuccessCondition, - FailureThreshold: metric.FailureThreshold, - FailureCondition: metric.FailureCondition, - SuccessThreshold: metric.SuccessThreshold, - Provider: metric.Provider, - Measurements: []oapi.VerificationMeasurement{}, - } - } - - verificationRecord := &oapi.JobVerification{ - Id: uuid.New().String(), - JobId: job.Id, - Metrics: metricStatuses, - CreatedAt: time.Now(), - } - - // Store the verification - m.store.JobVerifications.Upsert(ctx, verificationRecord) - - // Start goroutine for this verification - m.scheduler.StartVerification(ctx, verificationRecord.Id) - - // Call hook after verification is started - if err := m.hooks.OnVerificationStarted(ctx, verificationRecord); err != nil { - log.Error("Verification started hook failed", - "verification_id", verificationRecord.Id, - "error", err) - // Don't fail the verification due to hook errors - } - - span.SetAttributes(attribute.String("verification.status", "created")) - span.SetStatus(codes.Ok, "verification created") - - log.Info("Created verification for job", - "job_id", job.Id, - "release_id", job.ReleaseId, - "verification_id", verificationRecord.Id, - "metric_count", len(metrics)) - - return nil -} - -// StopVerificationsForJob cancels all verifications for a job and stops their goroutines -func (m *Manager) StopVerificationsForJob(ctx context.Context, jobID string) { - ctx, span := tracer.Start(ctx, "StopVerificationsForJob", - trace.WithAttributes( - attribute.String("job.id", jobID), - )) - defer span.End() - - verifications := m.store.JobVerifications.GetByJobId(jobID) - for _, verification := range verifications { - // Stop the goroutines - m.scheduler.StopVerification(verification.Id) - - // Call hook after verification is stopped - if err := m.hooks.OnVerificationStopped(ctx, verification); err != nil { - log.Error("Verification stopped hook failed", - "verification_id", verification.Id, - "error", err) - // Don't fail the stop operation due to hook errors - } - - log.Info("Stopped verification for job", - "job_id", jobID, - "verification_id", verification.Id) - } - - span.SetStatus(codes.Ok, "verifications stopped") -} diff --git a/apps/workspace-engine/pkg/workspace/releasemanager/verification/manager_test.go b/apps/workspace-engine/pkg/workspace/releasemanager/verification/manager_test.go deleted file mode 100644 index b29811b90..000000000 --- a/apps/workspace-engine/pkg/workspace/releasemanager/verification/manager_test.go +++ /dev/null @@ -1,1197 +0,0 @@ -package verification - -import ( - "context" - "sync" - "testing" - "time" - "workspace-engine/pkg/oapi" - "workspace-engine/pkg/workspace/store" - - "github.com/google/uuid" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// Note: Test helpers (NewTestServer, createTestHTTPProvider, etc.) are defined in scheduler_test.go - -// Tests for Manager - -func TestNewManager(t *testing.T) { - s := newTestStore() - manager := NewManager(s) - - assert.NotNil(t, manager) - assert.NotNil(t, manager.store) - assert.NotNil(t, manager.scheduler) - assert.Equal(t, s, manager.store) -} - -func TestManager_StartVerification_Success(t *testing.T) { - ctx := context.Background() - s := newTestStore() - manager := NewManager(s) - - // Create test server - ts := NewTestServer() - defer ts.Close() - - release := createTestRelease(s, ctx) - job := createTestJob(s, ctx, release.ID()) - - // Create metric specs using test server - metrics := []oapi.VerificationMetricSpec{ - { - Name: "health-check", - IntervalSeconds: 30, - Count: 5, - SuccessCondition: "result.statusCode == 200", - FailureThreshold: ptr(2), - Provider: createTestHTTPProvider(ts.URL, oapi.GET), - }, - } - - err := manager.StartVerification(ctx, job, metrics) - - require.NoError(t, err) - - // Verify verification was created - verifications := s.JobVerifications.GetByJobId(job.Id) - require.Len(t, verifications, 1) - verification := verifications[0] - assert.Equal(t, job.Id, verification.JobId) - assert.Equal(t, 1, len(verification.Metrics)) - assert.Equal(t, "health-check", verification.Metrics[0].Name) - assert.EqualValues(t, 30, verification.Metrics[0].IntervalSeconds) - assert.EqualValues(t, 5, verification.Metrics[0].Count) - // Note: scheduler runs first measurement immediately, so we should have at least one - assert.LessOrEqual(t, len(verification.Metrics[0].Measurements), verification.Metrics[0].Count) - - // Verify scheduler started the verification - manager.scheduler.mu.Lock() - _, schedulerRunning := manager.scheduler.cancelFuncs[verification.Id] - manager.scheduler.mu.Unlock() - assert.True(t, schedulerRunning, "scheduler should have started the verification") - - // Clean up - manager.scheduler.StopVerification(verification.Id) -} - -func TestManager_StartVerification_MultipleMetrics(t *testing.T) { - ctx := context.Background() - s := newTestStore() - manager := NewManager(s) - - // Create test server - ts := NewTestServer() - defer ts.Close() - - release := createTestRelease(s, ctx) - job := createTestJob(s, ctx, release.ID()) - - metrics := []oapi.VerificationMetricSpec{ - { - Name: "health-check", - IntervalSeconds: 30, - Count: 5, - SuccessCondition: "result.statusCode == 200", - Provider: createTestHTTPProvider(ts.URL+"/health", oapi.GET), - }, - { - Name: "availability-check", - IntervalSeconds: 60, - Count: 3, - SuccessCondition: "result.statusCode == 200", - Provider: createTestHTTPProvider(ts.URL+"/status", oapi.GET), - }, - } - - err := manager.StartVerification(ctx, job, metrics) - - require.NoError(t, err) - - verifications := s.JobVerifications.GetByJobId(job.Id) - require.Len(t, verifications, 1) - verification := verifications[0] - assert.Equal(t, 2, len(verification.Metrics)) - assert.Equal(t, "health-check", verification.Metrics[0].Name) - assert.Equal(t, "availability-check", verification.Metrics[1].Name) - - // Clean up - manager.scheduler.StopVerification(verification.Id) -} - -func TestManager_StartVerification_AlreadyExists(t *testing.T) { - ctx := context.Background() - s := newTestStore() - manager := NewManager(s) - - // Create test server - ts := NewTestServer() - defer ts.Close() - - release := createTestRelease(s, ctx) - job := createTestJob(s, ctx, release.ID()) - - metrics := []oapi.VerificationMetricSpec{ - { - Name: "health-check", - IntervalSeconds: 30, - Count: 5, - SuccessCondition: "result.statusCode == 200", - Provider: createTestHTTPProvider(ts.URL, oapi.GET), - }, - } - - // Start verification first time - err := manager.StartVerification(ctx, job, metrics) - require.NoError(t, err) - - verifications := s.JobVerifications.GetByJobId(job.Id) - require.Len(t, verifications, 1) - firstVerificationID := verifications[0].Id - - // Try to start again - should create a new verification - err = manager.StartVerification(ctx, job, metrics) - require.NoError(t, err) - - // Verify new verification was created - verifications = s.JobVerifications.GetByJobId(job.Id) - require.Len(t, verifications, 2) - // Most recent first - assert.NotEqual(t, firstVerificationID, verifications[0].Id, "should be a new verification") - - // Clean up - for _, v := range verifications { - manager.scheduler.StopVerification(v.Id) - } -} - -func TestManager_StartVerification_NoMetrics(t *testing.T) { - ctx := context.Background() - s := newTestStore() - manager := NewManager(s) - - release := createTestRelease(s, ctx) - job := createTestJob(s, ctx, release.ID()) - - // Try to start with empty metrics - err := manager.StartVerification(ctx, job, []oapi.VerificationMetricSpec{}) - - assert.Error(t, err) - assert.Contains(t, err.Error(), "at least one metric configuration is required") - - // Verify no verification was created - verifications := s.JobVerifications.GetByJobId(job.Id) - assert.Empty(t, verifications) -} - -func TestManager_StartVerification_WithFailureLimit(t *testing.T) { - ctx := context.Background() - s := newTestStore() - manager := NewManager(s) - - // Create test server - ts := NewTestServer() - defer ts.Close() - - release := createTestRelease(s, ctx) - job := createTestJob(s, ctx, release.ID()) - - failureLimit := 3 - metrics := []oapi.VerificationMetricSpec{ - { - Name: "health-check", - IntervalSeconds: 30, - Count: 10, - SuccessCondition: "result.statusCode == 200", - FailureThreshold: &failureLimit, - Provider: createTestHTTPProvider(ts.URL, oapi.GET), - }, - } - - err := manager.StartVerification(ctx, job, metrics) - - require.NoError(t, err) - - verifications := s.JobVerifications.GetByJobId(job.Id) - require.Len(t, verifications, 1) - verification := verifications[0] - require.NotNil(t, verification.Metrics[0].FailureThreshold) - assert.Equal(t, 3, *verification.Metrics[0].FailureThreshold) - - // Clean up - manager.scheduler.StopVerification(verification.Id) -} - -func TestManager_StopVerification_Success(t *testing.T) { - ctx := context.Background() - s := newTestStore() - manager := NewManager(s) - - // Create test server - ts := NewTestServer() - defer ts.Close() - - release := createTestRelease(s, ctx) - job := createTestJob(s, ctx, release.ID()) - - metrics := []oapi.VerificationMetricSpec{ - { - Name: "health-check", - IntervalSeconds: 30, - Count: 5, - SuccessCondition: "result.statusCode == 200", - Provider: createTestHTTPProvider(ts.URL, oapi.GET), - }, - } - - err := manager.StartVerification(ctx, job, metrics) - require.NoError(t, err) - - verifications := s.JobVerifications.GetByJobId(job.Id) - require.Len(t, verifications, 1) - verification := verifications[0] - - // Verify it's running - manager.scheduler.mu.Lock() - _, running := manager.scheduler.cancelFuncs[verification.Id] - manager.scheduler.mu.Unlock() - assert.True(t, running) - - // Stop the verification - manager.StopVerificationsForJob(ctx, job.Id) - - // Verify it's stopped - manager.scheduler.mu.Lock() - _, stillRunning := manager.scheduler.cancelFuncs[verification.Id] - manager.scheduler.mu.Unlock() - assert.False(t, stillRunning) -} - -func TestManager_StopVerification_NotFound(t *testing.T) { - ctx := context.Background() - s := newTestStore() - manager := NewManager(s) - - nonExistentJobID := uuid.New().String() - - // Should not panic - assert.NotPanics(t, func() { - manager.StopVerificationsForJob(ctx, nonExistentJobID) - }) -} - -func TestManager_Restore_NoVerifications(t *testing.T) { - ctx := context.Background() - s := newTestStore() - manager := NewManager(s) - - err := manager.Restore(ctx) - - assert.NoError(t, err) - - // No verifications should be running - manager.scheduler.mu.Lock() - count := len(manager.scheduler.cancelFuncs) - manager.scheduler.mu.Unlock() - assert.Equal(t, 0, count) -} - -func TestManager_Restore_RunningVerifications(t *testing.T) { - ctx := context.Background() - s := newTestStore() - manager := NewManager(s) - - // Create some releases, jobs, and verifications in running state - release1 := createTestRelease(s, ctx) - job1 := createTestJob(s, ctx, release1.ID()) - release2 := createTestRelease(s, ctx) - job2 := createTestJob(s, ctx, release2.ID()) - release3 := createTestRelease(s, ctx) - job3 := createTestJob(s, ctx, release3.ID()) - - verification1 := createTestVerification(s, ctx, job1.Id, 2, 3600) - verification2 := createTestVerification(s, ctx, job2.Id, 1, 300) - verification3 := createTestVerification(s, ctx, job3.Id, 3, 2700) - - // verification1 is running (no measurements) - // verification2 is completed (all measurements passed) - for i := 0; i < verification2.Metrics[0].Count; i++ { - msg := "Success" - verification2.Metrics[0].Measurements = append(verification2.Metrics[0].Measurements, oapi.VerificationMeasurement{ - Message: &msg, - Status: oapi.Passed, - MeasuredAt: time.Now(), - Data: &map[string]any{"statusCode": 200}, - }) - } - s.JobVerifications.Upsert(ctx, verification2) - - // verification3 has some measurements but not complete - msg := "Success" - verification3.Metrics[0].Measurements = append(verification3.Metrics[0].Measurements, oapi.VerificationMeasurement{ - Message: &msg, - Status: oapi.Passed, - MeasuredAt: time.Now(), - Data: &map[string]any{"statusCode": 200}, - }) - s.JobVerifications.Upsert(ctx, verification3) - - // Restore should restart verification1 and verification3, but not verification2 - err := manager.Restore(ctx) - - require.NoError(t, err) - - // Check which verifications are running - manager.scheduler.mu.Lock() - _, v1Running := manager.scheduler.cancelFuncs[verification1.Id] - _, v2Running := manager.scheduler.cancelFuncs[verification2.Id] - _, v3Running := manager.scheduler.cancelFuncs[verification3.Id] - totalRunning := len(manager.scheduler.cancelFuncs) - manager.scheduler.mu.Unlock() - - assert.True(t, v1Running, "verification1 should be restarted") - assert.False(t, v2Running, "verification2 should not be restarted (completed)") - assert.True(t, v3Running, "verification3 should be restarted") - assert.Equal(t, 2, totalRunning, "should have 2 verifications running") - - // Clean up - manager.scheduler.StopVerification(verification1.Id) - manager.scheduler.StopVerification(verification3.Id) -} - -func TestManager_Restore_FailedVerifications(t *testing.T) { - ctx := context.Background() - s := newTestStore() - manager := NewManager(s) - - release := createTestRelease(s, ctx) - job := createTestJob(s, ctx, release.ID()) - verification := createTestVerification(s, ctx, job.Id, 1, 30) - - // Make verification failed by exceeding failure limit - for i := 0; i <= *verification.Metrics[0].FailureThreshold; i++ { - msg := "Failed" - verification.Metrics[0].Measurements = append(verification.Metrics[0].Measurements, oapi.VerificationMeasurement{ - Message: &msg, - Status: oapi.Failed, - MeasuredAt: time.Now(), - Data: &map[string]any{"statusCode": 500}, - }) - } - s.JobVerifications.Upsert(ctx, verification) - - // Verify it's in failed state - assert.Equal(t, oapi.JobVerificationStatusFailed, verification.Status()) - - // Restore should not restart failed verifications - err := manager.Restore(ctx) - - require.NoError(t, err) - - manager.scheduler.mu.Lock() - _, running := manager.scheduler.cancelFuncs[verification.Id] - manager.scheduler.mu.Unlock() - - assert.False(t, running, "failed verification should not be restarted") -} - -func TestManager_Restore_MixedStates(t *testing.T) { - ctx := context.Background() - s := newTestStore() - manager := NewManager(s) - - // Create verifications in different states - runningRelease := createTestRelease(s, ctx) - runningJob := createTestJob(s, ctx, runningRelease.ID()) - passedRelease := createTestRelease(s, ctx) - passedJob := createTestJob(s, ctx, passedRelease.ID()) - failedRelease := createTestRelease(s, ctx) - failedJob := createTestJob(s, ctx, failedRelease.ID()) - - runningVerification := createTestVerification(s, ctx, runningJob.Id, 1, 3600) - - passedVerification := createTestVerification(s, ctx, passedJob.Id, 1, 3600) - for i := 0; i < passedVerification.Metrics[0].Count; i++ { - msg := "Success" - passedVerification.Metrics[0].Measurements = append(passedVerification.Metrics[0].Measurements, oapi.VerificationMeasurement{ - Message: &msg, - Status: oapi.Passed, - MeasuredAt: time.Now(), - Data: &map[string]any{"statusCode": 200}, - }) - } - s.JobVerifications.Upsert(ctx, passedVerification) - - failedVerification := createTestVerification(s, ctx, failedJob.Id, 1, 3600) - for i := 0; i <= *failedVerification.Metrics[0].FailureThreshold; i++ { - msg := "Failed" - failedVerification.Metrics[0].Measurements = append(failedVerification.Metrics[0].Measurements, oapi.VerificationMeasurement{ - Message: &msg, - Status: oapi.Failed, - MeasuredAt: time.Now(), - Data: &map[string]any{"statusCode": 500}, - }) - } - s.JobVerifications.Upsert(ctx, failedVerification) - - // Restore - err := manager.Restore(ctx) - require.NoError(t, err) - - // Only running verification should be restarted - manager.scheduler.mu.Lock() - _, runningActive := manager.scheduler.cancelFuncs[runningVerification.Id] - _, passedActive := manager.scheduler.cancelFuncs[passedVerification.Id] - _, failedActive := manager.scheduler.cancelFuncs[failedVerification.Id] - totalActive := len(manager.scheduler.cancelFuncs) - manager.scheduler.mu.Unlock() - - assert.True(t, runningActive, "running verification should be active") - assert.False(t, passedActive, "passed verification should not be active") - assert.False(t, failedActive, "failed verification should not be active") - assert.Equal(t, 1, totalActive) - - // Clean up - manager.scheduler.StopVerification(runningVerification.Id) -} - -func TestManager_StartAndStopMultiple(t *testing.T) { - ctx := context.Background() - s := newTestStore() - manager := NewManager(s) - - // Create test server - ts := NewTestServer() - defer ts.Close() - - metrics := []oapi.VerificationMetricSpec{ - { - Name: "health-check", - IntervalSeconds: 30, - Count: 5, - SuccessCondition: "result.statusCode == 200", - Provider: createTestHTTPProvider(ts.URL, oapi.GET), - }, - } - - // Start multiple verifications - jobs := make([]*oapi.Job, 5) - for i := 0; i < 5; i++ { - release := createTestRelease(s, ctx) - jobs[i] = createTestJob(s, ctx, release.ID()) - err := manager.StartVerification(ctx, jobs[i], metrics) - require.NoError(t, err) - } - - // Verify all are running - manager.scheduler.mu.Lock() - runningCount := len(manager.scheduler.cancelFuncs) - manager.scheduler.mu.Unlock() - assert.Equal(t, 5, runningCount) - - // Stop all - for _, job := range jobs { - manager.StopVerificationsForJob(ctx, job.Id) - } - - // Verify all are stopped - manager.scheduler.mu.Lock() - finalCount := len(manager.scheduler.cancelFuncs) - manager.scheduler.mu.Unlock() - assert.Equal(t, 0, finalCount) -} - -func TestManager_StartVerification_PreservesAllMetricFields(t *testing.T) { - ctx := context.Background() - s := newTestStore() - manager := NewManager(s) - - // Create test server - ts := NewTestServer() - defer ts.Close() - - release := createTestRelease(s, ctx) - job := createTestJob(s, ctx, release.ID()) - - method := oapi.POST - timeout := "10s" - body := `{"key": "value"}` - headers := map[string]string{"Authorization": "Bearer token"} - failureLimit := 5 - failureCondition := "result.statusCode == 500 || result.body.status == 'error'" - successThreshold := 3 - - provider := oapi.MetricProvider{} - _ = provider.FromHTTPMetricProvider(oapi.HTTPMetricProvider{ - Url: ts.URL + "/verify", - Method: &method, - Type: oapi.Http, - Timeout: &timeout, - Body: &body, - Headers: &headers, - }) - - metrics := []oapi.VerificationMetricSpec{ - { - Name: "complex-check", - IntervalSeconds: 120, - Count: 20, - SuccessCondition: "result.body.status == 'ok'", - FailureCondition: &failureCondition, - FailureThreshold: &failureLimit, - SuccessThreshold: &successThreshold, - Provider: provider, - }, - } - - err := manager.StartVerification(ctx, job, metrics) - require.NoError(t, err) - - verifications := s.JobVerifications.GetByJobId(job.Id) - require.Len(t, verifications, 1) - verification := verifications[0] - - // Verify all fields are preserved - metric := verification.Metrics[0] - assert.Equal(t, "complex-check", metric.Name) - assert.EqualValues(t, 120, metric.IntervalSeconds) - assert.EqualValues(t, 20, metric.Count) - assert.Equal(t, "result.body.status == 'ok'", metric.SuccessCondition) - require.NotNil(t, metric.FailureCondition, "FailureCondition should be preserved") - assert.Equal(t, failureCondition, *metric.FailureCondition) - assert.EqualValues(t, 5, *metric.FailureThreshold) - require.NotNil(t, metric.SuccessThreshold, "SuccessThreshold should be preserved") - assert.EqualValues(t, 3, *metric.SuccessThreshold) - - // Clean up - manager.scheduler.StopVerification(verification.Id) -} - -func TestManager_Integration_FullLifecycle(t *testing.T) { - if testing.Short() { - t.Skip("Skipping integration test in short mode") - } - - ctx := context.Background() - s := newTestStore() - manager := NewManager(s) - - // Create test server - ts := NewTestServer() - defer ts.Close() - - release := createTestRelease(s, ctx) - job := createTestJob(s, ctx, release.ID()) - - metrics := []oapi.VerificationMetricSpec{ - { - Name: "health-check", - IntervalSeconds: 1, - Count: 3, - SuccessCondition: "result.statusCode == 200", - FailureThreshold: ptr(2), - Provider: createTestHTTPProvider(ts.URL, oapi.GET), - }, - } - - // Start verification - err := manager.StartVerification(ctx, job, metrics) - require.NoError(t, err) - - // Wait for some measurements - time.Sleep(2 * time.Second) - - // Check that measurements were taken - verifications := s.JobVerifications.GetByJobId(job.Id) - require.Len(t, verifications, 1) - verification := verifications[0] - assert.GreaterOrEqual(t, len(verification.Metrics[0].Measurements), 1) - - // Verify test server received requests - assert.GreaterOrEqual(t, ts.RequestCount(), 1) - - // Stop verification - manager.StopVerificationsForJob(ctx, job.Id) - - // Verify it's stopped - manager.scheduler.mu.Lock() - _, running := manager.scheduler.cancelFuncs[verification.Id] - manager.scheduler.mu.Unlock() - assert.False(t, running) -} - -// Benchmark tests -func BenchmarkManager_StartVerification(b *testing.B) { - ctx := context.Background() - s := newTestStore() - manager := NewManager(s) - - // Create test server - ts := NewTestServer() - defer ts.Close() - - metrics := []oapi.VerificationMetricSpec{ - { - Name: "health-check", - IntervalSeconds: 30, - Count: 5, - SuccessCondition: "result.statusCode == 200", - Provider: createTestHTTPProvider(ts.URL, oapi.GET), - }, - } - - // Pre-create jobs - jobs := make([]*oapi.Job, b.N) - for i := 0; i < b.N; i++ { - release := createTestRelease(s, ctx) - jobs[i] = createTestJob(s, ctx, release.ID()) - } - - b.ResetTimer() - for i := 0; i < b.N; i++ { - _ = manager.StartVerification(ctx, jobs[i], metrics) - } - - // Clean up - for _, job := range jobs { - verifications := s.JobVerifications.GetByJobId(job.Id) - for _, verification := range verifications { - manager.scheduler.StopVerification(verification.Id) - } - } -} - -func BenchmarkManager_StopVerification(b *testing.B) { - ctx := context.Background() - s := newTestStore() - manager := NewManager(s) - - // Create test server - ts := NewTestServer() - defer ts.Close() - - metrics := []oapi.VerificationMetricSpec{ - { - Name: "health-check", - IntervalSeconds: 30, - Count: 5, - SuccessCondition: "result.statusCode == 200", - Provider: createTestHTTPProvider(ts.URL, oapi.GET), - }, - } - - // Pre-create and start verifications - jobs := make([]*oapi.Job, b.N) - for i := 0; i < b.N; i++ { - release := createTestRelease(s, ctx) - jobs[i] = createTestJob(s, ctx, release.ID()) - _ = manager.StartVerification(ctx, jobs[i], metrics) - } - - b.ResetTimer() - for i := 0; i < b.N; i++ { - manager.StopVerificationsForJob(ctx, jobs[i].Id) - } -} - -func BenchmarkManager_Restore(b *testing.B) { - ctx := context.Background() - - // Pre-create verifications - stores := make([]*store.Store, b.N) - for i := 0; i < b.N; i++ { - s := newTestStore() - // Create 10 running verifications per store - for j := 0; j < 10; j++ { - release := createTestRelease(s, ctx) - job := createTestJob(s, ctx, release.ID()) - createTestVerification(s, ctx, job.Id, 2, 3600) - } - stores[i] = s - } - - b.ResetTimer() - for i := 0; i < b.N; i++ { - manager := NewManager(stores[i]) - _ = manager.Restore(ctx) - - // Clean up - for _, verification := range stores[i].JobVerifications.Items() { - manager.scheduler.StopVerification(verification.Id) - } - } -} - -// Mock hooks for testing -type mockHooks struct { - mu sync.Mutex - - verificationStartedCalls []string - measurementTakenCalls []measurementCall - metricCompleteCalls []metricCall - verificationCompleteCalls []string - verificationStoppedCalls []string - - // Allow injecting errors for testing error handling - errorOnVerificationStarted error - errorOnMeasurementTaken error - errorOnMetricComplete error - errorOnVerificationComplete error - errorOnVerificationStopped error -} - -type measurementCall struct { - verificationID string - metricIndex int - measurement *oapi.VerificationMeasurement -} - -type metricCall struct { - verificationID string - metricIndex int -} - -func newMockHooks() *mockHooks { - return &mockHooks{ - verificationStartedCalls: make([]string, 0), - measurementTakenCalls: make([]measurementCall, 0), - metricCompleteCalls: make([]metricCall, 0), - verificationCompleteCalls: make([]string, 0), - verificationStoppedCalls: make([]string, 0), - } -} - -func (m *mockHooks) OnVerificationStarted(ctx context.Context, verification *oapi.JobVerification) error { - m.mu.Lock() - defer m.mu.Unlock() - m.verificationStartedCalls = append(m.verificationStartedCalls, verification.Id) - return m.errorOnVerificationStarted -} - -func (m *mockHooks) OnMeasurementTaken(ctx context.Context, verification *oapi.JobVerification, metricIndex int, measurement *oapi.VerificationMeasurement) error { - m.mu.Lock() - defer m.mu.Unlock() - m.measurementTakenCalls = append(m.measurementTakenCalls, measurementCall{ - verificationID: verification.Id, - metricIndex: metricIndex, - measurement: measurement, - }) - return m.errorOnMeasurementTaken -} - -func (m *mockHooks) OnMetricComplete(ctx context.Context, verification *oapi.JobVerification, metricIndex int) error { - m.mu.Lock() - defer m.mu.Unlock() - m.metricCompleteCalls = append(m.metricCompleteCalls, metricCall{ - verificationID: verification.Id, - metricIndex: metricIndex, - }) - return m.errorOnMetricComplete -} - -func (m *mockHooks) OnVerificationComplete(ctx context.Context, verification *oapi.JobVerification) error { - m.mu.Lock() - defer m.mu.Unlock() - m.verificationCompleteCalls = append(m.verificationCompleteCalls, verification.Id) - return m.errorOnVerificationComplete -} - -func (m *mockHooks) OnVerificationStopped(ctx context.Context, verification *oapi.JobVerification) error { - m.mu.Lock() - defer m.mu.Unlock() - m.verificationStoppedCalls = append(m.verificationStoppedCalls, verification.Id) - return m.errorOnVerificationStopped -} - -func (m *mockHooks) getVerificationStartedCount() int { - m.mu.Lock() - defer m.mu.Unlock() - return len(m.verificationStartedCalls) -} - -func (m *mockHooks) getMeasurementTakenCount() int { - m.mu.Lock() - defer m.mu.Unlock() - return len(m.measurementTakenCalls) -} - -func (m *mockHooks) getMetricCompleteCount() int { - m.mu.Lock() - defer m.mu.Unlock() - return len(m.metricCompleteCalls) -} - -func (m *mockHooks) getVerificationCompleteCount() int { - m.mu.Lock() - defer m.mu.Unlock() - return len(m.verificationCompleteCalls) -} - -func (m *mockHooks) getVerificationStoppedCount() int { - m.mu.Lock() - defer m.mu.Unlock() - return len(m.verificationStoppedCalls) -} - -// Hook tests - -func TestManager_HooksOnVerificationStarted(t *testing.T) { - ctx := context.Background() - s := newTestStore() - hooks := newMockHooks() - manager := NewManager(s, WithHooks(hooks)) - - // Create test server - ts := NewTestServer() - defer ts.Close() - - release := createTestRelease(s, ctx) - job := createTestJob(s, ctx, release.ID()) - - metrics := []oapi.VerificationMetricSpec{ - { - Name: "health-check", - IntervalSeconds: 30, - Count: 5, - SuccessCondition: "result.statusCode == 200", - Provider: createTestHTTPProvider(ts.URL, oapi.GET), - }, - } - - err := manager.StartVerification(ctx, job, metrics) - require.NoError(t, err) - - // Verify hook was called - assert.Equal(t, 1, hooks.getVerificationStartedCount()) - - verifications := s.JobVerifications.GetByJobId(job.Id) - require.Len(t, verifications, 1) - verification := verifications[0] - - hooks.mu.Lock() - assert.Equal(t, verification.Id, hooks.verificationStartedCalls[0]) - hooks.mu.Unlock() - - // Clean up - manager.scheduler.StopVerification(verification.Id) -} - -func TestManager_HooksOnVerificationStopped(t *testing.T) { - ctx := context.Background() - s := newTestStore() - hooks := newMockHooks() - manager := NewManager(s, WithHooks(hooks)) - - // Create test server - ts := NewTestServer() - defer ts.Close() - - release := createTestRelease(s, ctx) - job := createTestJob(s, ctx, release.ID()) - - metrics := []oapi.VerificationMetricSpec{ - { - Name: "health-check", - IntervalSeconds: 30, - Count: 5, - SuccessCondition: "result.statusCode == 200", - Provider: createTestHTTPProvider(ts.URL, oapi.GET), - }, - } - - err := manager.StartVerification(ctx, job, metrics) - require.NoError(t, err) - - verifications := s.JobVerifications.GetByJobId(job.Id) - require.Len(t, verifications, 1) - verification := verifications[0] - - // Stop the verification - manager.StopVerificationsForJob(ctx, job.Id) - - // Verify hook was called - assert.Equal(t, 1, hooks.getVerificationStoppedCount()) - - hooks.mu.Lock() - assert.Equal(t, verification.Id, hooks.verificationStoppedCalls[0]) - hooks.mu.Unlock() -} - -func TestManager_HooksOnMeasurementTaken(t *testing.T) { - ctx := context.Background() - s := newTestStore() - hooks := newMockHooks() - manager := NewManager(s, WithHooks(hooks)) - - // Create test server - ts := NewTestServer() - defer ts.Close() - - release := createTestRelease(s, ctx) - job := createTestJob(s, ctx, release.ID()) - - metrics := []oapi.VerificationMetricSpec{ - { - Name: "health-check", - IntervalSeconds: 100, - Count: 3, - SuccessCondition: "result.statusCode == 200", - Provider: createTestHTTPProvider(ts.URL, oapi.GET), - }, - } - - err := manager.StartVerification(ctx, job, metrics) - require.NoError(t, err) - - verifications := s.JobVerifications.GetByJobId(job.Id) - require.Len(t, verifications, 1) - verification := verifications[0] - - // Wait for at least one measurement to be taken - time.Sleep(200 * time.Millisecond) - - // Verify hook was called at least once - assert.GreaterOrEqual(t, hooks.getMeasurementTakenCount(), 1) - - hooks.mu.Lock() - if len(hooks.measurementTakenCalls) > 0 { - assert.Equal(t, verification.Id, hooks.measurementTakenCalls[0].verificationID) - assert.Equal(t, 0, hooks.measurementTakenCalls[0].metricIndex) - assert.NotNil(t, hooks.measurementTakenCalls[0].measurement) - } - hooks.mu.Unlock() - - // Clean up - manager.scheduler.StopVerification(verification.Id) -} - -func TestManager_HooksOnMetricComplete(t *testing.T) { - ctx := context.Background() - s := newTestStore() - hooks := newMockHooks() - manager := NewManager(s, WithHooks(hooks)) - - // Create test server - ts := NewTestServer() - defer ts.Close() - - release := createTestRelease(s, ctx) - job := createTestJob(s, ctx, release.ID()) - - // Use a very short interval, low count, and short timeout to complete quickly - metrics := []oapi.VerificationMetricSpec{ - { - Name: "health-check", - IntervalSeconds: 1, // 1 second interval for quick test - Count: 2, // Only 2 measurements to complete quickly - SuccessCondition: "result.statusCode == 200", - Provider: createTestHTTPProviderWithTimeout(ts.URL, oapi.GET, "100ms"), - }, - } - - err := manager.StartVerification(ctx, job, metrics) - require.NoError(t, err) - - verifications := s.JobVerifications.GetByJobId(job.Id) - require.Len(t, verifications, 1) - verification := verifications[0] - - // Wait for metric to complete using Eventually to poll for completion - // Need at least 1 second for the second measurement (IntervalSeconds: 1) - assert.Eventually(t, func() bool { - return hooks.getMetricCompleteCount() >= 1 - }, 3*time.Second, 50*time.Millisecond, "OnMetricComplete hook should be called") - - // Verify hook was called with correct parameters - hooks.mu.Lock() - if len(hooks.metricCompleteCalls) > 0 { - assert.Equal(t, verification.Id, hooks.metricCompleteCalls[0].verificationID) - assert.Equal(t, 0, hooks.metricCompleteCalls[0].metricIndex) - } - hooks.mu.Unlock() - - // Clean up - manager.scheduler.StopVerification(verification.Id) -} - -func TestManager_HooksOnVerificationComplete(t *testing.T) { - ctx := context.Background() - s := newTestStore() - hooks := newMockHooks() - manager := NewManager(s, WithHooks(hooks)) - - // Create test server - ts := NewTestServer() - defer ts.Close() - - release := createTestRelease(s, ctx) - job := createTestJob(s, ctx, release.ID()) - - // Use a very short interval, low count, and short timeout to complete quickly - metrics := []oapi.VerificationMetricSpec{ - { - Name: "health-check", - IntervalSeconds: 1, // 1 second interval for quick test - Count: 2, // Only 2 measurements to complete quickly - SuccessCondition: "result.statusCode == 200", - Provider: createTestHTTPProviderWithTimeout(ts.URL, oapi.GET, "100ms"), - }, - } - - err := manager.StartVerification(ctx, job, metrics) - require.NoError(t, err) - - verifications := s.JobVerifications.GetByJobId(job.Id) - require.Len(t, verifications, 1) - verification := verifications[0] - - // Wait for verification to complete using Eventually to poll for completion - // Need at least 1 second for the second measurement (IntervalSeconds: 1) - assert.Eventually(t, func() bool { - return hooks.getVerificationCompleteCount() >= 1 - }, 3*time.Second, 50*time.Millisecond, "OnVerificationComplete hook should be called") - - // Verify hook was called with correct parameters - hooks.mu.Lock() - if len(hooks.verificationCompleteCalls) > 0 { - assert.Equal(t, verification.Id, hooks.verificationCompleteCalls[0]) - } - hooks.mu.Unlock() - - // Clean up - manager.scheduler.StopVerification(verification.Id) -} - -func TestManager_HooksErrorsDontFailVerification(t *testing.T) { - ctx := context.Background() - s := newTestStore() - hooks := newMockHooks() - - // Create test server - ts := NewTestServer() - defer ts.Close() - - // Inject errors in all hooks - hooks.errorOnVerificationStarted = assert.AnError - hooks.errorOnMeasurementTaken = assert.AnError - hooks.errorOnMetricComplete = assert.AnError - hooks.errorOnVerificationComplete = assert.AnError - hooks.errorOnVerificationStopped = assert.AnError - - manager := NewManager(s, WithHooks(hooks)) - - release := createTestRelease(s, ctx) - job := createTestJob(s, ctx, release.ID()) - - metrics := []oapi.VerificationMetricSpec{ - { - Name: "health-check", - IntervalSeconds: 50, - Count: 2, - SuccessCondition: "result.statusCode == 200", - Provider: createTestHTTPProvider(ts.URL, oapi.GET), - }, - } - - // StartVerification should succeed despite hook error - err := manager.StartVerification(ctx, job, metrics) - require.NoError(t, err) - - verifications := s.JobVerifications.GetByJobId(job.Id) - require.Len(t, verifications, 1) - - // Wait for some measurements - time.Sleep(200 * time.Millisecond) - - // StopVerification should succeed despite hook error - manager.StopVerificationsForJob(ctx, job.Id) - - // Verify hooks were still called despite errors - assert.Equal(t, 1, hooks.getVerificationStartedCount()) - assert.GreaterOrEqual(t, hooks.getMeasurementTakenCount(), 1) - assert.Equal(t, 1, hooks.getVerificationStoppedCount()) -} - -func TestManager_HooksWithMultipleMetrics(t *testing.T) { - ctx := context.Background() - s := newTestStore() - hooks := newMockHooks() - manager := NewManager(s, WithHooks(hooks)) - - // Create test server - ts := NewTestServer() - defer ts.Close() - - release := createTestRelease(s, ctx) - job := createTestJob(s, ctx, release.ID()) - - // Create multiple metrics - metrics := []oapi.VerificationMetricSpec{ - { - Name: "health-check-1", - IntervalSeconds: 1, - Count: 2, - SuccessCondition: "result.statusCode == 200", - Provider: createTestHTTPProvider(ts.URL+"/health", oapi.GET), - }, - { - Name: "health-check-2", - IntervalSeconds: 1, - Count: 2, - SuccessCondition: "result.statusCode == 200", - Provider: createTestHTTPProvider(ts.URL+"/metrics", oapi.GET), - }, - } - - err := manager.StartVerification(ctx, job, metrics) - require.NoError(t, err) - - verifications := s.JobVerifications.GetByJobId(job.Id) - require.Len(t, verifications, 1) - verification := verifications[0] - - // Wait for both metrics to complete - // Poll until both metrics have completed their measurements - // IntervalSeconds: 1, Count: 2 means ~1 second for second measurement per metric - require.Eventually(t, func() bool { - verifications = s.JobVerifications.GetByJobId(job.Id) - if len(verifications) == 0 { - return false - } - verification = verifications[0] - completedCount := 0 - for _, metric := range verification.Metrics { - if len(metric.Measurements) >= metric.Count { - completedCount++ - } - } - return completedCount >= 2 - }, 4*time.Second, 100*time.Millisecond, "Both metrics should complete") - - // Wait for hooks to be called for both metrics - require.Eventually(t, func() bool { - return hooks.getMetricCompleteCount() >= 2 - }, 4*time.Second, 100*time.Millisecond, "Both metric complete hooks should be called") - - // Verify hooks were called for both metrics - assert.Equal(t, 1, hooks.getVerificationStartedCount()) - assert.GreaterOrEqual(t, hooks.getMeasurementTakenCount(), 2) // At least one per metric - assert.GreaterOrEqual(t, hooks.getMetricCompleteCount(), 2) // Both metrics should complete - assert.Equal(t, 1, hooks.getVerificationCompleteCount()) // Only one verification complete - - // Verify different metric indices were recorded - hooks.mu.Lock() - metricIndices := make(map[int]bool) - for _, call := range hooks.measurementTakenCalls { - metricIndices[call.metricIndex] = true - } - hooks.mu.Unlock() - - // Should have measurements from both metrics (index 0 and 1) - assert.GreaterOrEqual(t, len(metricIndices), 1) - - // Clean up - manager.scheduler.StopVerification(verification.Id) -} diff --git a/apps/workspace-engine/pkg/workspace/releasemanager/verification/recorder.go b/apps/workspace-engine/pkg/workspace/releasemanager/verification/recorder.go deleted file mode 100644 index 430ab73a0..000000000 --- a/apps/workspace-engine/pkg/workspace/releasemanager/verification/recorder.go +++ /dev/null @@ -1,120 +0,0 @@ -package verification - -import ( - "context" - "fmt" - "sync" - "time" - "workspace-engine/pkg/oapi" - "workspace-engine/pkg/workspace/store" -) - -// MeasurementRecorder handles storing measurement results in the verification store. -// It provides thread-safe updates to verification records. -type MeasurementRecorder struct { - store *store.Store - mu sync.Mutex -} - -// NewMeasurementRecorder creates a new measurement recorder -func NewMeasurementRecorder(store *store.Store) *MeasurementRecorder { - return &MeasurementRecorder{store: store} -} - -// RecordMeasurement safely adds a measurement to a verification's metric. -// Returns the updated verification record. -func (r *MeasurementRecorder) RecordMeasurement( - ctx context.Context, - verificationID string, - metricIndex int, - measurement oapi.VerificationMeasurement, -) (*oapi.JobVerification, error) { - r.mu.Lock() - defer r.mu.Unlock() - - // Verify the verification exists and metric index is valid - verification, ok := r.store.JobVerifications.Get(verificationID) - if !ok { - return nil, fmt.Errorf("verification not found: %s", verificationID) - } - - if metricIndex >= len(verification.Metrics) { - return nil, fmt.Errorf("metric index out of range: %d", metricIndex) - } - - // Update the verification with the new measurement - updated, _ := r.store.JobVerifications.Update( - ctx, verificationID, func(v *oapi.JobVerification) *oapi.JobVerification { - return r.appendMeasurement(v, metricIndex, measurement) - }, - ) - - return updated, nil -} - -// RecordError safely adds an error measurement to a verification's metric. -// Returns the updated verification record. -func (r *MeasurementRecorder) RecordError( - ctx context.Context, - verificationID string, - metricIndex int, - err error, -) (*oapi.JobVerification, error) { - errorMsg := fmt.Sprintf("Measurement error: %s", err.Error()) - errorData := map[string]any{"error": err.Error()} - - measurement := oapi.VerificationMeasurement{ - Message: &errorMsg, - Status: oapi.Failed, - MeasuredAt: time.Now(), - Data: &errorData, - } - - return r.RecordMeasurement(ctx, verificationID, metricIndex, measurement) -} - -// UpdateMessage updates the verification's summary message. -func (r *MeasurementRecorder) UpdateMessage( - ctx context.Context, - verificationID string, - message string, -) error { - r.mu.Lock() - defer r.mu.Unlock() - - _, err := r.store.JobVerifications.Update(ctx, verificationID, func(v *oapi.JobVerification) *oapi.JobVerification { - v.Message = &message - return v - }) - return err -} - -// appendMeasurement creates a deep copy of the verification and appends the measurement. -// This avoids race conditions when multiple goroutines update the same verification. -func (r *MeasurementRecorder) appendMeasurement( - v *oapi.JobVerification, - metricIndex int, - measurement oapi.VerificationMeasurement, -) *oapi.JobVerification { - // Make a deep copy to avoid race conditions - updated := *v - updated.Metrics = make([]oapi.VerificationMetricStatus, len(v.Metrics)) - - for i := range v.Metrics { - updated.Metrics[i] = v.Metrics[i] - // Copy measurements slice - updated.Metrics[i].Measurements = make( - []oapi.VerificationMeasurement, - len(v.Metrics[i].Measurements), - ) - copy(updated.Metrics[i].Measurements, v.Metrics[i].Measurements) - } - - // Append the new measurement - updated.Metrics[metricIndex].Measurements = append( - updated.Metrics[metricIndex].Measurements, - measurement, - ) - - return &updated -} diff --git a/apps/workspace-engine/pkg/workspace/releasemanager/verification/recorder_test.go b/apps/workspace-engine/pkg/workspace/releasemanager/verification/recorder_test.go deleted file mode 100644 index bd6e35cf8..000000000 --- a/apps/workspace-engine/pkg/workspace/releasemanager/verification/recorder_test.go +++ /dev/null @@ -1,324 +0,0 @@ -package verification - -import ( - "context" - "errors" - "sync" - "testing" - "time" - "workspace-engine/pkg/oapi" - - "github.com/google/uuid" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestNewMeasurementRecorder(t *testing.T) { - s := newTestStore() - recorder := NewMeasurementRecorder(s) - - assert.NotNil(t, recorder) - assert.NotNil(t, recorder.store) -} - -func TestRecorder_RecordMeasurement_Success(t *testing.T) { - ctx := context.Background() - s := newTestStore() - recorder := NewMeasurementRecorder(s) - - release := createTestRelease(s, ctx) - verification := createTestVerification(s, ctx, release.ID(), 1, 60) - - // Record a measurement - measurement := oapi.VerificationMeasurement{ - Message: ptr("Test measurement"), - Status: oapi.Passed, - MeasuredAt: time.Now(), - Data: &map[string]any{"status": "ok"}, - } - - updated, err := recorder.RecordMeasurement(ctx, verification.Id, 0, measurement) - - require.NoError(t, err) - require.NotNil(t, updated) - assert.Len(t, updated.Metrics[0].Measurements, 1) - assert.Equal(t, oapi.Passed, updated.Metrics[0].Measurements[0].Status) -} - -func TestRecorder_RecordMeasurement_VerificationNotFound(t *testing.T) { - ctx := context.Background() - s := newTestStore() - recorder := NewMeasurementRecorder(s) - - nonExistentID := uuid.New().String() - measurement := oapi.VerificationMeasurement{ - Status: oapi.Passed, - MeasuredAt: time.Now(), - } - - _, err := recorder.RecordMeasurement(ctx, nonExistentID, 0, measurement) - - assert.Error(t, err) - assert.Contains(t, err.Error(), "verification not found") -} - -func TestRecorder_RecordMeasurement_MetricIndexOutOfRange(t *testing.T) { - ctx := context.Background() - s := newTestStore() - recorder := NewMeasurementRecorder(s) - - release := createTestRelease(s, ctx) - verification := createTestVerification(s, ctx, release.ID(), 1, 60) - - measurement := oapi.VerificationMeasurement{ - Status: oapi.Passed, - MeasuredAt: time.Now(), - } - - _, err := recorder.RecordMeasurement(ctx, verification.Id, 10, measurement) - - assert.Error(t, err) - assert.Contains(t, err.Error(), "metric index out of range") -} - -func TestRecorder_RecordMeasurement_MultipleMeasurements(t *testing.T) { - ctx := context.Background() - s := newTestStore() - recorder := NewMeasurementRecorder(s) - - release := createTestRelease(s, ctx) - verification := createTestVerification(s, ctx, release.ID(), 1, 60) - - // Record multiple measurements - for i := 0; i < 5; i++ { - status := oapi.Passed - if i%2 != 0 { - status = oapi.Failed - } - measurement := oapi.VerificationMeasurement{ - Message: ptr("Measurement " + string(rune('1'+i))), - Status: status, // Alternate pass/fail - MeasuredAt: time.Now(), - } - _, err := recorder.RecordMeasurement(ctx, verification.Id, 0, measurement) - require.NoError(t, err) - } - - // Verify all measurements were recorded - updated, ok := s.JobVerifications.Get(verification.Id) - require.True(t, ok) - assert.Len(t, updated.Metrics[0].Measurements, 5) -} - -func TestRecorder_RecordMeasurement_MultipleMetrics(t *testing.T) { - ctx := context.Background() - s := newTestStore() - recorder := NewMeasurementRecorder(s) - - release := createTestRelease(s, ctx) - verification := createTestVerification(s, ctx, release.ID(), 3, 60) - - // Record measurement for each metric - for i := 0; i < 3; i++ { - measurement := oapi.VerificationMeasurement{ - Message: ptr("Measurement for metric " + string(rune('1'+i))), - Status: oapi.Passed, - MeasuredAt: time.Now(), - } - _, err := recorder.RecordMeasurement(ctx, verification.Id, i, measurement) - require.NoError(t, err) - } - - // Verify each metric has one measurement - updated, ok := s.JobVerifications.Get(verification.Id) - require.True(t, ok) - for i := 0; i < 3; i++ { - assert.Len(t, updated.Metrics[i].Measurements, 1) - } -} - -func TestRecorder_RecordError_Success(t *testing.T) { - ctx := context.Background() - s := newTestStore() - recorder := NewMeasurementRecorder(s) - - release := createTestRelease(s, ctx) - verification := createTestVerification(s, ctx, release.ID(), 1, 60) - - // Record an error - testError := errors.New("connection timeout") - updated, err := recorder.RecordError(ctx, verification.Id, 0, testError) - - require.NoError(t, err) - require.NotNil(t, updated) - assert.Len(t, updated.Metrics[0].Measurements, 1) - - errorMeasurement := updated.Metrics[0].Measurements[0] - assert.Equal(t, oapi.Failed, errorMeasurement.Status) - assert.Contains(t, *errorMeasurement.Message, "Measurement error") - assert.Contains(t, *errorMeasurement.Message, "connection timeout") - assert.NotNil(t, errorMeasurement.Data) -} - -func TestRecorder_RecordError_VerificationNotFound(t *testing.T) { - ctx := context.Background() - s := newTestStore() - recorder := NewMeasurementRecorder(s) - - nonExistentID := uuid.New().String() - testError := errors.New("some error") - - _, err := recorder.RecordError(ctx, nonExistentID, 0, testError) - - assert.Error(t, err) - assert.Contains(t, err.Error(), "verification not found") -} - -func TestRecorder_UpdateMessage_Success(t *testing.T) { - ctx := context.Background() - s := newTestStore() - recorder := NewMeasurementRecorder(s) - - release := createTestRelease(s, ctx) - verification := createTestVerification(s, ctx, release.ID(), 1, 60) - - // Update message - testMessage := "Verification completed: 5/5 measurements passed" - err := recorder.UpdateMessage(ctx, verification.Id, testMessage) - - require.NoError(t, err) - - // Verify message was updated - updated, ok := s.JobVerifications.Get(verification.Id) - require.True(t, ok) - assert.Equal(t, testMessage, *updated.Message) -} - -func TestRecorder_UpdateMessage_VerificationNotFound(t *testing.T) { - ctx := context.Background() - s := newTestStore() - recorder := NewMeasurementRecorder(s) - - nonExistentID := uuid.New().String() - err := recorder.UpdateMessage(ctx, nonExistentID, "some message") - - assert.Error(t, err) - assert.Contains(t, err.Error(), "verification not found") -} - -func TestRecorder_ConcurrentRecordMeasurements(t *testing.T) { - ctx := context.Background() - s := newTestStore() - recorder := NewMeasurementRecorder(s) - - release := createTestRelease(s, ctx) - verification := createTestVerification(s, ctx, release.ID(), 1, 60) - - // Record measurements concurrently - var wg sync.WaitGroup - numGoroutines := 10 - - for i := 0; i < numGoroutines; i++ { - wg.Add(1) - go func(idx int) { - defer wg.Done() - measurement := oapi.VerificationMeasurement{ - Message: ptr("Concurrent measurement " + string(rune('0'+idx))), - Status: oapi.Passed, - MeasuredAt: time.Now(), - } - _, err := recorder.RecordMeasurement(ctx, verification.Id, 0, measurement) - assert.NoError(t, err) - }(i) - } - - wg.Wait() - - // Verify all measurements were recorded - updated, ok := s.JobVerifications.Get(verification.Id) - require.True(t, ok) - assert.Len(t, updated.Metrics[0].Measurements, numGoroutines) -} - -func TestRecorder_ConcurrentRecordAndUpdate(t *testing.T) { - ctx := context.Background() - s := newTestStore() - recorder := NewMeasurementRecorder(s) - - release := createTestRelease(s, ctx) - verification := createTestVerification(s, ctx, release.ID(), 3, 60) - - var wg sync.WaitGroup - - // Concurrently record measurements to different metrics - for metricIdx := 0; metricIdx < 3; metricIdx++ { - for i := 0; i < 5; i++ { - wg.Add(1) - go func(mIdx, mNum int) { - defer wg.Done() - measurement := oapi.VerificationMeasurement{ - Message: ptr("Measurement"), - Status: oapi.Passed, - MeasuredAt: time.Now(), - } - _, err := recorder.RecordMeasurement(ctx, verification.Id, mIdx, measurement) - assert.NoError(t, err) - }(metricIdx, i) - } - } - - // Also update message concurrently - for i := 0; i < 5; i++ { - wg.Add(1) - go func(idx int) { - defer wg.Done() - err := recorder.UpdateMessage(ctx, verification.Id, "Message update "+string(rune('0'+idx))) - assert.NoError(t, err) - }(i) - } - - wg.Wait() - - // Verify measurements were recorded (order may vary due to concurrency) - updated, ok := s.JobVerifications.Get(verification.Id) - require.True(t, ok) - for i := 0; i < 3; i++ { - assert.Len(t, updated.Metrics[i].Measurements, 5) - } - assert.NotNil(t, updated.Message) -} - -func TestRecorder_AppendMeasurement_PreservesExistingMeasurements(t *testing.T) { - ctx := context.Background() - s := newTestStore() - recorder := NewMeasurementRecorder(s) - - release := createTestRelease(s, ctx) - verification := createTestVerification(s, ctx, release.ID(), 2, 60) - - // Record first measurement - measurement1 := oapi.VerificationMeasurement{ - Message: ptr("First measurement"), - Status: oapi.Passed, - MeasuredAt: time.Now(), - } - _, err := recorder.RecordMeasurement(ctx, verification.Id, 0, measurement1) - require.NoError(t, err) - - // Record second measurement to same metric - measurement2 := oapi.VerificationMeasurement{ - Message: ptr("Second measurement"), - Status: oapi.Failed, - MeasuredAt: time.Now(), - } - updated, err := recorder.RecordMeasurement(ctx, verification.Id, 0, measurement2) - require.NoError(t, err) - - // Both measurements should be preserved - assert.Len(t, updated.Metrics[0].Measurements, 2) - assert.Equal(t, oapi.Passed, updated.Metrics[0].Measurements[0].Status) - assert.Equal(t, oapi.Failed, updated.Metrics[0].Measurements[1].Status) - - // Other metric should be unaffected - assert.Len(t, updated.Metrics[1].Measurements, 0) -} diff --git a/apps/workspace-engine/pkg/workspace/releasemanager/verification/scheduler.go b/apps/workspace-engine/pkg/workspace/releasemanager/verification/scheduler.go deleted file mode 100644 index 5ca8cdf4d..000000000 --- a/apps/workspace-engine/pkg/workspace/releasemanager/verification/scheduler.go +++ /dev/null @@ -1,330 +0,0 @@ -package verification - -import ( - "context" - "fmt" - "sync" - "time" - "workspace-engine/pkg/oapi" - "workspace-engine/pkg/workspace/releasemanager/verification/metrics" - "workspace-engine/pkg/workspace/store" - - "github.com/charmbracelet/log" -) - -// scheduler manages goroutines for running verification measurements. -// Each metric in each verification gets its own goroutine with a ticker. -// The scheduler delegates measurement execution to the executor and -// store updates to the recorder. -type scheduler struct { - store *store.Store - executor *MeasurementExecutor - recorder *MeasurementRecorder - hooks VerificationHooks - - mu sync.Mutex - cancelFuncs map[string][]context.CancelFunc // verification ID -> cancel functions (one per metric) - completionHookFired map[string]bool // tracks if completion hook was already fired -} - -// newScheduler creates a new verification scheduler -func newScheduler(store *store.Store, hooks VerificationHooks) *scheduler { - return &scheduler{ - store: store, - executor: NewMeasurementExecutor(store), - recorder: NewMeasurementRecorder(store), - hooks: hooks, - cancelFuncs: make(map[string][]context.CancelFunc), - completionHookFired: make(map[string]bool), - } -} - -// StartVerification starts goroutines for all metrics in a verification. -// Each metric gets its own goroutine with a ticker at its interval. -func (s *scheduler) StartVerification(ctx context.Context, verificationID string) { - s.mu.Lock() - defer s.mu.Unlock() - - // Check if already running - if _, exists := s.cancelFuncs[verificationID]; exists { - log.Debug("Verification already running", "verification_id", verificationID) - return - } - - // Check if verification exists and is not already completed - verification, ok := s.store.JobVerifications.Get(verificationID) - if !ok { - log.Error("Verification not found", "verification_id", verificationID) - return - } - - if s.isCompleted(verification) { - log.Debug("Verification already completed, not starting goroutines", - "verification_id", verificationID, - "status", verification.Status()) - return - } - - // Start a goroutine for each metric - cancelFuncs := make([]context.CancelFunc, 0, len(verification.Metrics)) - for metricIndex := range verification.Metrics { - metricCtx, cancel := context.WithCancel(ctx) - cancelFuncs = append(cancelFuncs, cancel) - go s.runMetricLoop(metricCtx, verificationID, metricIndex) - } - - s.cancelFuncs[verificationID] = cancelFuncs - - log.Info("Started verification goroutines", - "verification_id", verificationID, - "metric_count", len(cancelFuncs)) -} - -// StopVerification stops all goroutines for a verification -func (s *scheduler) StopVerification(verificationID string) { - s.mu.Lock() - defer s.mu.Unlock() - - if cancelFuncs, exists := s.cancelFuncs[verificationID]; exists { - for _, cancel := range cancelFuncs { - cancel() - } - delete(s.cancelFuncs, verificationID) - delete(s.completionHookFired, verificationID) - log.Info("Stopped verification goroutines", - "verification_id", verificationID, - "metric_count", len(cancelFuncs)) - } -} - -// runMetricLoop runs measurements for a single metric on a ticker interval. -// All state is read from and written to the store - this goroutine is stateless. -func (s *scheduler) runMetricLoop(ctx context.Context, verificationID string, metricIndex int) { - interval, err := s.getMetricInterval(verificationID, metricIndex) - if err != nil { - log.Error("Failed to get metric interval", "error", err) - return - } - - ticker := time.NewTicker(interval) - defer ticker.Stop() - - // Run first measurement immediately - s.runMeasurementCycle(ctx, verificationID, metricIndex) - - for { - select { - case <-ctx.Done(): - log.Debug("Metric loop cancelled", - "verification_id", verificationID, - "metric_index", metricIndex) - return - - case <-ticker.C: - if s.shouldStopMetric(ctx, verificationID, metricIndex) { - return - } - s.runMeasurementCycle(ctx, verificationID, metricIndex) - } - } -} - -// runMeasurementCycle executes one measurement and handles all related side effects. -func (s *scheduler) runMeasurementCycle(ctx context.Context, verificationID string, metricIndex int) { - // Fetch verification once to get metric and jobID - verification, ok := s.store.JobVerifications.Get(verificationID) - if !ok { - log.Error("Verification not found", "verification_id", verificationID) - return - } - - if metricIndex >= len(verification.Metrics) { - log.Error("Metric index out of range", - "verification_id", verificationID, - "metric_index", metricIndex) - return - } - - metric := &verification.Metrics[metricIndex] - - // Get the job to derive releaseId for measurement executor - job, jobOk := s.store.Jobs.Get(verification.JobId) - if !jobOk { - log.Error("Job not found for verification", "job_id", verification.JobId) - return - } - - // Execute measurement with direct objects - measurement, err := s.executor.Execute(ctx, metric, job.ReleaseId) - - // Record result (measurement or error) - if err != nil { - verification, err = s.recorder.RecordError(ctx, verificationID, metricIndex, err) - if err != nil { - log.Error("Failed to record error", "error", err) - return - } - } else { - verification, err = s.recorder.RecordMeasurement(ctx, verificationID, metricIndex, measurement) - if err != nil { - log.Error("Failed to record measurement", "error", err) - return - } - } - - // Fire hooks and update status - s.handlePostMeasurement(ctx, verification, metricIndex) -} - -// handlePostMeasurement fires hooks and updates verification status after a measurement. -func (s *scheduler) handlePostMeasurement( - ctx context.Context, - verification *oapi.JobVerification, - metricIndex int, -) { - // Fire measurement taken hook - lastIdx := len(verification.Metrics[metricIndex].Measurements) - 1 - if lastIdx >= 0 { - lastMeasurement := &verification.Metrics[metricIndex].Measurements[lastIdx] - if err := s.hooks.OnMeasurementTaken(ctx, verification, metricIndex, lastMeasurement); err != nil { - log.Error("Measurement taken hook failed", - "verification_id", verification.Id, - "metric_index", metricIndex, - "error", err) - } - } - - // Check if verification is complete and fire completion hook - status := verification.Status() - if status == oapi.JobVerificationStatusRunning { - return - } - - s.mu.Lock() - alreadyFired := s.completionHookFired[verification.Id] - if !alreadyFired { - s.completionHookFired[verification.Id] = true - } - s.mu.Unlock() - - if alreadyFired { - return - } - - // Update summary message - message := s.buildSummaryMessage(verification, status) - if err := s.recorder.UpdateMessage(ctx, verification.Id, message); err != nil { - log.Error("Failed to update verification message", "error", err) - } - - // Fire completion hook - if err := s.hooks.OnVerificationComplete(ctx, verification); err != nil { - log.Error("Verification complete hook failed", - "verification_id", verification.Id, - "error", err) - } - - log.Info("Metric measurement completed", - "verification_id", verification.Id, - "metric_index", metricIndex, - "metric_name", verification.Metrics[metricIndex].Name, - "job_id", verification.JobId, - "verification_status", status, - "metric_measurement_count", len(verification.Metrics[metricIndex].Measurements)) -} - -// shouldStopMetric checks if a metric loop should stop and fires the metric complete hook. -func (s *scheduler) shouldStopMetric(ctx context.Context, verificationID string, metricIndex int) bool { - verification, ok := s.store.JobVerifications.Get(verificationID) - if !ok { - log.Warn("Verification not found in store, stopping", - "verification_id", verificationID) - return true - } - - if metricIndex >= len(verification.Metrics) { - log.Warn("Metric index out of range, stopping", - "verification_id", verificationID, - "metric_index", metricIndex) - return true - } - - metric := &verification.Metrics[metricIndex] - measurements := metrics.NewMeasurements(metric.Measurements) - - if measurements.ShouldContinue(metric) { - return false - } - - // Metric is complete - fire hook - if err := s.hooks.OnMetricComplete(ctx, verification, metricIndex); err != nil { - log.Error("Metric complete hook failed", - "verification_id", verificationID, - "metric_index", metricIndex, - "error", err) - } - - log.Info("Metric complete, stopping loop", - "verification_id", verificationID, - "metric_index", metricIndex, - "metric_name", metric.Name) - - return true -} - -// Helper methods - -func (s *scheduler) isCompleted(v *oapi.JobVerification) bool { - status := v.Status() - return status == oapi.JobVerificationStatusPassed || - status == oapi.JobVerificationStatusFailed -} - -const defaultMetricInterval = 30 * time.Second - -func (s *scheduler) getMetricInterval(verificationID string, metricIndex int) (time.Duration, error) { - verification, ok := s.store.JobVerifications.Get(verificationID) - if !ok { - return 0, fmt.Errorf("verification not found: %s", verificationID) - } - - if metricIndex >= len(verification.Metrics) { - return 0, fmt.Errorf("metric index out of range: %d", metricIndex) - } - - metric := &verification.Metrics[metricIndex] - interval := metric.GetInterval() - if interval <= 0 { - log.Info("Using default metric interval", - "verification_id", verificationID, - "metric_index", metricIndex, - "metric_name", metric.Name) - return defaultMetricInterval, nil - } - - return interval, nil -} - -func (s *scheduler) buildSummaryMessage(v *oapi.JobVerification, status oapi.JobVerificationStatus) string { - totalMeasurements := 0 - passedMeasurements := 0 - failedMeasurements := 0 - inconclusiveMeasurements := 0 - - for _, m := range v.Metrics { - for _, measurement := range m.Measurements { - totalMeasurements++ - switch measurement.Status { - case oapi.Passed: - passedMeasurements++ - case oapi.Failed: - failedMeasurements++ - case oapi.Inconclusive: - inconclusiveMeasurements++ - } - } - } - - return fmt.Sprintf("Verification %s: %d passed, %d failed, %d inconclusive (%d total) across %d metrics", - status, passedMeasurements, failedMeasurements, inconclusiveMeasurements, totalMeasurements, len(v.Metrics)) -} diff --git a/apps/workspace-engine/pkg/workspace/releasemanager/verification/scheduler_test.go b/apps/workspace-engine/pkg/workspace/releasemanager/verification/scheduler_test.go deleted file mode 100644 index 8254bd351..000000000 --- a/apps/workspace-engine/pkg/workspace/releasemanager/verification/scheduler_test.go +++ /dev/null @@ -1,777 +0,0 @@ -package verification - -import ( - "context" - "encoding/json" - "net/http" - "net/http/httptest" - "sync" - "testing" - "time" - "workspace-engine/pkg/oapi" - "workspace-engine/pkg/statechange" - "workspace-engine/pkg/workspace/store" - - "github.com/google/uuid" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// Helper functions -func ptr[T any](v T) *T { - return &v -} - -// TestServer wraps httptest.Server with helper methods for verification tests -type TestServer struct { - *httptest.Server - statusCode int - responseBody any - requestCount int - mu sync.Mutex -} - -// NewTestServer creates a test HTTP server that returns configurable responses. -// The server returns 200 with {"status": "ok"} by default. -func NewTestServer() *TestServer { - ts := &TestServer{ - statusCode: http.StatusOK, - responseBody: map[string]any{"status": "ok"}, - } - - ts.Server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ts.mu.Lock() - ts.requestCount++ - statusCode := ts.statusCode - body := ts.responseBody - ts.mu.Unlock() - - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(statusCode) - json.NewEncoder(w).Encode(body) - })) - - return ts -} - -// SetResponse configures the server's response for subsequent requests -func (ts *TestServer) SetResponse(statusCode int, body any) { - ts.mu.Lock() - defer ts.mu.Unlock() - ts.statusCode = statusCode - ts.responseBody = body -} - -// RequestCount returns the number of requests received -func (ts *TestServer) RequestCount() int { - ts.mu.Lock() - defer ts.mu.Unlock() - return ts.requestCount -} - -// createTestHTTPProvider creates an HTTP metric provider pointing to the test server -func createTestHTTPProvider(serverURL string, method oapi.HTTPMetricProviderMethod) oapi.MetricProvider { - provider := oapi.MetricProvider{} - _ = provider.FromHTTPMetricProvider(oapi.HTTPMetricProvider{ - Url: serverURL, - Method: &method, - Type: oapi.Http, - }) - return provider -} - -// createTestHTTPProviderWithTimeout creates an HTTP metric provider with custom timeout -func createTestHTTPProviderWithTimeout(serverURL string, method oapi.HTTPMetricProviderMethod, timeout string) oapi.MetricProvider { - provider := oapi.MetricProvider{} - _ = provider.FromHTTPMetricProvider(oapi.HTTPMetricProvider{ - Url: serverURL, - Method: &method, - Type: oapi.Http, - Timeout: &timeout, - }) - return provider -} - -func newTestStore() *store.Store { - changeset := statechange.NewChangeSet[any]() - return store.New("test-workspace-"+uuid.New().String(), changeset) -} - -func createTestRelease(s *store.Store, ctx context.Context) *oapi.Release { - // Create system - systemId := uuid.New().String() - system := &oapi.System{ - Id: systemId, - Name: "test-system", - Description: ptr("Test system"), - } - _ = s.Systems.Upsert(ctx, system) - - // Create resource - resourceId := uuid.New().String() - resource := &oapi.Resource{ - Id: resourceId, - Name: "test-resource", - Kind: "kubernetes", - Version: "1.0.0", - Identifier: "test-res-1", - Metadata: map[string]string{}, - Config: map[string]interface{}{}, - CreatedAt: time.Now(), - } - _, _ = s.Resources.Upsert(ctx, resource) - - // Create environment - environmentId := uuid.New().String() - environment := &oapi.Environment{ - Id: environmentId, - Name: "test-env", - Description: ptr("Test environment"), - } - selector := &oapi.Selector{} - _ = selector.FromCelSelector(oapi.CelSelector{Cel: "true"}) - environment.ResourceSelector = selector - _ = s.Environments.Upsert(ctx, environment) - - // Create deployment - deploymentId := uuid.New().String() - deployment := &oapi.Deployment{ - Id: deploymentId, - Name: "test-deployment", - Slug: "test-deployment", - Description: ptr("Test deployment"), - } - deploymentSelector := &oapi.Selector{} - _ = deploymentSelector.FromCelSelector(oapi.CelSelector{Cel: "true"}) - deployment.ResourceSelector = deploymentSelector - _ = s.Deployments.Upsert(ctx, deployment) - - // Create version - versionId := uuid.New().String() - version := &oapi.DeploymentVersion{ - Id: versionId, - Tag: "v1.0.0", - DeploymentId: deploymentId, - CreatedAt: time.Now(), - } - s.DeploymentVersions.Upsert(ctx, versionId, version) - - // Create release target - releaseTarget := &oapi.ReleaseTarget{ - ResourceId: resourceId, - EnvironmentId: environmentId, - DeploymentId: deploymentId, - } - _ = s.ReleaseTargets.Upsert(ctx, releaseTarget) - - // Create release - release := &oapi.Release{ - ReleaseTarget: *releaseTarget, - Version: *version, - Variables: map[string]oapi.LiteralValue{}, - } - _ = s.Releases.Upsert(ctx, release) - - return release -} - -func createTestReleaseAndJob(s *store.Store, ctx context.Context) (*oapi.Release, *oapi.Job) { - release := createTestRelease(s, ctx) - job := createTestJob(s, ctx, release.ID()) - return release, job -} - -func createTestJob(s *store.Store, ctx context.Context, releaseId string) *oapi.Job { - completedAt := time.Now() - job := &oapi.Job{ - Id: uuid.New().String(), - ReleaseId: releaseId, - Status: oapi.JobStatusSuccessful, - CreatedAt: completedAt.Add(-1 * time.Minute), - CompletedAt: &completedAt, - JobAgentId: uuid.New().String(), - Metadata: map[string]string{}, - UpdatedAt: time.Now(), - } - s.Jobs.Upsert(ctx, job) - return job -} - -func createTestVerification(s *store.Store, ctx context.Context, jobId string, metricCount int, intervalSeconds int32) *oapi.JobVerification { - return createTestVerificationWithURL(s, ctx, jobId, metricCount, intervalSeconds, "http://localhost/health") -} - -func createTestVerificationWithURL(s *store.Store, ctx context.Context, jobId string, metricCount int, intervalSeconds int32, url string) *oapi.JobVerification { - metrics := make([]oapi.VerificationMetricStatus, metricCount) - for i := 0; i < metricCount; i++ { - // Create a simple HTTP provider config - method := oapi.GET - httpProvider := oapi.HTTPMetricProvider{ - Url: url, - Method: &method, - Type: oapi.Http, - } - provider := oapi.MetricProvider{} - _ = provider.FromHTTPMetricProvider(httpProvider) - - metrics[i] = oapi.VerificationMetricStatus{ - Name: "metric-" + uuid.New().String()[:8], - IntervalSeconds: intervalSeconds, - Count: 5, - SuccessCondition: "result.statusCode == 200", - FailureThreshold: ptr(2), - Provider: provider, - Measurements: []oapi.VerificationMeasurement{}, - } - } - - verification := &oapi.JobVerification{ - Id: uuid.New().String(), - JobId: jobId, - Metrics: metrics, - CreatedAt: time.Now(), - } - - s.JobVerifications.Upsert(ctx, verification) - return verification -} - -// Tests - -func TestNewScheduler(t *testing.T) { - s := newTestStore() - scheduler := newScheduler(s, DefaultHooks()) - - assert.NotNil(t, scheduler) - assert.NotNil(t, scheduler.store) - assert.NotNil(t, scheduler.cancelFuncs) - assert.Equal(t, 0, len(scheduler.cancelFuncs)) -} - -func TestScheduler_StartVerification_NotFound(t *testing.T) { - ctx := context.Background() - s := newTestStore() - scheduler := newScheduler(s, DefaultHooks()) - - // Try to start a verification that doesn't exist - nonExistentID := uuid.New().String() - scheduler.StartVerification(ctx, nonExistentID) - - // Should not panic and should not create any cancel functions - scheduler.mu.Lock() - assert.Equal(t, 0, len(scheduler.cancelFuncs)) - scheduler.mu.Unlock() -} - -func TestScheduler_StartVerification_AlreadyRunning(t *testing.T) { - ctx := context.Background() - s := newTestStore() - scheduler := newScheduler(s, DefaultHooks()) - - release := createTestRelease(s, ctx) - verification := createTestVerification(s, ctx, release.ID(), 1, 3600) - - // Start verification first time - scheduler.StartVerification(ctx, verification.Id) - - scheduler.mu.Lock() - cancelFuncs1 := scheduler.cancelFuncs[verification.Id] - scheduler.mu.Unlock() - - assert.Equal(t, 1, len(cancelFuncs1)) - - // Try to start again - should be a no-op - scheduler.StartVerification(ctx, verification.Id) - - scheduler.mu.Lock() - cancelFuncs2 := scheduler.cancelFuncs[verification.Id] - scheduler.mu.Unlock() - - // Should be the same cancel functions - assert.Equal(t, len(cancelFuncs1), len(cancelFuncs2)) - - // Clean up - scheduler.StopVerification(verification.Id) -} - -func TestScheduler_StartVerification_AlreadyCompleted(t *testing.T) { - ctx := context.Background() - s := newTestStore() - scheduler := newScheduler(s, DefaultHooks()) - - release := createTestRelease(s, ctx) - verification := createTestVerification(s, ctx, release.ID(), 1, 3600) - - // Mark all metrics as complete by adding measurements - for i := range verification.Metrics { - for j := 0; j < verification.Metrics[i].Count; j++ { - msg := "Success" - verification.Metrics[i].Measurements = append(verification.Metrics[i].Measurements, oapi.VerificationMeasurement{ - Message: &msg, - Status: oapi.Passed, - MeasuredAt: time.Now(), - Data: &map[string]any{"statusCode": 200}, - }) - } - } - s.JobVerifications.Upsert(ctx, verification) - - // Try to start - should not start any goroutines - scheduler.StartVerification(ctx, verification.Id) - - scheduler.mu.Lock() - assert.Equal(t, 0, len(scheduler.cancelFuncs)) - scheduler.mu.Unlock() -} - -func TestScheduler_StartVerification_Success(t *testing.T) { - ctx := context.Background() - s := newTestStore() - scheduler := newScheduler(s, DefaultHooks()) - - release := createTestRelease(s, ctx) - verification := createTestVerification(s, ctx, release.ID(), 3, 3600) - - scheduler.StartVerification(ctx, verification.Id) - - scheduler.mu.Lock() - cancelFuncs := scheduler.cancelFuncs[verification.Id] - scheduler.mu.Unlock() - - assert.Equal(t, 3, len(cancelFuncs), "should have one cancel func per metric") - - // Clean up - scheduler.StopVerification(verification.Id) -} - -func TestScheduler_StopVerification(t *testing.T) { - ctx := context.Background() - s := newTestStore() - scheduler := newScheduler(s, DefaultHooks()) - - release := createTestRelease(s, ctx) - verification := createTestVerification(s, ctx, release.ID(), 2, 3600) - - scheduler.StartVerification(ctx, verification.Id) - - scheduler.mu.Lock() - assert.Equal(t, 1, len(scheduler.cancelFuncs)) - assert.Equal(t, 2, len(scheduler.cancelFuncs[verification.Id])) - scheduler.mu.Unlock() - - // Stop the verification - scheduler.StopVerification(verification.Id) - - scheduler.mu.Lock() - assert.Equal(t, 0, len(scheduler.cancelFuncs)) - scheduler.mu.Unlock() -} - -func TestScheduler_StopVerification_NotRunning(t *testing.T) { - s := newTestStore() - scheduler := newScheduler(s, DefaultHooks()) - - nonExistentID := uuid.New().String() - - // Should not panic - assert.NotPanics(t, func() { - scheduler.StopVerification(nonExistentID) - }) -} - -func TestScheduler_StopVerification_MultipleTimes(t *testing.T) { - ctx := context.Background() - s := newTestStore() - scheduler := newScheduler(s, DefaultHooks()) - - release := createTestRelease(s, ctx) - verification := createTestVerification(s, ctx, release.ID(), 1, 3600) - - scheduler.StartVerification(ctx, verification.Id) - scheduler.StopVerification(verification.Id) - - // Stop again - should not panic - assert.NotPanics(t, func() { - scheduler.StopVerification(verification.Id) - }) -} - -func TestScheduler_ConcurrentStartStop(t *testing.T) { - ctx := context.Background() - s := newTestStore() - scheduler := newScheduler(s, DefaultHooks()) - - // Create multiple verifications - verificationIDs := make([]string, 10) - for i := 0; i < 10; i++ { - release := createTestRelease(s, ctx) - verification := createTestVerification(s, ctx, release.ID(), 2, 3600) - verificationIDs[i] = verification.Id - } - - var wg sync.WaitGroup - - // Concurrently start all verifications - for _, vid := range verificationIDs { - wg.Add(1) - go func(id string) { - defer wg.Done() - scheduler.StartVerification(ctx, id) - }(vid) - } - - wg.Wait() - - // Verify all started - scheduler.mu.Lock() - assert.Equal(t, 10, len(scheduler.cancelFuncs)) - scheduler.mu.Unlock() - - // Concurrently stop all verifications - for _, vid := range verificationIDs { - wg.Add(1) - go func(id string) { - defer wg.Done() - scheduler.StopVerification(id) - }(vid) - } - - wg.Wait() - - // Verify all stopped - scheduler.mu.Lock() - assert.Equal(t, 0, len(scheduler.cancelFuncs)) - scheduler.mu.Unlock() -} - -func TestScheduler_MultipleMetrics(t *testing.T) { - ctx := context.Background() - s := newTestStore() - scheduler := newScheduler(s, DefaultHooks()) - - release := createTestRelease(s, ctx) - - // Create verification with 5 metrics - verification := createTestVerification(s, ctx, release.ID(), 5, 3600) - - scheduler.StartVerification(ctx, verification.Id) - - scheduler.mu.Lock() - cancelFuncs := scheduler.cancelFuncs[verification.Id] - scheduler.mu.Unlock() - - assert.Equal(t, 5, len(cancelFuncs), "should have one goroutine per metric") - - // Clean up - scheduler.StopVerification(verification.Id) -} - -func TestScheduler_RestartAfterStop(t *testing.T) { - ctx := context.Background() - s := newTestStore() - scheduler := newScheduler(s, DefaultHooks()) - - release := createTestRelease(s, ctx) - verification := createTestVerification(s, ctx, release.ID(), 2, 3600) - - // Start, stop, start again - scheduler.StartVerification(ctx, verification.Id) - - scheduler.mu.Lock() - assert.Equal(t, 1, len(scheduler.cancelFuncs)) - scheduler.mu.Unlock() - - scheduler.StopVerification(verification.Id) - - scheduler.mu.Lock() - assert.Equal(t, 0, len(scheduler.cancelFuncs)) - scheduler.mu.Unlock() - - scheduler.StartVerification(ctx, verification.Id) - - scheduler.mu.Lock() - assert.Equal(t, 1, len(scheduler.cancelFuncs)) - assert.Equal(t, 2, len(scheduler.cancelFuncs[verification.Id])) - scheduler.mu.Unlock() - - // Clean up - scheduler.StopVerification(verification.Id) -} - -func TestScheduler_VerificationWithNoMetrics(t *testing.T) { - ctx := context.Background() - s := newTestStore() - scheduler := newScheduler(s, DefaultHooks()) - - release := createTestRelease(s, ctx) - job := createTestJob(s, ctx, release.ID()) - - verification := &oapi.JobVerification{ - Id: uuid.New().String(), - JobId: job.Id, - Metrics: []oapi.VerificationMetricStatus{}, - CreatedAt: time.Now(), - } - s.JobVerifications.Upsert(ctx, verification) - - // Should handle empty metrics gracefully - scheduler.StartVerification(ctx, verification.Id) - - scheduler.mu.Lock() - cancelFuncs := scheduler.cancelFuncs[verification.Id] - scheduler.mu.Unlock() - - assert.Equal(t, 0, len(cancelFuncs)) -} - -// Integration test: verify measurements are actually taken -func TestScheduler_Integration_MeasurementsTaken(t *testing.T) { - if testing.Short() { - t.Skip("Skipping integration test in short mode") - } - - ctx := context.Background() - s := newTestStore() - scheduler := newScheduler(s, DefaultHooks()) - - _, job := createTestReleaseAndJob(s, ctx) - - // Create verification with very short interval for testing - verification := createTestVerification(s, ctx, job.Id, 1, 1) - - // Start the verification - scheduler.StartVerification(ctx, verification.Id) - - // Wait for measurements to be taken - // Poll until we have at least 2 measurements - // Interval is 1 second, so 2 measurements should happen within 2 seconds - var updatedVerification *oapi.JobVerification - require.Eventually(t, func() bool { - var ok bool - updatedVerification, ok = s.JobVerifications.Get(verification.Id) - if !ok { - return false - } - totalMeasurements := 0 - for _, metric := range updatedVerification.Metrics { - totalMeasurements += len(metric.Measurements) - } - return totalMeasurements >= 2 - }, 3*time.Second, 100*time.Millisecond, "should have taken at least 2 measurements") - - // Should have at least 2-3 measurements (initial + 2-3 ticks) - totalMeasurements := 0 - for _, metric := range updatedVerification.Metrics { - totalMeasurements += len(metric.Measurements) - } - - assert.GreaterOrEqual(t, totalMeasurements, 2, "should have taken at least 2 measurements") - assert.LessOrEqual(t, totalMeasurements, 5, "should not have taken too many measurements") - - // Clean up - scheduler.StopVerification(verification.Id) -} - -// Test that goroutines stop when metrics are complete -func TestScheduler_Integration_StopsWhenMetricsComplete(t *testing.T) { - if testing.Short() { - t.Skip("Skipping integration test in short mode") - } - - ctx := context.Background() - s := newTestStore() - scheduler := newScheduler(s, DefaultHooks()) - - _, job := createTestReleaseAndJob(s, ctx) - - // Create verification with 1 measurement count - verification := createTestVerification(s, ctx, job.Id, 1, 1) - verification.Metrics[0].Count = 1 // Only take 1 measurement - s.JobVerifications.Upsert(ctx, verification) - - // Start the verification - scheduler.StartVerification(ctx, verification.Id) - - // Wait for the metric to complete (1 measurement) - var updatedVerification *oapi.JobVerification - require.Eventually(t, func() bool { - var ok bool - updatedVerification, ok = s.JobVerifications.Get(verification.Id) - if !ok { - return false - } - return len(updatedVerification.Metrics[0].Measurements) >= 1 - }, 2*time.Second, 100*time.Millisecond, "should have taken 1 measurement") - - // Should have exactly 1 measurement (goroutine should have stopped) - assert.Equal(t, 1, len(updatedVerification.Metrics[0].Measurements)) - - // Clean up - scheduler.StopVerification(verification.Id) -} - -// Test that verification stops early on failure limit -func TestScheduler_Integration_StopsOnFailureLimit(t *testing.T) { - if testing.Short() { - t.Skip("Skipping integration test in short mode") - } - - ctx := context.Background() - s := newTestStore() - scheduler := newScheduler(s, DefaultHooks()) - - _, job := createTestReleaseAndJob(s, ctx) - - // Create verification with failure limit of 2 - verification := createTestVerification(s, ctx, job.Id, 1, 1) - verification.Metrics[0].Count = 10 // Allow up to 10 measurements - verification.Metrics[0].FailureThreshold = ptr(2) // But stop after 3 failures (limit exceeded) - s.JobVerifications.Upsert(ctx, verification) - - // Start the verification - scheduler.StartVerification(ctx, verification.Id) - - // Wait for measurements to exceed failure limit - // Poll until we have at least 3 failed measurements (limit exceeded) - var updatedVerification *oapi.JobVerification - require.Eventually(t, func() bool { - var ok bool - updatedVerification, ok = s.JobVerifications.Get(verification.Id) - if !ok { - return false - } - return len(updatedVerification.Metrics[0].Measurements) >= 3 - }, 3*time.Second, 100*time.Millisecond, "should have at least 3 measurements") - - // Should have stopped after exceeding failure limit - measurementCount := len(updatedVerification.Metrics[0].Measurements) - assert.GreaterOrEqual(t, measurementCount, 3, "should have at least 3 measurements") - assert.LessOrEqual(t, measurementCount, 4, "should have stopped near failure limit, not taken all 10") - - // Clean up - scheduler.StopVerification(verification.Id) -} - -// Test concurrent measurements across multiple metrics -func TestScheduler_Integration_ConcurrentMetrics(t *testing.T) { - if testing.Short() { - t.Skip("Skipping integration test in short mode") - } - - ctx := context.Background() - s := newTestStore() - scheduler := newScheduler(s, DefaultHooks()) - - _, job := createTestReleaseAndJob(s, ctx) - - // Create verification with 3 metrics - verification := createTestVerification(s, ctx, job.Id, 3, 1) - - // Start the verification - scheduler.StartVerification(ctx, verification.Id) - - // Wait for all metrics to take measurements - // Poll until all 3 metrics have at least 2 measurements each - var updatedVerification *oapi.JobVerification - require.Eventually(t, func() bool { - var ok bool - updatedVerification, ok = s.JobVerifications.Get(verification.Id) - if !ok { - return false - } - // Check if all metrics have at least 2 measurements - for _, metric := range updatedVerification.Metrics { - if len(metric.Measurements) < 2 { - return false - } - } - return true - }, 3*time.Second, 100*time.Millisecond, "all metrics should have at least 2 measurements") - - // Each metric should have taken measurements - for i, metric := range updatedVerification.Metrics { - assert.GreaterOrEqual(t, len(metric.Measurements), 2, - "metric %d should have taken at least 2 measurements", i) - } - - // Clean up - scheduler.StopVerification(verification.Id) -} - -// Benchmark tests -func BenchmarkScheduler_StartVerification(b *testing.B) { - ctx := context.Background() - s := newTestStore() - scheduler := newScheduler(s, DefaultHooks()) - - // Pre-create verifications - verificationIDs := make([]string, b.N) - for i := 0; i < b.N; i++ { - release := createTestRelease(s, ctx) - verification := createTestVerification(s, ctx, release.ID(), 2, 3600) - verificationIDs[i] = verification.Id - } - - b.ResetTimer() - for i := 0; i < b.N; i++ { - scheduler.StartVerification(ctx, verificationIDs[i]) - } - - // Clean up - for _, vid := range verificationIDs { - scheduler.StopVerification(vid) - } -} - -func BenchmarkScheduler_StopVerification(b *testing.B) { - ctx := context.Background() - s := newTestStore() - scheduler := newScheduler(s, DefaultHooks()) - - // Pre-create and start verifications - verificationIDs := make([]string, b.N) - for i := 0; i < b.N; i++ { - release := createTestRelease(s, ctx) - verification := createTestVerification(s, ctx, release.ID(), 2, 3600) - verificationIDs[i] = verification.Id - scheduler.StartVerification(ctx, verification.Id) - } - - b.ResetTimer() - for i := 0; i < b.N; i++ { - scheduler.StopVerification(verificationIDs[i]) - } -} - -func BenchmarkScheduler_ConcurrentOperations(b *testing.B) { - ctx := context.Background() - s := newTestStore() - scheduler := newScheduler(s, DefaultHooks()) - - // Pre-create verifications - verificationIDs := make([]string, 100) - for i := 0; i < 100; i++ { - release := createTestRelease(s, ctx) - verification := createTestVerification(s, ctx, release.ID(), 2, 3600) - verificationIDs[i] = verification.Id - } - - b.ResetTimer() - b.RunParallel(func(pb *testing.PB) { - i := 0 - for pb.Next() { - vid := verificationIDs[i%100] - if i%2 == 0 { - scheduler.StartVerification(ctx, vid) - } else { - scheduler.StopVerification(vid) - } - i++ - } - }) - - // Clean up - for _, vid := range verificationIDs { - scheduler.StopVerification(vid) - } -} diff --git a/apps/workspace-engine/pkg/workspace/releasemanager/verification_hooks.go b/apps/workspace-engine/pkg/workspace/releasemanager/verification_hooks.go deleted file mode 100644 index fc5d1bc24..000000000 --- a/apps/workspace-engine/pkg/workspace/releasemanager/verification_hooks.go +++ /dev/null @@ -1,59 +0,0 @@ -package releasemanager - -import ( - "context" - "fmt" - "workspace-engine/pkg/oapi" - "workspace-engine/pkg/workspace/releasemanager/verification" - "workspace-engine/pkg/workspace/store" -) - -type releasemanagerVerificationHooks struct { - stateIndex *StateIndex - store *store.Store -} - -var _ verification.VerificationHooks = &releasemanagerVerificationHooks{} - -func newReleaseManagerVerificationHooks(store *store.Store, stateIndex *StateIndex) *releasemanagerVerificationHooks { - return &releasemanagerVerificationHooks{ - store: store, - stateIndex: stateIndex, - } -} - -func (h *releasemanagerVerificationHooks) dirtyStateForVerification(ctx context.Context, verification *oapi.JobVerification) error { - job, ok := h.store.Jobs.Get(verification.JobId) - if !ok { - return fmt.Errorf("job not found") - } - - release, ok := h.store.Releases.Get(job.ReleaseId) - if !ok { - return fmt.Errorf("release not found") - } - - h.stateIndex.DirtyCurrentAndJob(release.ReleaseTarget) - h.stateIndex.Recompute(ctx) - return nil -} - -func (h *releasemanagerVerificationHooks) OnMeasurementTaken(ctx context.Context, verification *oapi.JobVerification, metricIndex int, measurement *oapi.VerificationMeasurement) error { - return h.dirtyStateForVerification(ctx, verification) -} - -func (h *releasemanagerVerificationHooks) OnMetricComplete(ctx context.Context, verification *oapi.JobVerification, metricIndex int) error { - return h.dirtyStateForVerification(ctx, verification) -} - -func (h *releasemanagerVerificationHooks) OnVerificationStarted(ctx context.Context, verification *oapi.JobVerification) error { - return h.dirtyStateForVerification(ctx, verification) -} - -func (h *releasemanagerVerificationHooks) OnVerificationComplete(ctx context.Context, verification *oapi.JobVerification) error { - return h.dirtyStateForVerification(ctx, verification) -} - -func (h *releasemanagerVerificationHooks) OnVerificationStopped(ctx context.Context, verification *oapi.JobVerification) error { - return h.dirtyStateForVerification(ctx, verification) -} diff --git a/apps/workspace-engine/pkg/workspace/workflowmanager/manager_test.go b/apps/workspace-engine/pkg/workspace/workflowmanager/manager_test.go index 4e1c7bb40..a12296d90 100644 --- a/apps/workspace-engine/pkg/workspace/workflowmanager/manager_test.go +++ b/apps/workspace-engine/pkg/workspace/workflowmanager/manager_test.go @@ -7,7 +7,6 @@ import ( "workspace-engine/pkg/oapi" "workspace-engine/pkg/statechange" "workspace-engine/pkg/workspace/jobagents" - "workspace-engine/pkg/workspace/releasemanager/verification" "workspace-engine/pkg/workspace/store" "github.com/google/uuid" @@ -17,7 +16,7 @@ import ( func TestWorkflowManager_CreatesNewWorkflowRun(t *testing.T) { ctx := context.Background() store := store.New("test-workspace", statechange.NewChangeSet[any]()) - jobAgentRegistry := jobagents.NewRegistry(store, verification.NewManager(store)) + jobAgentRegistry := jobagents.NewRegistry(store) manager := NewWorkflowManager(store, jobAgentRegistry) var stringInput oapi.WorkflowInput @@ -90,7 +89,7 @@ func TestWorkflowManager_CreatesNewWorkflowRun(t *testing.T) { func TestWorkflowManager_DispatchesAllJobsConcurrently(t *testing.T) { ctx := context.Background() store := store.New("test-workspace", statechange.NewChangeSet[any]()) - jobAgentRegistry := jobagents.NewRegistry(store, verification.NewManager(store)) + jobAgentRegistry := jobagents.NewRegistry(store) manager := NewWorkflowManager(store, jobAgentRegistry) var stringInput oapi.WorkflowInput @@ -179,7 +178,7 @@ func TestWorkflowManager_DispatchesAllJobsConcurrently(t *testing.T) { func TestWorkflowView_IsComplete(t *testing.T) { ctx := context.Background() store := store.New("test-workspace", statechange.NewChangeSet[any]()) - jobAgentRegistry := jobagents.NewRegistry(store, verification.NewManager(store)) + jobAgentRegistry := jobagents.NewRegistry(store) manager := NewWorkflowManager(store, jobAgentRegistry) jobAgent1 := &oapi.JobAgent{ @@ -235,7 +234,7 @@ func TestWorkflowView_IsComplete(t *testing.T) { func TestCreateWorkflow_SkipsJobWhenIfEvaluatesToFalse(t *testing.T) { ctx := context.Background() store := store.New("test-workspace", statechange.NewChangeSet[any]()) - jobAgentRegistry := jobagents.NewRegistry(store, verification.NewManager(store)) + jobAgentRegistry := jobagents.NewRegistry(store) manager := NewWorkflowManager(store, jobAgentRegistry) jobAgent := &oapi.JobAgent{ @@ -271,7 +270,7 @@ func TestCreateWorkflow_SkipsJobWhenIfEvaluatesToFalse(t *testing.T) { func TestMaybeSetDefaultInputValues_SetsStringDefault(t *testing.T) { store := store.New("test-workspace", statechange.NewChangeSet[any]()) - jobAgentRegistry := jobagents.NewRegistry(store, verification.NewManager(store)) + jobAgentRegistry := jobagents.NewRegistry(store) manager := NewWorkflowManager(store, jobAgentRegistry) var stringInput oapi.WorkflowInput @@ -294,7 +293,7 @@ func TestMaybeSetDefaultInputValues_SetsStringDefault(t *testing.T) { func TestMaybeSetDefaultInputValues_SetsNumberDefault(t *testing.T) { store := store.New("test-workspace", statechange.NewChangeSet[any]()) - jobAgentRegistry := jobagents.NewRegistry(store, verification.NewManager(store)) + jobAgentRegistry := jobagents.NewRegistry(store) manager := NewWorkflowManager(store, jobAgentRegistry) var numberInput oapi.WorkflowInput @@ -317,7 +316,7 @@ func TestMaybeSetDefaultInputValues_SetsNumberDefault(t *testing.T) { func TestMaybeSetDefaultInputValues_SetsBooleanDefault(t *testing.T) { store := store.New("test-workspace", statechange.NewChangeSet[any]()) - jobAgentRegistry := jobagents.NewRegistry(store, verification.NewManager(store)) + jobAgentRegistry := jobagents.NewRegistry(store) manager := NewWorkflowManager(store, jobAgentRegistry) var booleanInput oapi.WorkflowInput @@ -340,7 +339,7 @@ func TestMaybeSetDefaultInputValues_SetsBooleanDefault(t *testing.T) { func TestMaybeSetDefaultInputValues_SetsObjectDefault(t *testing.T) { store := store.New("test-workspace", statechange.NewChangeSet[any]()) - jobAgentRegistry := jobagents.NewRegistry(store, verification.NewManager(store)) + jobAgentRegistry := jobagents.NewRegistry(store) manager := NewWorkflowManager(store, jobAgentRegistry) var objectInput oapi.WorkflowInput @@ -362,7 +361,7 @@ func TestMaybeSetDefaultInputValues_SetsObjectDefault(t *testing.T) { func TestMaybeSetDefaultInputValues_DoesNotOverwriteExistingValue(t *testing.T) { store := store.New("test-workspace", statechange.NewChangeSet[any]()) - jobAgentRegistry := jobagents.NewRegistry(store, verification.NewManager(store)) + jobAgentRegistry := jobagents.NewRegistry(store) manager := NewWorkflowManager(store, jobAgentRegistry) var stringInput oapi.WorkflowInput @@ -387,7 +386,7 @@ func TestMaybeSetDefaultInputValues_DoesNotOverwriteExistingValue(t *testing.T) func TestMaybeSetDefaultInputValues_HandlesMultipleInputTypes(t *testing.T) { store := store.New("test-workspace", statechange.NewChangeSet[any]()) - jobAgentRegistry := jobagents.NewRegistry(store, verification.NewManager(store)) + jobAgentRegistry := jobagents.NewRegistry(store) manager := NewWorkflowManager(store, jobAgentRegistry) var stringInput oapi.WorkflowInput @@ -426,7 +425,7 @@ func TestMaybeSetDefaultInputValues_HandlesMultipleInputTypes(t *testing.T) { func TestMaybeSetDefaultInputValues_SkipsInputsWithoutDefault(t *testing.T) { store := store.New("test-workspace", statechange.NewChangeSet[any]()) - jobAgentRegistry := jobagents.NewRegistry(store, verification.NewManager(store)) + jobAgentRegistry := jobagents.NewRegistry(store) manager := NewWorkflowManager(store, jobAgentRegistry) var stringInput oapi.WorkflowInput diff --git a/apps/workspace-engine/pkg/workspace/workspace.go b/apps/workspace-engine/pkg/workspace/workspace.go index 2acc3e782..14accd2ed 100644 --- a/apps/workspace-engine/pkg/workspace/workspace.go +++ b/apps/workspace-engine/pkg/workspace/workspace.go @@ -14,7 +14,6 @@ import ( "workspace-engine/pkg/workspace/releasemanager/policy/evaluator/environmentprogression" "workspace-engine/pkg/workspace/releasemanager/trace" "workspace-engine/pkg/workspace/releasemanager/trace/spanstore" - "workspace-engine/pkg/workspace/releasemanager/verification" "workspace-engine/pkg/workspace/store" "workspace-engine/pkg/workspace/workflowmanager" ) @@ -30,16 +29,13 @@ func New(ctx context.Context, id string, options ...WorkspaceOption) *Workspace traceStore: spanstore.NewInMemoryStore(), } - // Apply options first to allow setting traceStore for _, option := range options { option(ws) } - ws.verificationManager = verification.NewManager(s) - ws.jobAgentRegistry = jobagents.NewRegistry(ws.store, ws.verificationManager) + ws.jobAgentRegistry = jobagents.NewRegistry(ws.store) - // Create release manager with trace store (will panic if nil) - ws.releasemanager = releasemanager.New(s, ws.traceStore, ws.verificationManager, ws.jobAgentRegistry) + ws.releasemanager = releasemanager.New(s, ws.traceStore, ws.jobAgentRegistry) ws.workflowManager = workflowmanager.NewWorkflowManager(s, ws.jobAgentRegistry) reconcileFn := func(ctx context.Context, targets []*oapi.ReleaseTarget) error { @@ -50,12 +46,19 @@ func New(ctx context.Context, id string, options ...WorkspaceOption) *Workspace return ws.releasemanager.ReconcileTargets(ctx, targets, releasemanager.WithTrigger(trace.TriggerJobSuccess)) } - ws.actionOrchestrator = action. + verificationStarter := &verificationaction.DBVerificationStarter{ + Queue: ws.reconcileQueue, + WorkspaceID: ws.ID, + } + + orchestrator := action. NewOrchestrator(s). - RegisterAction(verificationaction.NewVerificationAction(ws.verificationManager)). RegisterAction(deploymentdependency.NewDeploymentDependencyAction(s, reconcileFn)). RegisterAction(environmentprogression.NewEnvironmentProgressionActionFromStore(s, reconcileFn)). - RegisterAction(rollback.NewRollbackAction(s, ws.jobAgentRegistry)) + RegisterAction(rollback.NewRollbackAction(s, ws.jobAgentRegistry)). + RegisterAction(verificationaction.NewVerificationAction(verificationStarter)) + + ws.actionOrchestrator = orchestrator ws.workflowActionOrchestrator = workflowmanager. NewWorkflowActionOrchestrator(s). @@ -69,7 +72,6 @@ type Workspace struct { changeset *statechange.ChangeSet[any] store *store.Store - verificationManager *verification.Manager workflowManager *workflowmanager.Manager releasemanager *releasemanager.Manager traceStore releasemanager.PersistenceStore @@ -99,10 +101,6 @@ func (w *Workspace) ReleaseManager() *releasemanager.Manager { return w.releasemanager } -func (w *Workspace) VerificationManager() *verification.Manager { - return w.verificationManager -} - func (w *Workspace) WorkflowManager() *workflowmanager.Manager { return w.workflowManager } diff --git a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/github/githubaction_test.go b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/github/githubaction_test.go index 1906501ac..a3341b0a1 100644 --- a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/github/githubaction_test.go +++ b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/github/githubaction_test.go @@ -71,8 +71,6 @@ func (m *mockSetter) getCalls() []updateCall { // ----- Helpers ----- -func ptr[T any](v T) *T { return &v } - func validConfig() oapi.JobAgentConfig { return oapi.JobAgentConfig{ "installationId": float64(12345), diff --git a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/testrunner/testrunner.go b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/testrunner/testrunner.go index 3b0d9ce86..ae2e82e78 100644 --- a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/testrunner/testrunner.go +++ b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/testrunner/testrunner.go @@ -7,12 +7,8 @@ import ( "workspace-engine/pkg/oapi" "workspace-engine/pkg/workspace/jobagents/types" - - "go.opentelemetry.io/otel" ) -var tracer = otel.Tracer("jobagents/testrunner") - var _ types.Dispatchable = &TestRunner{} type TestRunner struct { diff --git a/apps/workspace-engine/svc/controllers/jobverificationmetric/metrics/metric.go b/apps/workspace-engine/svc/controllers/jobverificationmetric/metrics/metric.go index 6e92ea8d3..43c2d3843 100644 --- a/apps/workspace-engine/svc/controllers/jobverificationmetric/metrics/metric.go +++ b/apps/workspace-engine/svc/controllers/jobverificationmetric/metrics/metric.go @@ -53,7 +53,6 @@ func Measure(ctx context.Context, metric *VerificationMetric, providerCtx *provi return Measurement{}, err } - message := "Measurement completed" hasFailureCondition := metric.FailureCondition != nil && *metric.FailureCondition != "" if hasFailureCondition { @@ -65,14 +64,13 @@ func Measure(ctx context.Context, metric *VerificationMetric, providerCtx *provi failed, err := failureEvaluator.Evaluate(data) if err != nil { - message = fmt.Sprintf("Failure evaluation failed: %s", err.Error()) + log.Error("Failure evaluation failed", "error", err) } if failed { - message = "Failure condition met" return Measurement{ MetricID: metric.ID, - Message: message, + Message: "Failure condition met", Status: StatusFailed, Data: data, MeasuredAt: measuredAt, @@ -88,14 +86,13 @@ func Measure(ctx context.Context, metric *VerificationMetric, providerCtx *provi passed, err := successEvaluator.Evaluate(data) if err != nil { - message = fmt.Sprintf("Success evaluation failed: %s", err.Error()) + log.Error("Success evaluation failed", "error", err) } if passed { - message = "Success condition met" return Measurement{ MetricID: metric.ID, - Message: message, + Message: "Success condition met", Status: StatusPassed, Data: data, MeasuredAt: measuredAt, @@ -103,20 +100,18 @@ func Measure(ctx context.Context, metric *VerificationMetric, providerCtx *provi } if hasFailureCondition { - message = "Waiting for success or failure condition" return Measurement{ MetricID: metric.ID, - Message: message, + Message: "Waiting for success or failure condition", Status: StatusInconclusive, Data: data, MeasuredAt: measuredAt, }, nil } - message = "Success condition not met" return Measurement{ MetricID: metric.ID, - Message: message, + Message: "Success condition not met", Status: StatusFailed, Data: data, MeasuredAt: measuredAt, diff --git a/apps/workspace-engine/svc/controllers/jobverificationmetric/reconcile_test.go b/apps/workspace-engine/svc/controllers/jobverificationmetric/reconcile_test.go index f2ff813cb..efcd99ac9 100644 --- a/apps/workspace-engine/svc/controllers/jobverificationmetric/reconcile_test.go +++ b/apps/workspace-engine/svc/controllers/jobverificationmetric/reconcile_test.go @@ -161,8 +161,6 @@ func reconcileItem(scopeID, kind string) reconcile.Item { func int32Ptr(i int32) *int32 { return &i } -func strPtr(s string) *string { return &s } - // --------------------------------------------------------------------------- // Controller.Process tests // --------------------------------------------------------------------------- diff --git a/apps/workspace-engine/svc/workspaceconsumer/consumer.go b/apps/workspace-engine/svc/workspaceconsumer/consumer.go index 00044ed43..05e5426f6 100644 --- a/apps/workspace-engine/svc/workspaceconsumer/consumer.go +++ b/apps/workspace-engine/svc/workspaceconsumer/consumer.go @@ -86,6 +86,7 @@ func (s *Service) configureManager() error { manager.WithPersistentStore(store), manager.WithWorkspaceCreateOptions( workspace.WithTraceStore(traceStore), + workspace.WithReconcileQueue(s.reconcileQueue), workspace.WithStoreOptions( wsstore.WithDBDeploymentVersions(bgCtx), wsstore.WithDBEnvironments(bgCtx), diff --git a/apps/workspace-engine/test/controllers/harness/mocks.go b/apps/workspace-engine/test/controllers/harness/mocks.go index 654f9f470..c232f3fe9 100644 --- a/apps/workspace-engine/test/controllers/harness/mocks.go +++ b/apps/workspace-engine/test/controllers/harness/mocks.go @@ -63,9 +63,7 @@ func (s *SelectorEvalSetter) SetComputedDeploymentResources(_ context.Context, _ // DesiredReleaseGetter implements desiredrelease.Getter. type DesiredReleaseGetter struct { - mu sync.Mutex - - Scope *evaluator.EvaluatorScope + Scope *evaluator.EvaluatorScope Versions []*oapi.DeploymentVersion Policies []*oapi.Policy ApprovalRecords []*oapi.UserApprovalRecord diff --git a/apps/workspace-engine/test/e2e/engine_job_verification_query_test.go b/apps/workspace-engine/test/e2e/engine_job_verification_query_test.go deleted file mode 100644 index 1571c9867..000000000 --- a/apps/workspace-engine/test/e2e/engine_job_verification_query_test.go +++ /dev/null @@ -1,229 +0,0 @@ -package e2e - -import ( - "context" - "testing" - "time" - "workspace-engine/pkg/oapi" - "workspace-engine/test/integration" - - "github.com/google/uuid" - "github.com/stretchr/testify/assert" -) - -func TestEngine_JobVerification_QueryByReleaseId(t *testing.T) { - jobAgentID := uuid.New().String() - deploymentID := uuid.New().String() - environmentID := uuid.New().String() - resourceID := uuid.New().String() - - engine := integration.NewTestWorkspace(t, - integration.WithJobAgent( - integration.JobAgentID(jobAgentID), - integration.JobAgentName("test-agent"), - ), - integration.WithSystem( - integration.WithDeployment( - integration.DeploymentID(deploymentID), - integration.DeploymentJobAgent(jobAgentID), - integration.DeploymentCelResourceSelector("true"), - integration.WithDeploymentVersion( - integration.DeploymentVersionTag("v1.0.0"), - ), - ), - integration.WithEnvironment( - integration.EnvironmentID(environmentID), - integration.EnvironmentCelResourceSelector("true"), - ), - ), - integration.WithResource( - integration.ResourceID(resourceID), - ), - ) - - ctx := context.Background() - - // Get the created release - releases := engine.Workspace().Store().Releases.Items() - assert.Len(t, releases, 1) - - var release *oapi.Release - for _, r := range releases { - release = r - break - } - - // Get the job - agentJobs := engine.Workspace().Store().Jobs.GetJobsForAgent(jobAgentID) - assert.NotEmpty(t, agentJobs) - - var job *oapi.Job - for _, j := range agentJobs { - job = j - break - } - - // Start a verification - metricProvider := oapi.MetricProvider{} - _ = metricProvider.FromSleepMetricProvider(oapi.SleepMetricProvider{ - Type: oapi.Sleep, - DurationSeconds: 0, - }) - - metric := oapi.VerificationMetricSpec{ - Name: "health-check", - IntervalSeconds: 1, - Count: 1, - SuccessCondition: "result.ok == true", - Provider: metricProvider, - } - - err := engine.Workspace().ReleaseManager().VerificationManager().StartVerification(ctx, job, []oapi.VerificationMetricSpec{metric}) - assert.NoError(t, err) - - time.Sleep(500 * time.Millisecond) - - // Query by job ID - byJob := engine.Workspace().Store().JobVerifications.GetByJobId(job.Id) - assert.Len(t, byJob, 1) - - // Query by release ID - byRelease := engine.Workspace().Store().JobVerifications.GetByReleaseId(release.ID()) - assert.Len(t, byRelease, 1) - assert.Equal(t, job.Id, byRelease[0].JobId) -} - -func TestEngine_JobVerification_StatusCheck(t *testing.T) { - jobAgentID := uuid.New().String() - deploymentID := uuid.New().String() - environmentID := uuid.New().String() - resourceID := uuid.New().String() - - engine := integration.NewTestWorkspace(t, - integration.WithJobAgent( - integration.JobAgentID(jobAgentID), - ), - integration.WithSystem( - integration.WithDeployment( - integration.DeploymentID(deploymentID), - integration.DeploymentJobAgent(jobAgentID), - integration.DeploymentCelResourceSelector("true"), - integration.WithDeploymentVersion( - integration.DeploymentVersionTag("v1.0.0"), - ), - ), - integration.WithEnvironment( - integration.EnvironmentID(environmentID), - integration.EnvironmentCelResourceSelector("true"), - ), - ), - integration.WithResource( - integration.ResourceID(resourceID), - ), - ) - - ctx := context.Background() - - agentJobs := engine.Workspace().Store().Jobs.GetJobsForAgent(jobAgentID) - var job *oapi.Job - for _, j := range agentJobs { - job = j - break - } - - metricProvider := oapi.MetricProvider{} - _ = metricProvider.FromSleepMetricProvider(oapi.SleepMetricProvider{ - Type: oapi.Sleep, - DurationSeconds: 0, - }) - - metric := oapi.VerificationMetricSpec{ - Name: "quick-check", - IntervalSeconds: 1, - Count: 1, - SuccessCondition: "result.ok == true", - Provider: metricProvider, - } - - err := engine.Workspace().ReleaseManager().VerificationManager().StartVerification(ctx, job, []oapi.VerificationMetricSpec{metric}) - assert.NoError(t, err) - - time.Sleep(500 * time.Millisecond) - - // Check verification status - status := engine.Workspace().Store().JobVerifications.GetJobVerificationStatus(job.Id) - assert.NotEmpty(t, string(status)) - - // Verify Status() method on the verification itself - verifications := engine.Workspace().Store().JobVerifications.GetByJobId(job.Id) - assert.Len(t, verifications, 1) - v := verifications[0] - assert.NotEmpty(t, string(v.Status())) -} - -func TestEngine_JobVerification_MultipleJobs(t *testing.T) { - jobAgentID := uuid.New().String() - deploymentID := uuid.New().String() - environmentID := uuid.New().String() - r1ID := uuid.New().String() - r2ID := uuid.New().String() - - engine := integration.NewTestWorkspace(t, - integration.WithJobAgent( - integration.JobAgentID(jobAgentID), - ), - integration.WithSystem( - integration.WithDeployment( - integration.DeploymentID(deploymentID), - integration.DeploymentJobAgent(jobAgentID), - integration.DeploymentCelResourceSelector("true"), - integration.WithDeploymentVersion( - integration.DeploymentVersionTag("v1.0.0"), - ), - ), - integration.WithEnvironment( - integration.EnvironmentID(environmentID), - integration.EnvironmentCelResourceSelector("true"), - ), - ), - integration.WithResource( - integration.ResourceID(r1ID), - integration.ResourceName("resource-1"), - ), - integration.WithResource( - integration.ResourceID(r2ID), - integration.ResourceName("resource-2"), - ), - ) - - ctx := context.Background() - - // Both resources should have jobs - agentJobs := engine.Workspace().Store().Jobs.GetJobsForAgent(jobAgentID) - assert.Len(t, agentJobs, 2) - - metricProvider := oapi.MetricProvider{} - _ = metricProvider.FromSleepMetricProvider(oapi.SleepMetricProvider{ - Type: oapi.Sleep, - DurationSeconds: 0, - }) - metric := oapi.VerificationMetricSpec{ - Name: "check", - IntervalSeconds: 1, - Count: 1, - SuccessCondition: "result.ok == true", - Provider: metricProvider, - } - - // Start verification for each job - for _, job := range agentJobs { - err := engine.Workspace().ReleaseManager().VerificationManager().StartVerification(ctx, job, []oapi.VerificationMetricSpec{metric}) - assert.NoError(t, err) - } - - time.Sleep(500 * time.Millisecond) - - // Verify each job has its own verification - allVerifications := engine.Workspace().Store().JobVerifications.Items() - assert.Len(t, allVerifications, 2) -} diff --git a/apps/workspace-engine/test/e2e/engine_policy_rollback_evaluator_test.go b/apps/workspace-engine/test/e2e/engine_policy_rollback_evaluator_test.go index aef0d3238..8b99df114 100644 --- a/apps/workspace-engine/test/e2e/engine_policy_rollback_evaluator_test.go +++ b/apps/workspace-engine/test/e2e/engine_policy_rollback_evaluator_test.go @@ -3,7 +3,6 @@ package e2e import ( "context" "testing" - "time" "workspace-engine/pkg/events/handler" "workspace-engine/pkg/oapi" "workspace-engine/test/integration" @@ -84,92 +83,3 @@ func TestEngine_RollbackEvaluator_MultipleFailureStatuses(t *testing.T) { rollbackRelease, _ := engine.Workspace().Releases().Get(rollbackJob.ReleaseId) assert.Equal(t, "v1.0.0", rollbackRelease.Version.Tag) } - -// TestEngine_RollbackEvaluator_CombinedJobAndVerificationFailure tests rollback -// with both job status and verification failure configured. -func TestEngine_RollbackEvaluator_CombinedJobAndVerificationFailure(t *testing.T) { - jobAgentID := uuid.New().String() - deploymentID := uuid.New().String() - environmentID := uuid.New().String() - resourceID := uuid.New().String() - - engine := integration.NewTestWorkspace(t, - integration.WithJobAgent( - integration.JobAgentID(jobAgentID), - ), - integration.WithSystem( - integration.WithDeployment( - integration.DeploymentID(deploymentID), - integration.DeploymentJobAgent(jobAgentID), - integration.DeploymentCelResourceSelector("true"), - ), - integration.WithEnvironment( - integration.EnvironmentID(environmentID), - integration.EnvironmentCelResourceSelector("true"), - ), - ), - integration.WithResource( - integration.ResourceID(resourceID), - ), - integration.WithPolicy( - integration.WithPolicySelector("true"), - integration.WithPolicyRule( - integration.WithRuleRollback( - []oapi.JobStatus{oapi.JobStatusFailure}, - true, - ), - ), - ), - ) - - ctx := context.Background() - - // Deploy v1.0.0 and succeed - dv1 := c.NewDeploymentVersion() - dv1.DeploymentId = deploymentID - dv1.Tag = "v1.0.0" - engine.PushEvent(ctx, handler.DeploymentVersionCreate, dv1) - - pendingJobs := engine.Workspace().Jobs().GetPending() - require.Len(t, pendingJobs, 1) - job1 := getFirstJob(pendingJobs) - engine.PushEvent(ctx, handler.JobUpdate, createJobUpdateEvent(job1, jobAgentID, oapi.JobStatusSuccessful)) - - // Deploy v2.0.0 and succeed (but fail verification) - dv2 := c.NewDeploymentVersion() - dv2.DeploymentId = deploymentID - dv2.Tag = "v2.0.0" - engine.PushEvent(ctx, handler.DeploymentVersionCreate, dv2) - - pendingJobs = engine.Workspace().Jobs().GetPending() - require.Len(t, pendingJobs, 1) - job2 := getFirstJob(pendingJobs) - engine.PushEvent(ctx, handler.JobUpdate, createJobUpdateEvent(job2, jobAgentID, oapi.JobStatusSuccessful)) - - // Start and fail verification - metricProvider := oapi.MetricProvider{} - _ = metricProvider.FromSleepMetricProvider(oapi.SleepMetricProvider{ - Type: oapi.Sleep, - DurationSeconds: 0, - }) - failureCondition := "result.ok == true" - metric := oapi.VerificationMetricSpec{ - Name: "combined-check", - IntervalSeconds: 1, - Count: 1, - FailureCondition: &failureCondition, - Provider: metricProvider, - } - - err := engine.Workspace().ReleaseManager().VerificationManager().StartVerification(ctx, job2, []oapi.VerificationMetricSpec{metric}) - require.NoError(t, err) - - time.Sleep(3 * time.Second) - - pendingJobs = engine.Workspace().Jobs().GetPending() - require.Len(t, pendingJobs, 1, "expected rollback from combined rule on verification failure") - - rollbackJob := getFirstJob(pendingJobs) - rollbackRelease, _ := engine.Workspace().Releases().Get(rollbackJob.ReleaseId) - assert.Equal(t, "v1.0.0", rollbackRelease.Version.Tag) -} diff --git a/apps/workspace-engine/test/e2e/engine_policy_rollback_test.go b/apps/workspace-engine/test/e2e/engine_policy_rollback_test.go index a3596b4ed..2ad338531 100644 --- a/apps/workspace-engine/test/e2e/engine_policy_rollback_test.go +++ b/apps/workspace-engine/test/e2e/engine_policy_rollback_test.go @@ -454,278 +454,6 @@ func TestEngine_Rollback_OnJobFailure_DisabledPolicy(t *testing.T) { assert.Len(t, pendingJobs, 0, "should not create rollback job when policy is disabled") } -// TestEngine_Rollback_OnVerificationFailure tests that when verification fails -// and the policy has onVerificationFailure=true, rollback is triggered. -func TestEngine_Rollback_OnVerificationFailure(t *testing.T) { - jobAgentID := uuid.New().String() - deploymentID := uuid.New().String() - environmentID := uuid.New().String() - resourceID := uuid.New().String() - - engine := integration.NewTestWorkspace(t, - integration.WithJobAgent( - integration.JobAgentID(jobAgentID), - integration.JobAgentName("test-agent"), - ), - integration.WithSystem( - integration.SystemName("test-system"), - integration.WithDeployment( - integration.DeploymentID(deploymentID), - integration.DeploymentName("api-service"), - integration.DeploymentJobAgent(jobAgentID), - integration.DeploymentCelResourceSelector("true"), - ), - integration.WithEnvironment( - integration.EnvironmentID(environmentID), - integration.EnvironmentName("production"), - integration.EnvironmentCelResourceSelector("true"), - ), - ), - integration.WithResource( - integration.ResourceID(resourceID), - integration.ResourceName("server-1"), - ), - integration.WithPolicy( - integration.PolicyName("rollback-policy"), - integration.WithPolicySelector("true"), - integration.WithPolicyRule( - integration.WithRuleRollbackOnVerificationFailure(), - ), - ), - ) - - ctx := context.Background() - - // Step 1: Deploy v1.0.0 and mark successful - dv1 := c.NewDeploymentVersion() - dv1.DeploymentId = deploymentID - dv1.Tag = "v1.0.0" - engine.PushEvent(ctx, handler.DeploymentVersionCreate, dv1) - - pendingJobs := engine.Workspace().Jobs().GetPending() - job1 := getFirstJob(pendingJobs) - - engine.PushEvent(ctx, handler.JobUpdate, createJobUpdateEvent(job1, jobAgentID, oapi.JobStatusSuccessful)) - - // Step 2: Deploy v2.0.0 and mark successful (job-level) - dv2 := c.NewDeploymentVersion() - dv2.DeploymentId = deploymentID - dv2.Tag = "v2.0.0" - engine.PushEvent(ctx, handler.DeploymentVersionCreate, dv2) - - pendingJobs = engine.Workspace().Jobs().GetPending() - job2 := getFirstJob(pendingJobs) - - engine.PushEvent(ctx, handler.JobUpdate, createJobUpdateEvent(job2, jobAgentID, oapi.JobStatusSuccessful)) - - // Verify no pending jobs after job2 succeeds - pendingJobs = engine.Workspace().Jobs().GetPending() - assert.Len(t, pendingJobs, 0) - - // Step 3: Start and fail verification for v2.0.0 - metricProvider := oapi.MetricProvider{} - _ = metricProvider.FromSleepMetricProvider(oapi.SleepMetricProvider{ - Type: oapi.Sleep, - DurationSeconds: 0, - }) - - // This condition will always fail (result.ok is undefined for sleep) - failureCondition := "result.ok == true" - metric := oapi.VerificationMetricSpec{ - Name: "always-fail", - IntervalSeconds: 1, - Count: 1, - FailureCondition: &failureCondition, - Provider: metricProvider, - } - - err := engine.Workspace().ReleaseManager().VerificationManager().StartVerification(ctx, job2, []oapi.VerificationMetricSpec{metric}) - require.NoError(t, err) - - // Wait for verification to complete and fail - time.Sleep(3 * time.Second) - - // Step 4: Verify rollback job was created for v1.0.0 - pendingJobs = engine.Workspace().Jobs().GetPending() - require.Len(t, pendingJobs, 1, "expected rollback job after verification failure") - - rollbackJob := getFirstJob(pendingJobs) - rollbackRelease, _ := engine.Workspace().Releases().Get(rollbackJob.ReleaseId) - assert.Equal(t, "v1.0.0", rollbackRelease.Version.Tag, "rollback should target v1.0.0") -} - -// TestEngine_Rollback_OnVerificationFailure_NotConfigured tests that verification failure -// does NOT trigger rollback when onVerificationFailure is not configured. -func TestEngine_Rollback_OnVerificationFailure_NotConfigured(t *testing.T) { - jobAgentID := uuid.New().String() - deploymentID := uuid.New().String() - environmentID := uuid.New().String() - resourceID := uuid.New().String() - - engine := integration.NewTestWorkspace(t, - integration.WithJobAgent( - integration.JobAgentID(jobAgentID), - integration.JobAgentName("test-agent"), - ), - integration.WithSystem( - integration.SystemName("test-system"), - integration.WithDeployment( - integration.DeploymentID(deploymentID), - integration.DeploymentName("api-service"), - integration.DeploymentJobAgent(jobAgentID), - integration.DeploymentCelResourceSelector("true"), - ), - integration.WithEnvironment( - integration.EnvironmentID(environmentID), - integration.EnvironmentName("production"), - integration.EnvironmentCelResourceSelector("true"), - ), - ), - integration.WithResource( - integration.ResourceID(resourceID), - integration.ResourceName("server-1"), - ), - integration.WithPolicy( - integration.PolicyName("rollback-policy"), - integration.WithPolicySelector("true"), - integration.WithPolicyRule( - // Only rollback on job failure, NOT on verification failure - integration.WithRuleRollbackOnJobStatuses(oapi.JobStatusFailure), - ), - ), - ) - - ctx := context.Background() - - // Step 1: Deploy v1.0.0 and mark successful - dv1 := c.NewDeploymentVersion() - dv1.DeploymentId = deploymentID - dv1.Tag = "v1.0.0" - engine.PushEvent(ctx, handler.DeploymentVersionCreate, dv1) - - pendingJobs := engine.Workspace().Jobs().GetPending() - job1 := getFirstJob(pendingJobs) - - engine.PushEvent(ctx, handler.JobUpdate, createJobUpdateEvent(job1, jobAgentID, oapi.JobStatusSuccessful)) - - // Step 2: Deploy v2.0.0 and mark successful (job-level) - dv2 := c.NewDeploymentVersion() - dv2.DeploymentId = deploymentID - dv2.Tag = "v2.0.0" - engine.PushEvent(ctx, handler.DeploymentVersionCreate, dv2) - - pendingJobs = engine.Workspace().Jobs().GetPending() - job2 := getFirstJob(pendingJobs) - - engine.PushEvent(ctx, handler.JobUpdate, createJobUpdateEvent(job2, jobAgentID, oapi.JobStatusSuccessful)) - - // Step 3: Start and fail verification for v2.0.0 - metricProvider := oapi.MetricProvider{} - _ = metricProvider.FromSleepMetricProvider(oapi.SleepMetricProvider{ - Type: oapi.Sleep, - DurationSeconds: 0, - }) - - failureCondition := "result.ok == true" - metric := oapi.VerificationMetricSpec{ - Name: "always-fail", - IntervalSeconds: 1, - Count: 1, - SuccessCondition: failureCondition, - Provider: metricProvider, - } - - err := engine.Workspace().ReleaseManager().VerificationManager().StartVerification(ctx, job2, []oapi.VerificationMetricSpec{metric}) - require.NoError(t, err) - - // Wait for verification to complete - time.Sleep(3 * time.Second) - - // Step 4: Verify NO rollback job was created (onVerificationFailure not configured) - pendingJobs = engine.Workspace().Jobs().GetPending() - assert.Len(t, pendingJobs, 0, "should not create rollback job when onVerificationFailure is not configured") -} - -// TestEngine_Rollback_OnVerificationFailure_NoPreviousRelease tests that verification -// failure handles the case where there's no previous release gracefully. -func TestEngine_Rollback_OnVerificationFailure_NoPreviousRelease(t *testing.T) { - jobAgentID := uuid.New().String() - deploymentID := uuid.New().String() - environmentID := uuid.New().String() - resourceID := uuid.New().String() - - engine := integration.NewTestWorkspace(t, - integration.WithJobAgent( - integration.JobAgentID(jobAgentID), - integration.JobAgentName("test-agent"), - ), - integration.WithSystem( - integration.SystemName("test-system"), - integration.WithDeployment( - integration.DeploymentID(deploymentID), - integration.DeploymentName("api-service"), - integration.DeploymentJobAgent(jobAgentID), - integration.DeploymentCelResourceSelector("true"), - ), - integration.WithEnvironment( - integration.EnvironmentID(environmentID), - integration.EnvironmentName("production"), - integration.EnvironmentCelResourceSelector("true"), - ), - ), - integration.WithResource( - integration.ResourceID(resourceID), - integration.ResourceName("server-1"), - ), - integration.WithPolicy( - integration.PolicyName("rollback-policy"), - integration.WithPolicySelector("true"), - integration.WithPolicyRule( - integration.WithRuleRollbackOnVerificationFailure(), - ), - ), - ) - - ctx := context.Background() - - // First ever deployment - deploy v1.0.0 and mark successful - dv1 := c.NewDeploymentVersion() - dv1.DeploymentId = deploymentID - dv1.Tag = "v1.0.0" - engine.PushEvent(ctx, handler.DeploymentVersionCreate, dv1) - - pendingJobs := engine.Workspace().Jobs().GetPending() - job1 := getFirstJob(pendingJobs) - - engine.PushEvent(ctx, handler.JobUpdate, createJobUpdateEvent(job1, jobAgentID, oapi.JobStatusSuccessful)) - - // Start and fail verification for v1.0.0 - metricProvider := oapi.MetricProvider{} - _ = metricProvider.FromSleepMetricProvider(oapi.SleepMetricProvider{ - Type: oapi.Sleep, - DurationSeconds: 0, - }) - - failureCondition := "result.ok == true" - metric := oapi.VerificationMetricSpec{ - Name: "always-fail", - IntervalSeconds: 1, - Count: 1, - SuccessCondition: failureCondition, - Provider: metricProvider, - } - - err := engine.Workspace().ReleaseManager().VerificationManager().StartVerification(ctx, job1, []oapi.VerificationMetricSpec{metric}) - require.NoError(t, err) - - // Wait for verification to complete - time.Sleep(3 * time.Second) - - // Should NOT create a rollback job (nothing to roll back to) - pendingJobs = engine.Workspace().Jobs().GetPending() - assert.Len(t, pendingJobs, 0, "should not create rollback job when no previous release exists") -} - // TestEngine_Rollback_BothJobAndVerificationConfigured tests a policy that has both // job status rollback AND verification failure rollback configured. func TestEngine_Rollback_BothJobAndVerificationConfigured(t *testing.T) { diff --git a/apps/workspace-engine/test/e2e/engine_verification_hooks_test.go b/apps/workspace-engine/test/e2e/engine_verification_hooks_test.go deleted file mode 100644 index 6193f89d8..000000000 --- a/apps/workspace-engine/test/e2e/engine_verification_hooks_test.go +++ /dev/null @@ -1,247 +0,0 @@ -package e2e - -import ( - "context" - "testing" - "time" - "workspace-engine/pkg/events/handler" - "workspace-engine/pkg/oapi" - "workspace-engine/test/integration" - - "github.com/google/uuid" - "github.com/stretchr/testify/assert" -) - -func TestEngineVerificationHooks(t *testing.T) { - jobAgentID := uuid.New().String() - deploymentID := uuid.New().String() - environmentID := uuid.New().String() - resourceID := uuid.New().String() - - ws := integration.NewTestWorkspace(t, - integration.WithJobAgent( - integration.JobAgentID(jobAgentID), - integration.JobAgentName("test-job-agent"), - ), - integration.WithSystem( - integration.SystemName("test-system"), - integration.WithDeployment( - integration.DeploymentID(deploymentID), - integration.DeploymentName("test-deployment"), - integration.DeploymentJobAgent(jobAgentID), - integration.DeploymentCelResourceSelector("true"), - integration.WithDeploymentVersion( - integration.DeploymentVersionTag("v1.0.0"), - ), - ), - integration.WithEnvironment( - integration.EnvironmentID(environmentID), - integration.EnvironmentName("test-environment"), - integration.EnvironmentCelResourceSelector("true"), - ), - ), - integration.WithResource( - integration.ResourceID(resourceID), - integration.ResourceName("test-resource"), - integration.ResourceKind("test-resource"), - ), - ) - - ctx := context.Background() - - // start verification for release - releases := ws.Workspace().Store().Releases.Items() - releasesSlice := make([]*oapi.Release, 0, len(releases)) - for _, release := range releases { - releasesSlice = append(releasesSlice, release) - } - assert.Len(t, releasesSlice, 1) - release := releasesSlice[0] - - metricProvider := oapi.MetricProvider{} - _ = metricProvider.FromSleepMetricProvider(oapi.SleepMetricProvider{ - Type: oapi.Sleep, - DurationSeconds: 3, - }) - - successCondition := "result.ok == true" - metric := oapi.VerificationMetricSpec{ - Name: "test-metric", - IntervalSeconds: 3, - Count: 1, - SuccessCondition: successCondition, - Provider: metricProvider, - } - - // Get the job that was created for the release - agentJobs := ws.Workspace().Store().Jobs.GetJobsForAgent(jobAgentID) - agentJobsSlice := make([]*oapi.Job, 0, len(agentJobs)) - for _, job := range agentJobs { - agentJobsSlice = append(agentJobsSlice, job) - } - assert.Len(t, agentJobsSlice, 1) - agentJob := agentJobsSlice[0] - - err := ws.Workspace().ReleaseManager().VerificationManager().StartVerification(ctx, agentJob, []oapi.VerificationMetricSpec{metric}) - assert.NoError(t, err) - - time.Sleep(500 * time.Millisecond) - - verifications := ws.Workspace().Store().JobVerifications.GetByJobId(agentJob.Id) - assert.Len(t, verifications, 1) - verification := verifications[0] - assert.Equal(t, agentJob.Id, verification.JobId) - - // mark job as successful - completedAt := time.Now() - jobUpdateEvent := oapi.JobUpdateEvent{ - Id: &agentJob.Id, - AgentId: &jobAgentID, - Job: oapi.Job{ - Id: agentJob.Id, - Status: oapi.JobStatusSuccessful, - CompletedAt: &completedAt, - }, - FieldsToUpdate: &[]oapi.JobUpdateEventFieldsToUpdate{ - oapi.JobUpdateEventFieldsToUpdateStatus, - oapi.JobUpdateEventFieldsToUpdateCompletedAt, - }, - } - - ws.PushEvent(ctx, handler.JobUpdate, jobUpdateEvent) - - // verify current release is nil since verification is not yet completed - rm := ws.Workspace().ReleaseManager() - releaseTarget := release.ReleaseTarget - releaseTargetState, err := rm.GetReleaseTargetState(ctx, &releaseTarget) - assert.NoError(t, err) - - assert.Nil(t, releaseTargetState.CurrentRelease) - - time.Sleep(5 * time.Second) - - // verify current release is set since verification is completed - releaseTargetState, err = rm.GetReleaseTargetState(ctx, &releaseTarget) - assert.NoError(t, err) - - if releaseTargetState.CurrentRelease == nil { - t.Fatalf("expected current release, got nil") - } - assert.Equal(t, release.ID(), releaseTargetState.CurrentRelease.ID()) -} - -func TestEngineVerificationHooks_SuccessThreshold(t *testing.T) { - jobAgentID := uuid.New().String() - deploymentID := uuid.New().String() - environmentID := uuid.New().String() - resourceID := uuid.New().String() - - ws := integration.NewTestWorkspace(t, - integration.WithJobAgent( - integration.JobAgentID(jobAgentID), - integration.JobAgentName("test-job-agent"), - ), - integration.WithSystem( - integration.SystemName("test-system"), - integration.WithDeployment( - integration.DeploymentID(deploymentID), - integration.DeploymentName("test-deployment"), - integration.DeploymentJobAgent(jobAgentID), - integration.DeploymentCelResourceSelector("true"), - integration.WithDeploymentVersion( - integration.DeploymentVersionTag("v1.0.0"), - ), - ), - integration.WithEnvironment( - integration.EnvironmentID(environmentID), - integration.EnvironmentName("test-environment"), - integration.EnvironmentCelResourceSelector("true"), - ), - ), - integration.WithResource( - integration.ResourceID(resourceID), - integration.ResourceName("test-resource"), - integration.ResourceKind("test-resource"), - ), - ) - - ctx := context.Background() - - // start verification for release - releases := ws.Workspace().Store().Releases.Items() - releasesSlice := make([]*oapi.Release, 0, len(releases)) - for _, release := range releases { - releasesSlice = append(releasesSlice, release) - } - assert.Len(t, releasesSlice, 1) - release := releasesSlice[0] - - metricProvider := oapi.MetricProvider{} - _ = metricProvider.FromSleepMetricProvider(oapi.SleepMetricProvider{ - Type: oapi.Sleep, - DurationSeconds: 0, // instant measurements for fast test - }) - - successCondition := "result.ok == true" - metric := oapi.VerificationMetricSpec{ - Name: "test-metric", - IntervalSeconds: 1, // short interval for quick measurements - Count: 5, - SuccessCondition: successCondition, - SuccessThreshold: &[]int{2}[0], - Provider: metricProvider, - } - - // Get the job for the release - agentJobs := ws.Workspace().Store().Jobs.GetJobsForAgent(jobAgentID) - agentJobsSlice := make([]*oapi.Job, 0, len(agentJobs)) - for _, job := range agentJobs { - agentJobsSlice = append(agentJobsSlice, job) - } - assert.Len(t, agentJobsSlice, 1) - agentJob := agentJobsSlice[0] - - err := ws.Workspace().ReleaseManager().VerificationManager().StartVerification(ctx, agentJob, []oapi.VerificationMetricSpec{metric}) - assert.NoError(t, err) - - // mark job as successful - completedAt := time.Now() - jobUpdateEvent := oapi.JobUpdateEvent{ - Id: &agentJob.Id, - AgentId: &jobAgentID, - Job: oapi.Job{ - Id: agentJob.Id, - Status: oapi.JobStatusSuccessful, - CompletedAt: &completedAt, - }, - FieldsToUpdate: &[]oapi.JobUpdateEventFieldsToUpdate{ - oapi.JobUpdateEventFieldsToUpdateStatus, - oapi.JobUpdateEventFieldsToUpdateCompletedAt, - }, - } - - ws.PushEvent(ctx, handler.JobUpdate, jobUpdateEvent) - - // wait for verification to complete (with successThreshold=2, should exit early after 2 measurements) - // Need to wait at least 1 second for the second measurement (IntervalSeconds: 1), plus buffer - time.Sleep(2 * time.Second) - - // verify verification exists and completed with early exit - verifications := ws.Workspace().Store().JobVerifications.GetByJobId(agentJob.Id) - assert.Len(t, verifications, 1) - verification := verifications[0] - assert.Equal(t, agentJob.Id, verification.JobId) - // verify early exit: should have only 2 measurements (successThreshold), not all 5 (count) - assert.Len(t, verification.Metrics[0].Measurements, 2, "expected early exit after 2 consecutive successes") - - // verify current release is set since verification completed successfully - rm := ws.Workspace().ReleaseManager() - releaseTarget := release.ReleaseTarget - releaseTargetState, err := rm.GetReleaseTargetState(ctx, &releaseTarget) - assert.NoError(t, err) - - if releaseTargetState.CurrentRelease == nil { - t.Fatalf("expected current release, got nil") - } - assert.Equal(t, release.ID(), releaseTargetState.CurrentRelease.ID()) -} diff --git a/apps/workspace-engine/test/e2e/engine_verification_lifecycle_test.go b/apps/workspace-engine/test/e2e/engine_verification_lifecycle_test.go deleted file mode 100644 index 1c7e3ab58..000000000 --- a/apps/workspace-engine/test/e2e/engine_verification_lifecycle_test.go +++ /dev/null @@ -1,258 +0,0 @@ -package e2e - -import ( - "context" - "testing" - "time" - "workspace-engine/pkg/events/handler" - "workspace-engine/pkg/oapi" - "workspace-engine/test/integration" - - "github.com/google/uuid" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// TestEngine_VerificationLifecycle_StartAndComplete tests a full verification lifecycle: -// start verification, wait for metric completion, verify the hooks fired. -func TestEngine_VerificationLifecycle_StartAndComplete(t *testing.T) { - jobAgentID := uuid.New().String() - deploymentID := uuid.New().String() - environmentID := uuid.New().String() - resourceID := uuid.New().String() - - ws := integration.NewTestWorkspace(t, - integration.WithJobAgent( - integration.JobAgentID(jobAgentID), - ), - integration.WithSystem( - integration.WithDeployment( - integration.DeploymentID(deploymentID), - integration.DeploymentJobAgent(jobAgentID), - integration.DeploymentCelResourceSelector("true"), - integration.WithDeploymentVersion( - integration.DeploymentVersionTag("v1.0.0"), - ), - ), - integration.WithEnvironment( - integration.EnvironmentID(environmentID), - integration.EnvironmentCelResourceSelector("true"), - ), - ), - integration.WithResource( - integration.ResourceID(resourceID), - ), - ) - - ctx := context.Background() - - // Get the job - agentJobs := ws.Workspace().Store().Jobs.GetJobsForAgent(jobAgentID) - require.Len(t, agentJobs, 1) - var job *oapi.Job - for _, j := range agentJobs { - job = j - } - - // Start a verification with a sleep metric - metricProvider := oapi.MetricProvider{} - _ = metricProvider.FromSleepMetricProvider(oapi.SleepMetricProvider{ - Type: oapi.Sleep, - DurationSeconds: 0, - }) - - metric := oapi.VerificationMetricSpec{ - Name: "health", - IntervalSeconds: 1, - Count: 2, - SuccessCondition: "result.ok == true", - Provider: metricProvider, - } - - err := ws.Workspace().ReleaseManager().VerificationManager().StartVerification(ctx, job, []oapi.VerificationMetricSpec{metric}) - require.NoError(t, err) - - // Mark job successful - completedAt := time.Now() - ws.PushEvent(ctx, handler.JobUpdate, oapi.JobUpdateEvent{ - Id: &job.Id, - AgentId: &jobAgentID, - Job: oapi.Job{ - Id: job.Id, - Status: oapi.JobStatusSuccessful, - CompletedAt: &completedAt, - }, - FieldsToUpdate: &[]oapi.JobUpdateEventFieldsToUpdate{ - oapi.JobUpdateEventFieldsToUpdateStatus, - oapi.JobUpdateEventFieldsToUpdateCompletedAt, - }, - }) - - // Wait for verification to complete (2 measurements at 1s intervals) - time.Sleep(4 * time.Second) - - // Verify verification exists and has measurements - verifications := ws.Workspace().Store().JobVerifications.GetByJobId(job.Id) - require.Len(t, verifications, 1) - - v := verifications[0] - assert.Equal(t, job.Id, v.JobId) - assert.NotEmpty(t, v.Metrics) - assert.NotEmpty(t, v.Metrics[0].Measurements) - - // Verify the verification status - status := v.Status() - assert.NotEmpty(t, string(status)) -} - -// TestEngine_VerificationLifecycle_StopForJob tests that stopping verifications -// for a job properly cancels the running verification. -func TestEngine_VerificationLifecycle_StopForJob(t *testing.T) { - jobAgentID := uuid.New().String() - deploymentID := uuid.New().String() - environmentID := uuid.New().String() - resourceID := uuid.New().String() - - ws := integration.NewTestWorkspace(t, - integration.WithJobAgent( - integration.JobAgentID(jobAgentID), - ), - integration.WithSystem( - integration.WithDeployment( - integration.DeploymentID(deploymentID), - integration.DeploymentJobAgent(jobAgentID), - integration.DeploymentCelResourceSelector("true"), - integration.WithDeploymentVersion( - integration.DeploymentVersionTag("v1.0.0"), - ), - ), - integration.WithEnvironment( - integration.EnvironmentID(environmentID), - integration.EnvironmentCelResourceSelector("true"), - ), - ), - integration.WithResource( - integration.ResourceID(resourceID), - ), - ) - - ctx := context.Background() - - // Get the job - agentJobs := ws.Workspace().Store().Jobs.GetJobsForAgent(jobAgentID) - require.Len(t, agentJobs, 1) - var job *oapi.Job - for _, j := range agentJobs { - job = j - } - - // Start verification with many measurements (long-running) - metricProvider := oapi.MetricProvider{} - _ = metricProvider.FromSleepMetricProvider(oapi.SleepMetricProvider{ - Type: oapi.Sleep, - DurationSeconds: 0, - }) - - metric := oapi.VerificationMetricSpec{ - Name: "long-check", - IntervalSeconds: 1, - Count: 100, // very long - SuccessCondition: "result.ok == true", - Provider: metricProvider, - } - - err := ws.Workspace().ReleaseManager().VerificationManager().StartVerification(ctx, job, []oapi.VerificationMetricSpec{metric}) - require.NoError(t, err) - - time.Sleep(500 * time.Millisecond) - - // Verify verification was started - verifications := ws.Workspace().Store().JobVerifications.GetByJobId(job.Id) - assert.Len(t, verifications, 1) - - // Stop verifications for this job - ws.Workspace().ReleaseManager().VerificationManager().StopVerificationsForJob(ctx, job.Id) - - // Allow cleanup - time.Sleep(500 * time.Millisecond) - - // Verification should still exist in the store (just stopped, not removed) - verifications = ws.Workspace().Store().JobVerifications.GetByJobId(job.Id) - assert.Len(t, verifications, 1) -} - -// TestEngine_VerificationLifecycle_MultipleMetrics tests starting a verification with -// multiple metrics at once. -func TestEngine_VerificationLifecycle_MultipleMetrics(t *testing.T) { - jobAgentID := uuid.New().String() - deploymentID := uuid.New().String() - environmentID := uuid.New().String() - resourceID := uuid.New().String() - - ws := integration.NewTestWorkspace(t, - integration.WithJobAgent( - integration.JobAgentID(jobAgentID), - ), - integration.WithSystem( - integration.WithDeployment( - integration.DeploymentID(deploymentID), - integration.DeploymentJobAgent(jobAgentID), - integration.DeploymentCelResourceSelector("true"), - integration.WithDeploymentVersion( - integration.DeploymentVersionTag("v1.0.0"), - ), - ), - integration.WithEnvironment( - integration.EnvironmentID(environmentID), - integration.EnvironmentCelResourceSelector("true"), - ), - ), - integration.WithResource( - integration.ResourceID(resourceID), - ), - ) - - ctx := context.Background() - - agentJobs := ws.Workspace().Store().Jobs.GetJobsForAgent(jobAgentID) - require.Len(t, agentJobs, 1) - var job *oapi.Job - for _, j := range agentJobs { - job = j - } - - metricProvider := oapi.MetricProvider{} - _ = metricProvider.FromSleepMetricProvider(oapi.SleepMetricProvider{ - Type: oapi.Sleep, - DurationSeconds: 0, - }) - - metrics := []oapi.VerificationMetricSpec{ - { - Name: "health-check", - IntervalSeconds: 1, - Count: 1, - SuccessCondition: "result.ok == true", - Provider: metricProvider, - }, - { - Name: "latency-check", - IntervalSeconds: 1, - Count: 1, - SuccessCondition: "result.ok == true", - Provider: metricProvider, - }, - } - - err := ws.Workspace().ReleaseManager().VerificationManager().StartVerification(ctx, job, metrics) - require.NoError(t, err) - - time.Sleep(3 * time.Second) - - verifications := ws.Workspace().Store().JobVerifications.GetByJobId(job.Id) - require.Len(t, verifications, 1) - - v := verifications[0] - // Should have 2 metrics - assert.Len(t, v.Metrics, 2) -} diff --git a/apps/workspace-engine/test/e2e/engine_workspace_accessors_test.go b/apps/workspace-engine/test/e2e/engine_workspace_accessors_test.go index d60fcea75..0891e23cf 100644 --- a/apps/workspace-engine/test/e2e/engine_workspace_accessors_test.go +++ b/apps/workspace-engine/test/e2e/engine_workspace_accessors_test.go @@ -64,9 +64,6 @@ func TestEngine_WorkspaceAccessors(t *testing.T) { // Test ReleaseManager accessor assert.NotNil(t, ws.ReleaseManager()) - // Test VerificationManager accessor - assert.NotNil(t, ws.ReleaseManager().VerificationManager()) - // Test WorkflowManager accessor assert.NotNil(t, ws.WorkflowManager()) diff --git a/packages/db/drizzle/meta/0155_snapshot.json b/packages/db/drizzle/meta/0155_snapshot.json index 7dbbb6ab5..2f61910a6 100644 --- a/packages/db/drizzle/meta/0155_snapshot.json +++ b/packages/db/drizzle/meta/0155_snapshot.json @@ -112,12 +112,8 @@ "name": "account_user_id_user_id_fk", "tableFrom": "account", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -205,12 +201,8 @@ "name": "session_user_id_user_id_fk", "tableFrom": "session", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -220,9 +212,7 @@ "session_session_token_unique": { "name": "session_session_token_unique", "nullsNotDistinct": false, - "columns": [ - "session_token" - ] + "columns": ["session_token"] } }, "policies": {}, @@ -308,12 +298,8 @@ "name": "user_active_workspace_id_workspace_id_fk", "tableFrom": "user", "tableTo": "workspace", - "columnsFrom": [ - "active_workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["active_workspace_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -400,12 +386,8 @@ "name": "user_api_key_user_id_user_id_fk", "tableFrom": "user_api_key", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -525,12 +507,8 @@ "name": "changelog_entry_workspace_id_workspace_id_fk", "tableFrom": "changelog_entry", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -538,11 +516,7 @@ "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" - ] + "columns": ["workspace_id", "entity_type", "entity_id"] } }, "uniqueConstraints": {}, @@ -599,12 +573,8 @@ "name": "dashboard_workspace_id_workspace_id_fk", "tableFrom": "dashboard", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -683,12 +653,8 @@ "name": "dashboard_widget_dashboard_id_dashboard_id_fk", "tableFrom": "dashboard_widget", "tableTo": "dashboard", - "columnsFrom": [ - "dashboard_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["dashboard_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -1019,12 +985,8 @@ "name": "deployment_trace_span_workspace_id_workspace_id_fk", "tableFrom": "deployment_trace_span", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -1077,12 +1039,8 @@ "name": "deployment_variable_deployment_id_deployment_id_fk", "tableFrom": "deployment_variable", "tableTo": "deployment", - "columnsFrom": [ - "deployment_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["deployment_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -1092,10 +1050,7 @@ "deployment_variable_deployment_id_key_unique": { "name": "deployment_variable_deployment_id_key_unique", "nullsNotDistinct": false, - "columns": [ - "deployment_id", - "key" - ] + "columns": ["deployment_id", "key"] } }, "policies": {}, @@ -1145,12 +1100,8 @@ "name": "deployment_variable_value_deployment_variable_id_deployment_variable_id_fk", "tableFrom": "deployment_variable_value", "tableTo": "deployment_variable", - "columnsFrom": [ - "deployment_variable_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["deployment_variable_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -1282,12 +1233,8 @@ "name": "deployment_version_workspace_id_workspace_id_fk", "tableFrom": "deployment_version", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "no action", "onUpdate": "no action" } @@ -1334,12 +1281,8 @@ "name": "computed_deployment_resource_deployment_id_deployment_id_fk", "tableFrom": "computed_deployment_resource", "tableTo": "deployment", - "columnsFrom": [ - "deployment_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["deployment_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -1347,12 +1290,8 @@ "name": "computed_deployment_resource_resource_id_resource_id_fk", "tableFrom": "computed_deployment_resource", "tableTo": "resource", - "columnsFrom": [ - "resource_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["resource_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -1360,10 +1299,7 @@ "compositePrimaryKeys": { "computed_deployment_resource_deployment_id_resource_id_pk": { "name": "computed_deployment_resource_deployment_id_resource_id_pk", - "columns": [ - "deployment_id", - "resource_id" - ] + "columns": ["deployment_id", "resource_id"] } }, "uniqueConstraints": {}, @@ -1441,12 +1377,8 @@ "name": "deployment_workspace_id_workspace_id_fk", "tableFrom": "deployment", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "no action", "onUpdate": "no action" } @@ -1493,12 +1425,8 @@ "name": "computed_environment_resource_environment_id_environment_id_fk", "tableFrom": "computed_environment_resource", "tableTo": "environment", - "columnsFrom": [ - "environment_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["environment_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -1506,12 +1434,8 @@ "name": "computed_environment_resource_resource_id_resource_id_fk", "tableFrom": "computed_environment_resource", "tableTo": "resource", - "columnsFrom": [ - "resource_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["resource_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -1519,10 +1443,7 @@ "compositePrimaryKeys": { "computed_environment_resource_environment_id_resource_id_pk": { "name": "computed_environment_resource_environment_id_resource_id_pk", - "columns": [ - "environment_id", - "resource_id" - ] + "columns": ["environment_id", "resource_id"] } }, "uniqueConstraints": {}, @@ -1588,12 +1509,8 @@ "name": "environment_workspace_id_workspace_id_fk", "tableFrom": "environment", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "no action", "onUpdate": "no action" } @@ -1647,12 +1564,8 @@ "name": "event_workspace_id_workspace_id_fk", "tableFrom": "event", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -1772,12 +1685,8 @@ "name": "resource_provider_id_resource_provider_id_fk", "tableFrom": "resource", "tableTo": "resource_provider", - "columnsFrom": [ - "provider_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["provider_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" }, @@ -1785,12 +1694,8 @@ "name": "resource_workspace_id_workspace_id_fk", "tableFrom": "resource", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -1871,12 +1776,8 @@ "name": "resource_schema_workspace_id_workspace_id_fk", "tableFrom": "resource_schema", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "no action", "onUpdate": "no action" } @@ -1953,12 +1854,8 @@ "name": "resource_provider_workspace_id_workspace_id_fk", "tableFrom": "resource_provider", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -2013,12 +1910,8 @@ "name": "system_workspace_id_workspace_id_fk", "tableFrom": "system", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -2059,12 +1952,8 @@ "name": "system_deployment_system_id_system_id_fk", "tableFrom": "system_deployment", "tableTo": "system", - "columnsFrom": [ - "system_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["system_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -2072,12 +1961,8 @@ "name": "system_deployment_deployment_id_deployment_id_fk", "tableFrom": "system_deployment", "tableTo": "deployment", - "columnsFrom": [ - "deployment_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["deployment_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -2085,10 +1970,7 @@ "compositePrimaryKeys": { "system_deployment_system_id_deployment_id_pk": { "name": "system_deployment_system_id_deployment_id_pk", - "columns": [ - "system_id", - "deployment_id" - ] + "columns": ["system_id", "deployment_id"] } }, "uniqueConstraints": {}, @@ -2126,12 +2008,8 @@ "name": "system_environment_system_id_system_id_fk", "tableFrom": "system_environment", "tableTo": "system", - "columnsFrom": [ - "system_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["system_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -2139,12 +2017,8 @@ "name": "system_environment_environment_id_environment_id_fk", "tableFrom": "system_environment", "tableTo": "environment", - "columnsFrom": [ - "environment_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["environment_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -2152,10 +2026,7 @@ "compositePrimaryKeys": { "system_environment_system_id_environment_id_pk": { "name": "system_environment_system_id_environment_id_pk", - "columns": [ - "system_id", - "environment_id" - ] + "columns": ["system_id", "environment_id"] } }, "uniqueConstraints": {}, @@ -2193,12 +2064,8 @@ "name": "team_workspace_id_workspace_id_fk", "tableFrom": "team", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -2261,12 +2128,8 @@ "name": "team_member_team_id_team_id_fk", "tableFrom": "team_member", "tableTo": "team", - "columnsFrom": [ - "team_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["team_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -2274,12 +2137,8 @@ "name": "team_member_user_id_user_id_fk", "tableFrom": "team_member", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -2434,12 +2293,8 @@ "name": "job_job_agent_id_job_agent_id_fk", "tableFrom": "job", "tableTo": "job_agent", - "columnsFrom": [ - "job_agent_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["job_agent_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -2523,12 +2378,8 @@ "name": "job_metadata_job_id_job_id_fk", "tableFrom": "job_metadata", "tableTo": "job", - "columnsFrom": [ - "job_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["job_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -2604,12 +2455,8 @@ "name": "job_variable_job_id_job_id_fk", "tableFrom": "job_variable", "tableTo": "job", - "columnsFrom": [ - "job_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["job_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -2658,9 +2505,7 @@ "workspace_slug_unique": { "name": "workspace_slug_unique", "nullsNotDistinct": false, - "columns": [ - "slug" - ] + "columns": ["slug"] } }, "policies": {}, @@ -2751,12 +2596,8 @@ "name": "workspace_email_domain_matching_workspace_id_workspace_id_fk", "tableFrom": "workspace_email_domain_matching", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -2764,12 +2605,8 @@ "name": "workspace_email_domain_matching_role_id_role_id_fk", "tableFrom": "workspace_email_domain_matching", "tableTo": "role", - "columnsFrom": [ - "role_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["role_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -2829,12 +2666,8 @@ "name": "workspace_invite_token_role_id_role_id_fk", "tableFrom": "workspace_invite_token", "tableTo": "role", - "columnsFrom": [ - "role_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["role_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -2842,12 +2675,8 @@ "name": "workspace_invite_token_workspace_id_workspace_id_fk", "tableFrom": "workspace_invite_token", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -2855,12 +2684,8 @@ "name": "workspace_invite_token_created_by_user_id_fk", "tableFrom": "workspace_invite_token", "tableTo": "user", - "columnsFrom": [ - "created_by" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["created_by"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -2870,9 +2695,7 @@ "workspace_invite_token_token_unique": { "name": "workspace_invite_token_token_unique", "nullsNotDistinct": false, - "columns": [ - "token" - ] + "columns": ["token"] } }, "policies": {}, @@ -2969,12 +2792,8 @@ "name": "entity_role_role_id_role_id_fk", "tableFrom": "entity_role", "tableTo": "role", - "columnsFrom": [ - "role_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["role_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -3021,12 +2840,8 @@ "name": "role_workspace_id_workspace_id_fk", "tableFrom": "role", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -3089,12 +2904,8 @@ "name": "role_permission_role_id_role_id_fk", "tableFrom": "role_permission", "tableTo": "role", - "columnsFrom": [ - "role_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["role_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -3154,12 +2965,8 @@ "name": "release_resource_id_resource_id_fk", "tableFrom": "release", "tableTo": "resource", - "columnsFrom": [ - "resource_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["resource_id"], + "columnsTo": ["id"], "onDelete": "no action", "onUpdate": "no action" }, @@ -3167,12 +2974,8 @@ "name": "release_environment_id_environment_id_fk", "tableFrom": "release", "tableTo": "environment", - "columnsFrom": [ - "environment_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["environment_id"], + "columnsTo": ["id"], "onDelete": "no action", "onUpdate": "no action" }, @@ -3180,12 +2983,8 @@ "name": "release_deployment_id_deployment_id_fk", "tableFrom": "release", "tableTo": "deployment", - "columnsFrom": [ - "deployment_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["deployment_id"], + "columnsTo": ["id"], "onDelete": "no action", "onUpdate": "no action" }, @@ -3193,12 +2992,8 @@ "name": "release_version_id_deployment_version_id_fk", "tableFrom": "release", "tableTo": "deployment_version", - "columnsFrom": [ - "version_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["version_id"], + "columnsTo": ["id"], "onDelete": "no action", "onUpdate": "no action" } @@ -3281,12 +3076,8 @@ "name": "release_variable_release_id_release_id_fk", "tableFrom": "release_variable", "tableTo": "release", - "columnsFrom": [ - "release_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["release_id"], + "columnsTo": ["id"], "onDelete": "no action", "onUpdate": "no action" } @@ -3422,12 +3213,8 @@ "name": "reconcile_work_payload_scope_ref_reconcile_work_scope_id_fk", "tableFrom": "reconcile_work_payload", "tableTo": "reconcile_work_scope", - "columnsFrom": [ - "scope_ref" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["scope_ref"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -3685,12 +3472,8 @@ "name": "policy_workspace_id_workspace_id_fk", "tableFrom": "policy", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -3738,12 +3521,8 @@ "name": "policy_rule_any_approval_policy_id_policy_id_fk", "tableFrom": "policy_rule_any_approval", "tableTo": "policy", - "columnsFrom": [ - "policy_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["policy_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -3791,12 +3570,8 @@ "name": "policy_rule_deployment_dependency_policy_id_policy_id_fk", "tableFrom": "policy_rule_deployment_dependency", "tableTo": "policy", - "columnsFrom": [ - "policy_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["policy_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -3862,12 +3637,8 @@ "name": "policy_rule_deployment_window_policy_id_policy_id_fk", "tableFrom": "policy_rule_deployment_window", "tableTo": "policy", - "columnsFrom": [ - "policy_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["policy_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -3939,12 +3710,8 @@ "name": "policy_rule_environment_progression_policy_id_policy_id_fk", "tableFrom": "policy_rule_environment_progression", "tableTo": "policy", - "columnsFrom": [ - "policy_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["policy_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -3998,12 +3765,8 @@ "name": "policy_rule_gradual_rollout_policy_id_policy_id_fk", "tableFrom": "policy_rule_gradual_rollout", "tableTo": "policy", - "columnsFrom": [ - "policy_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["policy_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -4075,12 +3838,8 @@ "name": "policy_rule_retry_policy_id_policy_id_fk", "tableFrom": "policy_rule_retry", "tableTo": "policy", - "columnsFrom": [ - "policy_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["policy_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -4134,12 +3893,8 @@ "name": "policy_rule_rollback_policy_id_policy_id_fk", "tableFrom": "policy_rule_rollback", "tableTo": "policy", - "columnsFrom": [ - "policy_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["policy_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -4194,12 +3949,8 @@ "name": "policy_rule_verification_policy_id_policy_id_fk", "tableFrom": "policy_rule_verification", "tableTo": "policy", - "columnsFrom": [ - "policy_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["policy_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -4247,12 +3998,8 @@ "name": "policy_rule_version_cooldown_policy_id_policy_id_fk", "tableFrom": "policy_rule_version_cooldown", "tableTo": "policy", - "columnsFrom": [ - "policy_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["policy_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -4306,12 +4053,8 @@ "name": "policy_rule_version_selector_policy_id_policy_id_fk", "tableFrom": "policy_rule_version_selector", "tableTo": "policy", - "columnsFrom": [ - "policy_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["policy_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -4369,11 +4112,7 @@ "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" - ] + "columns": ["version_id", "user_id", "environment_id"] } }, "uniqueConstraints": {}, @@ -4410,12 +4149,8 @@ "name": "resource_variable_resource_id_resource_id_fk", "tableFrom": "resource_variable", "tableTo": "resource", - "columnsFrom": [ - "resource_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["resource_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -4423,10 +4158,7 @@ "compositePrimaryKeys": { "resource_variable_resource_id_key_pk": { "name": "resource_variable_resource_id_key_pk", - "columns": [ - "resource_id", - "key" - ] + "columns": ["resource_id", "key"] } }, "uniqueConstraints": {}, @@ -4478,12 +4210,8 @@ "name": "workflow_workspace_id_workspace_id_fk", "tableFrom": "workflow", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -4538,12 +4266,8 @@ "name": "workflow_job_workflow_run_id_workflow_run_id_fk", "tableFrom": "workflow_job", "tableTo": "workflow_run", - "columnsFrom": [ - "workflow_run_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workflow_run_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -4609,12 +4333,8 @@ "name": "workflow_job_template_workflow_id_workflow_id_fk", "tableFrom": "workflow_job_template", "tableTo": "workflow", - "columnsFrom": [ - "workflow_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -4656,12 +4376,8 @@ "name": "workflow_run_workflow_id_workflow_id_fk", "tableFrom": "workflow_run", "tableTo": "workflow", - "columnsFrom": [ - "workflow_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -4724,12 +4440,8 @@ "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" - ], + "columnsFrom": ["job_verification_metric_status_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -4907,12 +4619,8 @@ "name": "policy_rule_job_verification_metric_policy_id_policy_id_fk", "tableFrom": "policy_rule_job_verification_metric", "tableTo": "policy", - "columnsFrom": [ - "policy_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["policy_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -4988,12 +4696,8 @@ "name": "job_agent_workspace_id_workspace_id_fk", "tableFrom": "job_agent", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -5009,10 +4713,7 @@ "public.system_role": { "name": "system_role", "schema": "public", - "values": [ - "user", - "admin" - ] + "values": ["user", "admin"] }, "public.deployment_version_status": { "name": "deployment_version_status", @@ -5055,10 +4756,7 @@ "public.entity_type": { "name": "entity_type", "schema": "public", - "values": [ - "user", - "team" - ] + "values": ["user", "team"] }, "public.scope_type": { "name": "scope_type", @@ -5076,21 +4774,12 @@ "public.job_verification_status": { "name": "job_verification_status", "schema": "public", - "values": [ - "failed", - "inconclusive", - "passed" - ] + "values": ["failed", "inconclusive", "passed"] }, "public.job_verification_trigger_on": { "name": "job_verification_trigger_on", "schema": "public", - "values": [ - "jobCreated", - "jobStarted", - "jobSuccess", - "jobFailure" - ] + "values": ["jobCreated", "jobStarted", "jobSuccess", "jobFailure"] } }, "schemas": {}, @@ -5103,4 +4792,4 @@ "schemas": {}, "tables": {} } -} \ No newline at end of file +} diff --git a/packages/db/drizzle/meta/_journal.json b/packages/db/drizzle/meta/_journal.json index 058c03ccc..5e173e5be 100644 --- a/packages/db/drizzle/meta/_journal.json +++ b/packages/db/drizzle/meta/_journal.json @@ -1095,4 +1095,4 @@ "breakpoints": true } ] -} \ No newline at end of file +} diff --git a/packages/db/src/schema/job-verification-metric.ts b/packages/db/src/schema/job-verification-metric.ts index c7ad5e952..3c8a3eb1f 100644 --- a/packages/db/src/schema/job-verification-metric.ts +++ b/packages/db/src/schema/job-verification-metric.ts @@ -58,8 +58,7 @@ export const jobVerificationMetricStatus = pgTable("job_verification_metric", { .notNull() .defaultNow(), - jobId: uuid("job_id") - .notNull(), + jobId: uuid("job_id").notNull(), name: text("name").notNull(), provider: jsonb("provider").notNull(), diff --git a/packages/workspace-engine-sdk/src/schema.ts b/packages/workspace-engine-sdk/src/schema.ts index 8125544ae..a189574c5 100644 --- a/packages/workspace-engine-sdk/src/schema.ts +++ b/packages/workspace-engine-sdk/src/schema.ts @@ -4,5134 +4,5211 @@ */ export interface paths { - "/v1/validate/resource-selector": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** Validate a resource selector */ - post: operations["validateResourceSelector"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * List workspace IDs - * @description Returns a list of workspace that are in memory. These could be inactive. - */ - get: operations["listWorkspaceIds"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/deployment-variable-values/{valueId}": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get deployment variable value - * @description Returns a specific deployment variable value by ID. - */ - get: operations["getDeploymentVariableValue"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/deployment-variables/{variableId}": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get deployment variable - * @description Returns a specific deployment variable by ID. - */ - get: operations["getDeploymentVariable"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/deployment-versions/{versionId}/jobs-list": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get deployment version jobs list - * @description Returns jobs grouped by environment and release target for a deployment version. - */ - get: operations["getDeploymentVersionJobsList"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/deployments": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * List deployments - * @description Returns a paginated list of deployments for a workspace. - */ - get: operations["listDeployments"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/deployments/{deploymentId}": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get deployment - * @description Returns a specific deployment by ID. - */ - get: operations["getDeployment"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/deployments/{deploymentId}/policies": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get policies for a deployment - * @description Returns a list of resolved policies for a deployment. - */ - get: operations["getPoliciesForDeployment"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/deployments/{deploymentId}/release-targets": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get release targets for a deployment - * @description Returns a list of release targets for a deployment. - */ - get: operations["getReleaseTargetsForDeployment"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/deployments/{deploymentId}/resources": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get resources for a deployment - * @description Returns a paginated list of resources for deployment {deploymentId}. - */ - get: operations["getDeploymentResources"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/deployments/{deploymentId}/versions": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get versions for a deployment - * @description Returns a list of releases for a deployment. - */ - get: operations["getVersionsForDeployment"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/deploymentversions/{deploymentVersionId}": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get deployment version - * @description Returns a deployment version by ID. - */ - get: operations["getDeploymentVersion"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/entities/{relatableEntityType}/{entityId}/relations": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get related entities for a given entity - * @description Returns all entities related to the specified entity (deployment, environment, or resource) based on relationship rules. Relationships are grouped by relationship reference. - */ - get: operations["getRelatedEntities"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/environments": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * List environments - * @description Returns a list of environments for a workspace. - */ - get: operations["listEnvironments"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/environments/{environmentId}": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get environment - * @description Returns a specific environment by ID. - */ - get: operations["getEnvironment"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/environments/{environmentId}/release-targets": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get release targets for an environment - * @description Returns a list of release targets for an environment. - */ - get: operations["getReleaseTargetsForEnvironment"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/environments/{environmentId}/resources": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get resources for an environment - * @description Returns a paginated list of resources for environment {environmentId}. - */ - get: operations["getEnvironmentResources"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/github-entities/{installationId}": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get GitHub entity by installation ID - * @description Returns a GitHub entity by installation ID. - */ - get: operations["getGitHubEntityByInstallationId"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/job-agents": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get job agents - * @description Returns a list of job agents. - */ - get: operations["getJobAgents"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/job-agents/{jobAgentId}": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get job agent - * @description Returns a specific job agent by ID. - */ - get: operations["getJobAgent"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/job-agents/{jobAgentId}/deployments": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get deployments for a job agent - * @description Returns a list of deployments for a job agent. - */ - get: operations["getDeploymentsForJobAgent"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/job-agents/{jobAgentId}/jobs": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get jobs for a job agent - * @description Returns a list of jobs for a job agent. - */ - get: operations["getJobsForJobAgent"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/jobs": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * List jobs - * @description Returns a list of jobs. - */ - get: operations["getJobs"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/jobs/{jobId}": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get job - * @description Returns a specific job by ID. - */ - get: operations["getJob"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/jobs/{jobId}/with-release": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get job with release - * @description Returns a specific job by ID with its release. - */ - get: operations["getJobWithRelease"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/policies": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * List policies - * @description Returns a list of policies for workspace {workspaceId}. - */ - get: operations["listPolicies"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/policies/evaluate": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** - * Evaluate policies - * @description Evaluates all policies for a workspace. - */ - post: operations["evaluatePolicies"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/policies/{policyId}": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; + "/v1/validate/resource-selector": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Validate a resource selector */ + post: operations["validateResourceSelector"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List workspace IDs + * @description Returns a list of workspace that are in memory. These could be inactive. + */ + get: operations["listWorkspaceIds"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/deployment-variable-values/{valueId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get deployment variable value + * @description Returns a specific deployment variable value by ID. + */ + get: operations["getDeploymentVariableValue"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/deployment-variables/{variableId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get deployment variable + * @description Returns a specific deployment variable by ID. + */ + get: operations["getDeploymentVariable"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/deployment-versions/{versionId}/jobs-list": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get deployment version jobs list + * @description Returns jobs grouped by environment and release target for a deployment version. + */ + get: operations["getDeploymentVersionJobsList"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/deployments": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List deployments + * @description Returns a paginated list of deployments for a workspace. + */ + get: operations["listDeployments"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/deployments/{deploymentId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get deployment + * @description Returns a specific deployment by ID. + */ + get: operations["getDeployment"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/deployments/{deploymentId}/policies": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get policies for a deployment + * @description Returns a list of resolved policies for a deployment. + */ + get: operations["getPoliciesForDeployment"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/deployments/{deploymentId}/release-targets": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get release targets for a deployment + * @description Returns a list of release targets for a deployment. + */ + get: operations["getReleaseTargetsForDeployment"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/deployments/{deploymentId}/resources": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get resources for a deployment + * @description Returns a paginated list of resources for deployment {deploymentId}. + */ + get: operations["getDeploymentResources"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/deployments/{deploymentId}/versions": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get versions for a deployment + * @description Returns a list of releases for a deployment. + */ + get: operations["getVersionsForDeployment"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/deploymentversions/{deploymentVersionId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get deployment version + * @description Returns a deployment version by ID. + */ + get: operations["getDeploymentVersion"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/entities/{relatableEntityType}/{entityId}/relations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get related entities for a given entity + * @description Returns all entities related to the specified entity (deployment, environment, or resource) based on relationship rules. Relationships are grouped by relationship reference. + */ + get: operations["getRelatedEntities"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/environments": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List environments + * @description Returns a list of environments for a workspace. + */ + get: operations["listEnvironments"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/environments/{environmentId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get environment + * @description Returns a specific environment by ID. + */ + get: operations["getEnvironment"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/environments/{environmentId}/release-targets": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get release targets for an environment + * @description Returns a list of release targets for an environment. + */ + get: operations["getReleaseTargetsForEnvironment"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/environments/{environmentId}/resources": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get resources for an environment + * @description Returns a paginated list of resources for environment {environmentId}. + */ + get: operations["getEnvironmentResources"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/github-entities/{installationId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get GitHub entity by installation ID + * @description Returns a GitHub entity by installation ID. + */ + get: operations["getGitHubEntityByInstallationId"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/job-agents": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get job agents + * @description Returns a list of job agents. + */ + get: operations["getJobAgents"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/job-agents/{jobAgentId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get job agent + * @description Returns a specific job agent by ID. + */ + get: operations["getJobAgent"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/job-agents/{jobAgentId}/deployments": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get deployments for a job agent + * @description Returns a list of deployments for a job agent. + */ + get: operations["getDeploymentsForJobAgent"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/job-agents/{jobAgentId}/jobs": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get jobs for a job agent + * @description Returns a list of jobs for a job agent. + */ + get: operations["getJobsForJobAgent"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/jobs": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List jobs + * @description Returns a list of jobs. + */ + get: operations["getJobs"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/jobs/{jobId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get job + * @description Returns a specific job by ID. + */ + get: operations["getJob"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/jobs/{jobId}/with-release": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get job with release + * @description Returns a specific job by ID with its release. + */ + get: operations["getJobWithRelease"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/policies": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List policies + * @description Returns a list of policies for workspace {workspaceId}. + */ + get: operations["listPolicies"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/policies/evaluate": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Evaluate policies + * @description Evaluates all policies for a workspace. + */ + post: operations["evaluatePolicies"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/policies/{policyId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get policy + * @description Returns a specific policy by ID. + */ + get: operations["getPolicy"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/policies/{policyId}/release-targets": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get release targets for a policy + * @description Returns a list of release targets for a policy {policyId}. + */ + get: operations["getReleaseTargetsForPolicy"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/policies/{policyId}/rules/{ruleId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get rule + * @description Returns a specific rule by ID. + */ + get: operations["getRule"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/policy-skips": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List policy skips for a workspace + * @description Returns a list of policy skips for workspace {workspaceId}. + */ + get: operations["listPolicySkips"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/policy-skips/environment/{environmentId}/version/{deploymentVersionId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get policy skips for an environment and version + * @description Returns a list of policy skips for an environment and version. + */ + get: operations["getPolicySkipsForEnvironmentAndVersion"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/policy-skips/{policySkipId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get policy skip by ID + * @description Returns a specific policy skip by ID. + */ + get: operations["getPolicySkip"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/relationship-rules": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get relationship rules for a given workspace + * @description Returns all relationship rules for the specified workspace. + */ + get: operations["getRelationshipRules"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/relationship-rules/{relationshipRuleId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get relationship rule */ + get: operations["getRelationshipRule"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/release-targets/evaluate": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Evaluate policies for a release target + * @description Evaluates all policies and rules that apply to a given release target and returns the evaluation results. + */ + post: operations["evaluateReleaseTarget"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/release-targets/resource-preview": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Preview release targets for a resource + * @description Simulates which release targets would be created if the given resource were added to the workspace. This is a dry-run endpoint — no resources or release targets are actually created. + */ + post: operations["previewReleaseTargetsForResource"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/release-targets/state": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Get release target states by deployment and environment + * @description Returns paginated release target states for a given deployment and environment. + */ + post: operations["getReleaseTargetStates"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/release-targets/{releaseTargetKey}/desired-release": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get the desired release for a release target + * @description Returns the desired release for a release target {releaseTargetKey}. + */ + get: operations["getReleaseTargetDesiredRelease"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/release-targets/{releaseTargetKey}/jobs": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get jobs for a release target + * @description Returns a list of jobs for a release target {releaseTargetKey}. + */ + get: operations["getJobsForReleaseTarget"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/release-targets/{releaseTargetKey}/policies": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get policies for a release target + * @description Returns a list of policies for a release target {releaseTargetId}. + */ + get: operations["getPoliciesForReleaseTarget"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/release-targets/{releaseTargetKey}/state": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get the state for a release target + * @description Returns the state for a release target {releaseTargetKey}. + */ + get: operations["getReleaseTargetState"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/releases/{releaseId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get release + * @description Returns a specific release by ID. + */ + get: operations["getRelease"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/releases/{releaseId}/verifications": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get release verifications + * @description Returns all verifications for jobs belonging to this release. + */ + get: operations["getReleaseVerifications"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/resource-providers": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get all resource providers */ + get: operations["getResourceProviders"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/resource-providers/cache-batch": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Cache a large resource batch for deferred processing + * @description Stores resources in memory and returns a batch ID. The batch is processed when a corresponding Kafka event is received. Uses Ristretto cache with 5-minute TTL. + */ + post: operations["cacheBatch"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/resource-providers/name/{name}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get a resource provider by name */ + get: operations["getResourceProviderByName"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/resources/kinds": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get kinds for a workspace + * @description Returns a list of all resource kinds in a workspace. + */ + get: operations["getKindsForWorkspace"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/resources/query": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Query resources with CEL expression + * @description Returns paginated resources that match the provided CEL expression. Use the "resource" variable in your expression to access resource properties. + */ + post: operations["queryResources"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/resources/{resourceIdentifier}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get resource by identifier + * @description Returns a specific resource by its identifier. + */ + get: operations["getResourceByIdentifier"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/resources/{resourceIdentifier}/deployments": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get deployments for a resource + * @description Returns a paginated list of deployments that match the given resource. + */ + get: operations["getDeploymentsForResource"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/resources/{resourceIdentifier}/relationships": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get relationships for a resource + * @description Returns all relationships for the specified resource. + */ + get: operations["getRelationshipsForResource"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/resources/{resourceIdentifier}/release-targets": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get release targets for a resource + * @description Returns a list of release targets for a resource. + */ + get: operations["getReleaseTargetsForResource"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/resources/{resourceIdentifier}/release-targets/deployment/{deploymentId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get release target for a resource in a deployment + * @description Returns a release target for a resource in a deployment. + */ + get: operations["getReleaseTargetForResourceInDeployment"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/resources/{resourceIdentifier}/variables": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get variables for a resource + * @description Returns a list of variables for a resource + */ + get: operations["getVariablesForResource"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/status": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get engine status + * @description Returns the status of the engine. + */ + get: operations["getEngineStatus"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/systems": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List systems + * @description Returns a list of systems for a workspace. + */ + get: operations["listSystems"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/systems/{systemId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get system + * @description Returns a specific system by ID. + */ + get: operations["getSystem"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/systems/{systemId}/deployments/{deploymentId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get deployment system link + * @description Returns a specific deployment system link by ID. + */ + get: operations["getDeploymentSystemLink"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/systems/{systemId}/environments/{environmentId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get environment system link + * @description Returns a specific environment system link by ID. + */ + get: operations["getEnvironmentSystemLink"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/workflows": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List workflows + * @description Returns a list of workflows. + */ + get: operations["listWorkflows"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/workflows/{workflowId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get a workflow + * @description Gets a workflow by ID. + */ + get: operations["getWorkflow"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/workflows/{workflowId}/runs": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get all workflow runs for a workflow + * @description Gets all workflow runs for a workflow by ID. + */ + get: operations["getWorkflowRuns"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} +export type webhooks = Record; +export interface components { + schemas: { + AnyApprovalRule: { + /** Format: int32 */ + minApprovals: number; + }; + /** @enum {string} */ + ApprovalStatus: "approved" | "rejected"; + ArgoCDJobAgentConfig: { + /** @description ArgoCD API token. */ + apiKey: string; + /** @description ArgoCD server address (host[:port] or URL). */ + serverUrl: string; + /** @description ArgoCD application template. */ + template: string; + }; + BooleanValue: boolean; + CelMatcher: { + cel: string; + }; + CelSelector: { + cel: string; + }; + DatadogMetricProvider: { + /** + * @description Datadog aggregator + * @default last + * @enum {string} + */ + aggregator: + | "avg" + | "min" + | "max" + | "sum" + | "last" + | "percentile" + | "mean" + | "l2norm" + | "area"; + /** + * @description Datadog API key (supports Go templates for variable references) + * @example {{.variables.dd_api_key}} + */ + apiKey: string; + /** + * @description Datadog Application key (supports Go templates for variable references) + * @example {{.variables.dd_app_key}} + */ + appKey: string; + /** @description Datadog formula (supports Go templates) */ + formula?: string; + /** + * Format: int64 + * @example 30 + */ + intervalSeconds?: number; + /** + * @description Datadog metrics queries (supports Go templates) + * @example { + * "q": "sum:requests.error.rate{service:{{.resource.name}}}" + * } + */ + queries: { + [key: string]: string; + }; + /** + * @description Datadog site URL (e.g., datadoghq.com, datadoghq.eu, us3.datadoghq.com) + * @default datadoghq.com + */ + site: string; + /** + * @description Provider type (enum property replaced by openapi-typescript) + * @enum {string} + */ + type: "datadog"; + }; + DeployDecision: { + policyResults: components["schemas"]["PolicyEvaluation"][]; + }; + Deployment: { + description?: string; + id: string; + jobAgentConfig: components["schemas"]["JobAgentConfig"]; + jobAgentId?: string; + jobAgents?: components["schemas"]["DeploymentJobAgent"][]; + metadata: { + [key: string]: string; + }; + name: string; + resourceSelector?: components["schemas"]["Selector"]; + slug: string; + }; + DeploymentAndSystems: { + deployment: components["schemas"]["Deployment"]; + systems: components["schemas"]["System"][]; + }; + DeploymentDependencyRule: { + /** @description CEL expression to match upstream deployment(s) that must have a successful release before this deployment can proceed. */ + dependsOn: string; + }; + DeploymentJobAgent: { + config: components["schemas"]["JobAgentConfig"]; + ref: string; + /** @description CEL expression to determine if the job agent should be used */ + selector: string; + }; + DeploymentVariable: { + defaultValue?: components["schemas"]["LiteralValue"]; + deploymentId: string; + description?: string; + id: string; + key: string; + }; + DeploymentVariableValue: { + deploymentVariableId: string; + id: string; + /** Format: int64 */ + priority: number; + resourceSelector?: components["schemas"]["Selector"]; + value: components["schemas"]["Value"]; + }; + DeploymentVariableWithValues: { + values: components["schemas"]["DeploymentVariableValue"][]; + variable: components["schemas"]["DeploymentVariable"]; + }; + DeploymentVersion: { + config: { + [key: string]: unknown; + }; + /** Format: date-time */ + createdAt: string; + deploymentId: string; + id: string; + jobAgentConfig: components["schemas"]["JobAgentConfig"]; + message?: string; + metadata: { + [key: string]: string; + }; + name: string; + status: components["schemas"]["DeploymentVersionStatus"]; + tag: string; + }; + /** @enum {string} */ + DeploymentVersionStatus: + | "unspecified" + | "building" + | "ready" + | "failed" + | "rejected" + | "paused"; + DeploymentWindowRule: { + /** + * @description If true, deployments are only allowed during the window. If false, deployments are blocked during the window (deny window) + * @default true + */ + allowWindow: boolean; + /** + * Format: int32 + * @description Duration of each deployment window in minutes + */ + durationMinutes: number; + /** @description RFC 5545 recurrence rule defining when deployment windows start (e.g., FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR;BYHOUR=9) */ + rrule: string; + /** @description IANA timezone for the rrule (e.g., America/New_York). Defaults to UTC if not specified */ + timezone?: string; + }; + DeploymentWithVariablesAndSystems: { + deployment: components["schemas"]["Deployment"]; + systems: components["schemas"]["System"][]; + variables: components["schemas"]["DeploymentVariableWithValues"][]; + }; + DispatchContext: { + deployment?: components["schemas"]["Deployment"]; + environment?: components["schemas"]["Environment"]; + jobAgent: components["schemas"]["JobAgent"]; + jobAgentConfig: components["schemas"]["JobAgentConfig"]; + release?: components["schemas"]["Release"]; + resource?: components["schemas"]["Resource"]; + variables?: { + [key: string]: components["schemas"]["LiteralValue"]; + }; + version?: components["schemas"]["DeploymentVersion"]; + workflow?: components["schemas"]["Workflow"]; + workflowJob?: components["schemas"]["WorkflowJob"]; + workflowRun?: components["schemas"]["WorkflowRun"]; + }; + EntityRelation: { + direction: components["schemas"]["RelationDirection"]; + entity: components["schemas"]["RelatableEntity"]; + /** @description ID of the related entity */ + entityId: string; + entityType: components["schemas"]["RelatableEntityType"]; + rule: components["schemas"]["RelationshipRule"]; + }; + Environment: { + /** Format: date-time */ + createdAt: string; + description?: string; + id: string; + metadata: { + [key: string]: string; + }; + name: string; + resourceSelector?: components["schemas"]["Selector"]; + }; + EnvironmentProgressionRule: { + dependsOnEnvironmentSelector: components["schemas"]["Selector"]; + /** + * Format: int32 + * @description Maximum age of dependency deployment before blocking progression (prevents stale promotions) + */ + maximumAgeHours?: number; + /** + * Format: int32 + * @description Minimum time to wait after the depends on environment is in a success state before the current environment can be deployed + * @default 0 + */ + minimumSockTimeMinutes: number; + /** + * Format: float + * @default 100 + */ + minimumSuccessPercentage: number; + successStatuses?: components["schemas"]["JobStatus"][]; + }; + EnvironmentSummary: { + id: string; + name: string; + }; + EnvironmentWithSystems: components["schemas"]["Environment"] & { + systems: components["schemas"]["System"][]; + }; + ErrorResponse: { + /** @example Workspace not found */ + error?: string; + }; + EvaluateReleaseTargetRequest: { + releaseTarget: components["schemas"]["ReleaseTarget"]; + version: components["schemas"]["DeploymentVersion"]; + }; + EvaluationScope: { + environmentId?: string; + versionId?: string; + }; + GithubEntity: { + installationId: number; + slug: string; + }; + GithubJobAgentConfig: { + /** + * Format: int + * @description GitHub app installation ID. + */ + installationId: number; + /** @description GitHub repository owner. */ + owner: string; + /** @description Git ref to run the workflow on (defaults to "main" if omitted). */ + ref?: string; + /** @description GitHub repository name. */ + repo: string; + /** + * Format: int64 + * @description GitHub Actions workflow ID. + */ + workflowId: number; + }; + GradualRolloutRule: { + /** + * @description Strategy for scheduling deployments to release targets. "linear": Each target is deployed at a fixed interval of timeScaleInterval seconds. "linear-normalized": Deployments are spaced evenly so that the last target is scheduled at or before timeScaleInterval seconds. See rolloutType algorithm documentation for details. + * @enum {string} + */ + rolloutType: "linear" | "linear-normalized"; + /** + * Format: int32 + * @description Base time interval in seconds used to compute the delay between deployments to release targets. + */ + timeScaleInterval: number; + }; + HTTPMetricProvider: { + /** @description Request body (supports Go templates) */ + body?: string; + /** @description HTTP headers (values support Go templates) */ + headers?: { + [key: string]: string; + }; + /** + * @description HTTP method + * @default GET + * @enum {string} + */ + method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS"; + /** + * @description Request timeout (duration string, e.g., "30s") + * @default 30s + */ + timeout: string; + /** + * @description Provider type (enum property replaced by openapi-typescript) + * @enum {string} + */ + type: "http"; + /** + * @description HTTP endpoint URL (supports Go templates) + * @example http://{{ .resource.name }}.{{ .environment.name }}/health + */ + url: string; + }; + IntegerValue: number; + Job: { + /** Format: date-time */ + completedAt?: string; + /** Format: date-time */ + createdAt: string; + dispatchContext?: components["schemas"]["DispatchContext"]; + externalId?: string; + id: string; + jobAgentConfig: components["schemas"]["JobAgentConfig"]; + jobAgentId: string; + message?: string; + metadata: { + [key: string]: string; + }; + releaseId: string; + /** Format: date-time */ + startedAt?: string; + status: components["schemas"]["JobStatus"]; + traceToken?: string; + /** Format: date-time */ + updatedAt: string; + workflowJobId: string; + }; + JobAgent: { + config: components["schemas"]["JobAgentConfig"]; + id: string; + metadata?: { + [key: string]: string; + }; + name: string; + type: string; + workspaceId: string; + }; + JobAgentConfig: { + [key: string]: unknown; + }; + /** @enum {string} */ + JobStatus: + | "cancelled" + | "skipped" + | "inProgress" + | "actionRequired" + | "pending" + | "failure" + | "invalidJobAgent" + | "invalidIntegration" + | "externalRunNotFound" + | "successful"; + JobSummary: { + id: string; + /** @description External links extracted from job metadata */ + links?: { + [key: string]: string; + }; + message?: string; + status: components["schemas"]["JobStatus"]; + verifications: components["schemas"]["JobVerification"][]; + }; + JobUpdateEvent: { + agentId?: string; + externalId?: string; + fieldsToUpdate?: ( + | "completedAt" + | "createdAt" + | "dispatchContext" + | "externalId" + | "id" + | "jobAgentConfig" + | "jobAgentId" + | "message" + | "metadata" + | "releaseId" + | "startedAt" + | "status" + | "traceToken" + | "updatedAt" + | "workflowJobId" + )[]; + id?: string; + job: components["schemas"]["Job"]; + } & (unknown | unknown); + JobVerification: { + /** + * Format: date-time + * @description When verification was created + */ + createdAt: string; + id: string; + jobId: string; + /** @description Summary message of verification result */ + message?: string; + /** @description Metrics associated with this verification */ + metrics: components["schemas"]["VerificationMetricStatus"][]; + }; + /** @enum {string} */ + JobVerificationStatus: "running" | "passed" | "failed" | "cancelled"; + JobWithRelease: { + deployment?: components["schemas"]["Deployment"]; + environment?: components["schemas"]["Environment"]; + job: components["schemas"]["Job"]; + release: components["schemas"]["Release"]; + resource?: components["schemas"]["Resource"]; + }; + JobWithVerifications: { + job: components["schemas"]["Job"]; + verifications: components["schemas"]["JobVerification"][]; + }; + JsonSelector: { + json: { + [key: string]: unknown; + }; + }; + LiteralValue: + | components["schemas"]["BooleanValue"] + | components["schemas"]["NumberValue"] + | components["schemas"]["IntegerValue"] + | components["schemas"]["StringValue"] + | components["schemas"]["ObjectValue"] + | components["schemas"]["NullValue"]; + MetricProvider: + | components["schemas"]["HTTPMetricProvider"] + | components["schemas"]["SleepMetricProvider"] + | components["schemas"]["DatadogMetricProvider"] + | components["schemas"]["PrometheusMetricProvider"] + | components["schemas"]["TerraformCloudRunMetricProvider"]; + /** @enum {boolean} */ + NullValue: true; + NumberValue: number; + ObjectValue: { + object: { + [key: string]: unknown; + }; + }; + Policy: { + createdAt: string; + description?: string; + enabled: boolean; + id: string; + /** @description Arbitrary metadata for the policy (record) */ + metadata: { + [key: string]: string; + }; + name: string; + priority: number; + rules: components["schemas"]["PolicyRule"][]; + /** @description CEL expression for matching release targets. Use "true" to match all targets. */ + selector: string; + workspaceId: string; + }; + PolicyEvaluation: { + policy?: components["schemas"]["Policy"]; + ruleResults: components["schemas"]["RuleEvaluation"][]; + summary?: string; + }; + PolicyRule: { + anyApproval?: components["schemas"]["AnyApprovalRule"]; + createdAt: string; + deploymentDependency?: components["schemas"]["DeploymentDependencyRule"]; + deploymentWindow?: components["schemas"]["DeploymentWindowRule"]; + environmentProgression?: components["schemas"]["EnvironmentProgressionRule"]; + gradualRollout?: components["schemas"]["GradualRolloutRule"]; + id: string; + policyId: string; + retry?: components["schemas"]["RetryRule"]; + rollback?: components["schemas"]["RollbackRule"]; + verification?: components["schemas"]["VerificationRule"]; + versionCooldown?: components["schemas"]["VersionCooldownRule"]; + versionSelector?: components["schemas"]["VersionSelectorRule"]; + }; + PolicySkip: { + /** + * Format: date-time + * @description When this skip was created + */ + createdAt: string; + /** @description User ID who created this skip */ + createdBy: string; + /** @description Environment this skip applies to. If null, applies to all environments. */ + environmentId?: string; + /** + * Format: date-time + * @description When this skip expires. If null, skip never expires. + */ + expiresAt?: string; + /** @description Unique identifier for the skip */ + id: string; + /** @description Required reason for why this skip is needed (e.g., incident ticket, emergency situation) */ + reason: string; + /** @description Resource this skip applies to. If null, applies to all resources (in the environment if specified, or globally). */ + resourceId?: string; + /** @description Rule ID this skip applies to */ + ruleId: string; + /** @description Deployment version this skip applies to */ + versionId: string; + /** @description Workspace this skip belongs to */ + workspaceId: string; + }; + PrometheusMetricProvider: { + /** + * @description Prometheus server address (supports Go templates) + * @example http://prometheus.example.com:9090 + */ + address: string; + /** @description Authentication configuration for Prometheus */ + authentication?: { /** - * Get policy - * @description Returns a specific policy by ID. + * @description Bearer token for authentication (supports Go templates for variable references) + * @example {{.variables.prometheus_token}} */ - get: operations["getPolicy"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/policies/{policyId}/release-targets": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; + bearerToken?: string; + /** @description OAuth2 client credentials flow */ + oauth2?: { + /** @description OAuth2 client ID (supports Go templates) */ + clientId: string; + /** @description OAuth2 client secret (supports Go templates) */ + clientSecret: string; + /** @description OAuth2 scopes */ + scopes?: string[]; + /** @description Token endpoint URL */ + tokenUrl: string; + }; + }; + /** @description Additional HTTP headers for the Prometheus request (values support Go templates) */ + headers?: { + /** @example X-Scope-OrgID */ + key: string; + /** @example tenant_a */ + value: string; + }[]; + /** + * @description Skip TLS certificate verification + * @default false + */ + insecure: boolean; + /** + * @description PromQL query expression (supports Go templates) + * @example sum(irate(istio_requests_total{reporter="source",destination_service=~"{{.resource.name}}",response_code!~"5.*"}[5m])) + */ + query: string; + /** @description If provided, a range query (/api/v1/query_range) is used instead of an instant query (/api/v1/query) */ + rangeQuery?: { /** - * Get release targets for a policy - * @description Returns a list of release targets for a policy {policyId}. + * @description How far back from now for the query end, as a Prometheus duration (e.g., "0s" for now, "1m" for 1 minute ago). Defaults to "0s" (now) if unset. + * @example 0s */ - get: operations["getReleaseTargetsForPolicy"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/policies/{policyId}/rules/{ruleId}": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; + end?: string; /** - * Get rule - * @description Returns a specific rule by ID. + * @description How far back from now to start the query, as a Prometheus duration (e.g., "5m", "1h"). Defaults to 10 * step if unset. + * @example 5m */ - get: operations["getRule"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/policy-skips": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; + start?: string; /** - * List policy skips for a workspace - * @description Returns a list of policy skips for workspace {workspaceId}. + * @description Query resolution step width as a Prometheus duration (e.g., "15s", "1m", "500ms") + * @example 1m */ - get: operations["listPolicySkips"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/policy-skips/environment/{environmentId}/version/{deploymentVersionId}": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; + step: string; + }; + /** + * Format: int64 + * @description Query timeout in seconds + * @example 30 + */ + timeout?: number; + /** + * @description Provider type (enum property replaced by openapi-typescript) + * @enum {string} + */ + type: "prometheus"; + }; + PropertiesMatcher: { + properties: components["schemas"]["PropertyMatcher"][]; + }; + PropertyMatcher: { + fromProperty: string[]; + /** @enum {string} */ + operator: + | "equals" + | "notEquals" + | "contains" + | "startsWith" + | "endsWith" + | "regex"; + toProperty: string[]; + }; + ReferenceValue: { + path: string[]; + reference: string; + }; + RelatableEntity: + | components["schemas"]["Deployment"] + | components["schemas"]["Environment"] + | components["schemas"]["Resource"]; + /** @enum {string} */ + RelatableEntityType: "deployment" | "environment" | "resource"; + /** @enum {string} */ + RelationDirection: "from" | "to"; + RelationshipRule: { + description?: string; + fromSelector?: components["schemas"]["Selector"]; + fromType: components["schemas"]["RelatableEntityType"]; + id: string; + matcher: + | components["schemas"]["CelMatcher"] + | components["schemas"]["PropertiesMatcher"]; + metadata: { + [key: string]: string; + }; + name: string; + reference: string; + relationshipType: string; + toSelector?: components["schemas"]["Selector"]; + toType: components["schemas"]["RelatableEntityType"]; + workspaceId: string; + }; + Release: { + createdAt: string; + encryptedVariables: string[]; + releaseTarget: components["schemas"]["ReleaseTarget"]; + variables: { + [key: string]: components["schemas"]["LiteralValue"]; + }; + version: components["schemas"]["DeploymentVersion"]; + }; + ReleaseTarget: { + deploymentId: string; + environmentId: string; + resourceId: string; + }; + ReleaseTargetAndState: { + releaseTarget: components["schemas"]["ReleaseTarget"]; + state: components["schemas"]["ReleaseTargetState"]; + }; + ReleaseTargetPreview: { + deployment: components["schemas"]["Deployment"]; + environment: components["schemas"]["Environment"]; + system: components["schemas"]["System"]; + }; + ReleaseTargetState: { + currentRelease?: components["schemas"]["Release"]; + desiredRelease?: components["schemas"]["Release"]; + latestJob?: components["schemas"]["JobWithVerifications"]; + }; + ReleaseTargetSummary: { + currentVersion?: components["schemas"]["VersionSummary"]; + desiredVersion?: components["schemas"]["VersionSummary"]; + environment: components["schemas"]["EnvironmentSummary"]; + latestJob?: components["schemas"]["JobSummary"]; + releaseTarget: components["schemas"]["ReleaseTarget"]; + resource: components["schemas"]["ResourceSummary"]; + }; + ReleaseTargetWithState: { + deployment: components["schemas"]["Deployment"]; + environment: components["schemas"]["Environment"]; + releaseTarget: components["schemas"]["ReleaseTarget"]; + resource: components["schemas"]["Resource"]; + state: components["schemas"]["ReleaseTargetState"]; + }; + ResolvedPolicy: { + environmentIds: string[]; + policy: components["schemas"]["Policy"]; + releaseTargets: components["schemas"]["ReleaseTarget"][]; + }; + Resource: { + config: { + [key: string]: unknown; + }; + /** Format: date-time */ + createdAt: string; + /** Format: date-time */ + deletedAt?: string; + id: string; + identifier: string; + kind: string; + /** Format: date-time */ + lockedAt?: string; + metadata: { + [key: string]: string; + }; + name: string; + providerId?: string; + /** Format: date-time */ + updatedAt?: string; + version: string; + workspaceId: string; + }; + ResourcePreviewRequest: { + config: { + [key: string]: unknown; + }; + identifier: string; + kind: string; + metadata: { + [key: string]: string; + }; + name: string; + version: string; + }; + ResourceProvider: { + /** Format: date-time */ + createdAt: string; + id: string; + metadata: { + [key: string]: string; + }; + name: string; + /** Format: uuid */ + workspaceId: string; + }; + ResourceSummary: { + id: string; + identifier: string; + kind: string; + name: string; + version: string; + }; + ResourceVariable: { + key: string; + resourceId: string; + value: components["schemas"]["Value"]; + }; + ResourceVariablesBulkUpdateEvent: { + resourceId: string; + variables: { + [key: string]: unknown; + }; + }; + RetryRule: { + /** + * Format: int32 + * @description Minimum seconds to wait between retry attempts. If null, retries are allowed immediately after job completion. + */ + backoffSeconds?: number; + /** + * @description Backoff strategy: "linear" uses constant backoffSeconds delay, "exponential" doubles the delay with each retry (backoffSeconds * 2^(attempt-1)). + * @default linear + * @enum {string} + */ + backoffStrategy: "linear" | "exponential"; + /** + * Format: int32 + * @description Maximum backoff time in seconds (cap for exponential backoff). If null, no maximum is enforced. + */ + maxBackoffSeconds?: number; + /** + * Format: int32 + * @description Maximum number of retries allowed. 0 means no retries (1 attempt total), 3 means up to 4 attempts (1 initial + 3 retries). + */ + maxRetries: number; + /** @description Job statuses that count toward the retry limit. If null or empty, defaults to ["failure", "invalidIntegration", "invalidJobAgent"] for maxRetries > 0, or ["failure", "invalidIntegration", "invalidJobAgent", "successful"] for maxRetries = 0. Cancelled and skipped jobs never count by default (allows redeployment after cancellation). Example: ["failure", "cancelled"] will only count failed/cancelled jobs. */ + retryOnStatuses?: components["schemas"]["JobStatus"][]; + }; + RollbackRule: { + /** @description Job statuses that will trigger a rollback */ + onJobStatuses?: components["schemas"]["JobStatus"][]; + /** + * @description If true, a release target will be rolled back if the verification fails + * @default false + */ + onVerificationFailure: boolean; + }; + RuleEvaluation: { + /** @description Whether the rule requires an action (e.g., approval, wait) */ + actionRequired: boolean; + /** + * @description Type of action required + * @enum {string} + */ + actionType?: "approval" | "wait"; + /** @description Whether the rule allows the deployment */ + allowed: boolean; + /** @description Additional details about the rule evaluation */ + details: { + [key: string]: unknown; + }; + /** @description Human-readable explanation of the rule result */ + message: string; + /** + * Format: date-time + * @description The time when this rule should be re-evaluated (e.g., when soak time will be complete, when gradual rollout schedule is due) + */ + nextEvaluationTime?: string; + /** @description The ID of the rule that was evaluated */ + ruleId: string; + /** + * Format: date-time + * @description The time when the rule requirement was satisfied (e.g., when approvals were met, soak time completed) + */ + satisfiedAt?: string; + }; + Selector: + | components["schemas"]["JsonSelector"] + | components["schemas"]["CelSelector"]; + SensitiveValue: { + valueHash: string; + }; + SleepMetricProvider: { + /** + * Format: int32 + * @example 30 + */ + durationSeconds: number; + /** + * @description Provider type (enum property replaced by openapi-typescript) + * @enum {string} + */ + type: "sleep"; + }; + StringValue: string; + System: { + description?: string; + id: string; + metadata?: { + [key: string]: string; + }; + name: string; + workspaceId: string; + }; + SystemDeploymentLink: { + deploymentId: string; + systemId: string; + }; + SystemEnvironmentLink: { + environmentId: string; + systemId: string; + }; + TerraformCloudJobAgentConfig: { + /** @description Terraform Cloud address (e.g. https://app.terraform.io). */ + address: string; + /** @description Terraform Cloud organization name. */ + organization: string; + /** @description Terraform Cloud workspace template. */ + template: string; + /** @description Terraform Cloud API token. */ + token: string; + }; + TerraformCloudRunMetricProvider: { + /** + * @description Terraform Cloud address + * @example https://app.terraform.io + */ + address: string; + /** + * @description Terraform Cloud run ID + * @example run-1234567890 + */ + runId: string; + /** + * @description Terraform Cloud token + * @example {{.variables.terraform_cloud_token}} + */ + token: string; + /** + * @description Provider type (enum property replaced by openapi-typescript) + * @enum {string} + */ + type: "terraformCloudRun"; + }; + TestRunnerJobAgentConfig: { + /** + * Format: int + * @description Delay in seconds before resolving the job. + */ + delaySeconds?: number; + /** @description Optional message to include in the job output. */ + message?: string; + /** @description Final status to set (e.g. "successful", "failure"). */ + status?: string; + }; + UserApprovalRecord: { + createdAt: string; + environmentId: string; + reason?: string; + status: components["schemas"]["ApprovalStatus"]; + userId: string; + versionId: string; + }; + Value: + | components["schemas"]["LiteralValue"] + | components["schemas"]["ReferenceValue"] + | components["schemas"]["SensitiveValue"]; + VerificationMeasurement: { + /** @description Raw measurement data */ + data?: { + [key: string]: unknown; + }; + /** + * Format: date-time + * @description When measurement was taken + */ + measuredAt: string; + /** @description Measurement result message */ + message?: string; + status: components["schemas"]["VerificationMeasurementStatus"]; + }; + /** + * @description Status of a verification measurement + * @enum {string} + */ + VerificationMeasurementStatus: "passed" | "failed" | "inconclusive"; + VerificationMetricSpec: { + /** @description Number of measurements to take */ + count: number; + /** + * @description CEL expression to evaluate measurement failure (e.g., "result.statusCode == 500"), if not provided, a failure is just the opposite of the success condition + * @example result.statusCode == 500 + */ + failureCondition?: string; + /** + * @description Stop after this many consecutive failures (0 = no limit) + * @default 0 + */ + failureThreshold: number; + /** + * Format: int32 + * @description Interval between measurements in seconds + * @example 30 + */ + intervalSeconds: number; + /** @description Name of the verification metric */ + name: string; + provider: components["schemas"]["MetricProvider"]; + /** + * @description CEL expression to evaluate measurement success (e.g., "result.statusCode == 200") + * @example result.statusCode == 200 + */ + successCondition: string; + /** + * @description Minimum number of consecutive successful measurements required to consider the metric successful + * @example 0 + */ + successThreshold?: number; + }; + VerificationMetricStatus: components["schemas"]["VerificationMetricSpec"] & { + /** @description Individual verification measurements taken for this metric */ + measurements: components["schemas"]["VerificationMeasurement"][]; + }; + VerificationRule: { + /** @description Metrics to verify */ + metrics: components["schemas"]["VerificationMetricSpec"][]; + /** + * @description When to trigger verification + * @default jobSuccess + * @enum {string} + */ + triggerOn: "jobCreated" | "jobStarted" | "jobSuccess" | "jobFailure"; + }; + VersionCooldownRule: { + /** + * Format: int32 + * @description Minimum time in seconds that must pass since the currently deployed (or in-progress) version was created before allowing another deployment. This enables batching of frequent upstream releases into periodic deployments. + */ + intervalSeconds: number; + }; + VersionSelectorRule: { + /** @description Human-readable description of what this version selector does. Example: "Only deploy v2.x versions to staging environments" */ + description?: string; + selector: components["schemas"]["Selector"]; + }; + VersionSummary: { + id: string; + name: string; + tag: string; + }; + Workflow: { + id: string; + inputs: components["schemas"]["WorkflowInput"][]; + jobs: components["schemas"]["WorkflowJobTemplate"][]; + name: string; + }; + WorkflowArrayInput: + | components["schemas"]["WorkflowManualArrayInput"] + | components["schemas"]["WorkflowSelectorArrayInput"]; + WorkflowBooleanInput: { + default?: boolean; + key: string; + /** @enum {string} */ + type: "boolean"; + }; + WorkflowInput: + | components["schemas"]["WorkflowStringInput"] + | components["schemas"]["WorkflowNumberInput"] + | components["schemas"]["WorkflowBooleanInput"] + | components["schemas"]["WorkflowArrayInput"] + | components["schemas"]["WorkflowObjectInput"]; + WorkflowJob: { + /** @description Configuration for the job agent */ + config: { + [key: string]: unknown; + }; + id: string; + index: number; + /** @description Reference to the job agent */ + ref: string; + workflowRunId: string; + }; + WorkflowJobAgentConfig: { + config: { + [key: string]: unknown; + }; + id: string; + }; + WorkflowJobMatrix: { + [key: string]: + | { + [key: string]: unknown; + }[] + | string; + }; + WorkflowJobTemplate: { + /** @description Configuration for the job agent */ + config: { + [key: string]: unknown; + }; + id: string; + /** @description CEL expression to determine if the job should run */ + if?: string; + matrix?: components["schemas"]["WorkflowJobMatrix"]; + name: string; + /** @description Reference to the job agent */ + ref: string; + workflowId: string; + }; + WorkflowJobWithJobs: components["schemas"]["WorkflowJob"] & { + jobs: components["schemas"]["Job"][]; + }; + WorkflowManualArrayInput: { + default?: { + [key: string]: unknown; + }[]; + key: string; + /** @enum {string} */ + type: "array"; + }; + WorkflowNumberInput: { + default?: number; + key: string; + /** @enum {string} */ + type: "number"; + }; + WorkflowObjectInput: { + default?: { + [key: string]: unknown; + }; + key: string; + /** @enum {string} */ + type: "object"; + }; + WorkflowRun: { + id: string; + inputs: { + [key: string]: unknown; + }; + workflowId: string; + }; + WorkflowRunWithJobs: components["schemas"]["WorkflowRun"] & { + jobs: components["schemas"]["WorkflowJobWithJobs"][]; + }; + WorkflowSelectorArrayInput: { + key: string; + selector: { + default?: components["schemas"]["Selector"]; + /** @enum {string} */ + entityType: "resource" | "environment" | "deployment"; + }; + /** @enum {string} */ + type: "array"; + }; + WorkflowStringInput: { + default?: string; + key: string; + /** @enum {string} */ + type: "string"; + }; + }; + responses: never; + parameters: { + /** @description Type of the entity (deployment, environment, or resource) */ + relatableEntityType: components["schemas"]["RelatableEntityType"]; + }; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record; +export interface operations { + validateResourceSelector: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": { + resourceSelector?: components["schemas"]["Selector"]; + }; + }; + }; + responses: { + /** @description The validated resource selector */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + errors: string[]; + valid: boolean; + }; + }; + }; + }; + }; + listWorkspaceIds: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description A list of workspace IDs */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + workspaceIds?: string[]; + }; + }; + }; + }; + }; + getDeploymentVariableValue: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description ID of the deployment variable value */ + valueId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The requested deployment variable value */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DeploymentVariableValue"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getDeploymentVariable: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description ID of the deployment variable */ + variableId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The requested deployment variable */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DeploymentVariableWithValues"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getDeploymentVersionJobsList: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description ID of the deployment version */ + versionId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Jobs list grouped by environment and release target */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + environment: components["schemas"]["Environment"]; + releaseTargets: { + deployment: components["schemas"]["Deployment"]; + deploymentId: string; + environment: components["schemas"]["Environment"]; + environmentId: string; + id: string; + jobs: { + /** Format: date-time */ + createdAt: string; + externalId?: string; + id: string; + metadata: { + [key: string]: string; + }; + status: components["schemas"]["JobStatus"]; + }[]; + resource: components["schemas"]["Resource"]; + resourceId: string; + }[]; + }[]; }; - /** - * Get policy skips for an environment and version - * @description Returns a list of policy skips for an environment and version. - */ - get: operations["getPolicySkipsForEnvironmentAndVersion"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/policy-skips/{policySkipId}": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; }; - /** - * Get policy skip by ID - * @description Returns a specific policy skip by ID. - */ - get: operations["getPolicySkip"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/relationship-rules": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; + content: { + "application/json": components["schemas"]["ErrorResponse"]; }; - /** - * Get relationship rules for a given workspace - * @description Returns all relationship rules for the specified workspace. - */ - get: operations["getRelationshipRules"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/relationship-rules/{relationshipRuleId}": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** Get relationship rule */ - get: operations["getRelationshipRule"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/release-targets/evaluate": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** - * Evaluate policies for a release target - * @description Evaluates all policies and rules that apply to a given release target and returns the evaluation results. - */ - post: operations["evaluateReleaseTarget"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/release-targets/resource-preview": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** - * Preview release targets for a resource - * @description Simulates which release targets would be created if the given resource were added to the workspace. This is a dry-run endpoint — no resources or release targets are actually created. - */ - post: operations["previewReleaseTargetsForResource"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/release-targets/state": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** - * Get release target states by deployment and environment - * @description Returns paginated release target states for a given deployment and environment. - */ - post: operations["getReleaseTargetStates"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/release-targets/{releaseTargetKey}/desired-release": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; }; - /** - * Get the desired release for a release target - * @description Returns the desired release for a release target {releaseTargetKey}. - */ - get: operations["getReleaseTargetDesiredRelease"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/release-targets/{releaseTargetKey}/jobs": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; + content: { + "application/json": components["schemas"]["ErrorResponse"]; }; - /** - * Get jobs for a release target - * @description Returns a list of jobs for a release target {releaseTargetKey}. - */ - get: operations["getJobsForReleaseTarget"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/release-targets/{releaseTargetKey}/policies": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; + }; + }; + }; + listDeployments: { + parameters: { + query?: { + /** @description Maximum number of items to return */ + limit?: number; + /** @description Number of items to skip */ + offset?: number; + }; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Paginated list of items */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["DeploymentAndSystems"][]; + /** @description Maximum number of items returned */ + limit: number; + /** @description Number of items skipped */ + offset: number; + /** @description Total number of items available */ + total: number; + }; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getDeployment: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description ID of the deployment */ + deploymentId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The requested deployment */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DeploymentWithVariablesAndSystems"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getPoliciesForDeployment: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description ID of the deployment */ + deploymentId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description A list of resolved policies */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["ResolvedPolicy"][]; + }; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getReleaseTargetsForDeployment: { + parameters: { + query?: { + /** @description Maximum number of items to return */ + limit?: number; + /** @description Number of items to skip */ + offset?: number; + /** @description Filter by resource name */ + query?: string; + }; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description ID of the deployment */ + deploymentId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Paginated list of items */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["ReleaseTargetSummary"][]; + /** @description Maximum number of items returned */ + limit: number; + /** @description Number of items skipped */ + offset: number; + /** @description Total number of items available */ + total: number; + }; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getDeploymentResources: { + parameters: { + query?: { + /** @description Maximum number of items to return */ + limit?: number; + /** @description Number of items to skip */ + offset?: number; + }; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description ID of the deployment */ + deploymentId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Paginated list of items */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["Resource"][]; + /** @description Maximum number of items returned */ + limit: number; + /** @description Number of items skipped */ + offset: number; + /** @description Total number of items available */ + total: number; + }; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getVersionsForDeployment: { + parameters: { + query?: { + /** @description Maximum number of items to return */ + limit?: number; + /** @description Number of items to skip */ + offset?: number; + }; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description ID of the deployment */ + deploymentId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Paginated list of items */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["DeploymentVersion"][]; + /** @description Maximum number of items returned */ + limit: number; + /** @description Number of items skipped */ + offset: number; + /** @description Total number of items available */ + total: number; + }; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getDeploymentVersion: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description ID of the deployment version */ + deploymentVersionId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DeploymentVersion"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getRelatedEntities: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description Type of the entity (deployment, environment, or resource) */ + relatableEntityType: components["parameters"]["relatableEntityType"]; + /** @description ID of the entity */ + entityId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Related entities grouped by relationship reference */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + relations?: { + [key: string]: components["schemas"]["EntityRelation"][]; + }; + }; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + listEnvironments: { + parameters: { + query?: { + /** @description Number of items to skip */ + offset?: number; + /** @description Maximum number of items to return */ + limit?: number; + }; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description A list of environments */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["Environment"][]; + /** @description Maximum number of items returned */ + limit: number; + /** @description Number of items skipped */ + offset: number; + /** @description Total number of items available */ + total: number; + }; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getEnvironment: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description ID of the environment */ + environmentId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The requested environment */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["EnvironmentWithSystems"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getReleaseTargetsForEnvironment: { + parameters: { + query?: { + /** @description Maximum number of items to return */ + limit?: number; + /** @description Number of items to skip */ + offset?: number; + }; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description ID of the environment */ + environmentId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Paginated list of items */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["ReleaseTargetWithState"][]; + /** @description Maximum number of items returned */ + limit: number; + /** @description Number of items skipped */ + offset: number; + /** @description Total number of items available */ + total: number; + }; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getEnvironmentResources: { + parameters: { + query?: { + /** @description Maximum number of items to return */ + limit?: number; + /** @description Number of items to skip */ + offset?: number; + }; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description ID of the environment */ + environmentId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Paginated list of items */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["Resource"][]; + /** @description Maximum number of items returned */ + limit: number; + /** @description Number of items skipped */ + offset: number; + /** @description Total number of items available */ + total: number; + }; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getGitHubEntityByInstallationId: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description Installation ID of the GitHub entity */ + installationId: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GithubEntity"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getJobAgents: { + parameters: { + query?: { + /** @description Maximum number of items to return */ + limit?: number; + /** @description Number of items to skip */ + offset?: number; + }; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Paginated list of items */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["JobAgent"][]; + /** @description Maximum number of items returned */ + limit: number; + /** @description Number of items skipped */ + offset: number; + /** @description Total number of items available */ + total: number; + }; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getJobAgent: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description ID of the job agent */ + jobAgentId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The requested job agent */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["JobAgent"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getDeploymentsForJobAgent: { + parameters: { + query?: { + /** @description Maximum number of items to return */ + limit?: number; + /** @description Number of items to skip */ + offset?: number; + }; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description ID of the job agent */ + jobAgentId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Paginated list of items */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["Deployment"][]; + /** @description Maximum number of items returned */ + limit: number; + /** @description Number of items skipped */ + offset: number; + /** @description Total number of items available */ + total: number; + }; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getJobsForJobAgent: { + parameters: { + query?: { + /** @description Maximum number of items to return */ + limit?: number; + /** @description Number of items to skip */ + offset?: number; + }; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description ID of the job agent */ + jobAgentId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Paginated list of items */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["Job"][]; + /** @description Maximum number of items returned */ + limit: number; + /** @description Number of items skipped */ + offset: number; + /** @description Total number of items available */ + total: number; + }; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getJobs: { + parameters: { + query?: { + /** @description Maximum number of items to return */ + limit?: number; + /** @description Number of items to skip */ + offset?: number; + /** @description ID of the resource */ + resourceId?: string; + /** @description ID of the environment */ + environmentId?: string; + /** @description ID of the deployment */ + deploymentId?: string; + }; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Paginated list of items */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["Job"][]; + /** @description Maximum number of items returned */ + limit: number; + /** @description Number of items skipped */ + offset: number; + /** @description Total number of items available */ + total: number; + }; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getJob: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description ID of the job */ + jobId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Get job */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Job"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getJobWithRelease: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description ID of the job */ + jobId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Get job with release */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["JobWithRelease"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + listPolicies: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description A list of policies */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + policies?: components["schemas"]["Policy"][]; + }; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + evaluatePolicies: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["EvaluationScope"]; + }; + }; + responses: { + /** @description OK response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + decision?: components["schemas"]["DeployDecision"]; + }; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getPolicy: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description ID of the policy */ + policyId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The requested policy */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Policy"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getReleaseTargetsForPolicy: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description ID of the policy */ + policyId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description A list of release targets */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + releaseTargets?: components["schemas"]["ReleaseTarget"][]; + }; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getRule: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description ID of the policy */ + policyId: string; + /** @description ID of the rule */ + ruleId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PolicyRule"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + listPolicySkips: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description A list of policy skips */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + skips?: components["schemas"]["PolicySkip"][]; + }; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getPolicySkipsForEnvironmentAndVersion: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description ID of the environment */ + environmentId: string; + /** @description ID of the deployment version */ + deploymentVersionId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description A list of policy skips */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items?: components["schemas"]["PolicySkip"][]; + }; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getPolicySkip: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description Policy skip ID */ + policySkipId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The requested policy skip */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PolicySkip"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getRelationshipRules: { + parameters: { + query?: { + /** @description Number of items to skip */ + offset?: number; + /** @description Maximum number of items to return */ + limit?: number; + }; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Paginated list of items */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["RelationshipRule"][]; + /** @description Maximum number of items returned */ + limit: number; + /** @description Number of items skipped */ + offset: number; + /** @description Total number of items available */ + total: number; + }; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getRelationshipRule: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description ID of the relationship rule */ + relationshipRuleId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RelationshipRule"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + evaluateReleaseTarget: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["EvaluateReleaseTargetRequest"]; + }; + }; + responses: { + /** @description Policy evaluation results for the release target */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + decision?: components["schemas"]["DeployDecision"]; + /** @description The number of policies evaluated */ + policiesEvaluated?: number; + }; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + previewReleaseTargetsForResource: { + parameters: { + query?: { + /** @description Maximum number of items to return */ + limit?: number; + /** @description Number of items to skip */ + offset?: number; + }; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ResourcePreviewRequest"]; + }; + }; + responses: { + /** @description Paginated list of items */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["ReleaseTargetPreview"][]; + /** @description Maximum number of items returned */ + limit: number; + /** @description Number of items skipped */ + offset: number; + /** @description Total number of items available */ + total: number; + }; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getReleaseTargetStates: { + parameters: { + query?: { + /** @description Maximum number of items to return */ + limit?: number; + /** @description Number of items to skip */ + offset?: number; + }; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + deploymentId: string; + environmentId: string; + }; + }; + }; + responses: { + /** @description Paginated list of items */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["ReleaseTargetAndState"][]; + /** @description Maximum number of items returned */ + limit: number; + /** @description Number of items skipped */ + offset: number; + /** @description Total number of items available */ + total: number; + }; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getReleaseTargetDesiredRelease: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description Key of the release target */ + releaseTargetKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The desired release for the release target */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + desiredRelease?: components["schemas"]["Release"]; + }; }; - /** - * Get policies for a release target - * @description Returns a list of policies for a release target {releaseTargetId}. - */ - get: operations["getPoliciesForReleaseTarget"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/release-targets/{releaseTargetKey}/state": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; }; - /** - * Get the state for a release target - * @description Returns the state for a release target {releaseTargetKey}. - */ - get: operations["getReleaseTargetState"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/releases/{releaseId}": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; + content: { + "application/json": components["schemas"]["ErrorResponse"]; }; - /** - * Get release - * @description Returns a specific release by ID. - */ - get: operations["getRelease"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/releases/{releaseId}/verifications": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; + }; + }; + }; + getJobsForReleaseTarget: { + parameters: { + query?: { + /** @description Maximum number of items to return */ + limit?: number; + /** @description Number of items to skip */ + offset?: number; + /** @description CEL expression to filter the results */ + cel?: string; + }; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description Key of the release target */ + releaseTargetKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Paginated list of items */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["Job"][]; + /** @description Maximum number of items returned */ + limit: number; + /** @description Number of items skipped */ + offset: number; + /** @description Total number of items available */ + total: number; + }; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getPoliciesForReleaseTarget: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description Key of the release target */ + releaseTargetKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description A list of policies */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + policies?: components["schemas"]["Policy"][]; + }; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getReleaseTargetState: { + parameters: { + query?: { + /** @description Whether to bypass the cache */ + bypassCache?: boolean; + }; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description Key of the release target */ + releaseTargetKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The state for the release target */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ReleaseTargetState"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getRelease: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description ID of the release */ + releaseId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The requested release */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Release"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getReleaseVerifications: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description ID of the release */ + releaseId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description List of verifications for the release */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["JobVerification"][]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getResourceProviders: { + parameters: { + query?: { + /** @description Maximum number of items to return */ + limit?: number; + /** @description Number of items to skip */ + offset?: number; + }; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Paginated list of items */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["ResourceProvider"][]; + /** @description Maximum number of items returned */ + limit: number; + /** @description Number of items skipped */ + offset: number; + /** @description Total number of items available */ + total: number; + }; + }; + }; + }; + }; + cacheBatch: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @description The ID of the resource provider */ + providerId: string; + /** @description Array of resources to cache */ + resources: components["schemas"]["Resource"][]; + }; + }; + }; + responses: { + /** @description Batch cached successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description Unique ID for this cached batch */ + batchId?: string; + /** @description Number of resources cached */ + resourceCount?: number; + }; + }; + }; + }; + }; + getResourceProviderByName: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description Name of the resource provider */ + name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ResourceProvider"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getKindsForWorkspace: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The requested kinds */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": string[]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + queryResources: { + parameters: { + query?: { + /** @description Maximum number of items to return */ + limit?: number; + /** @description Number of items to skip */ + offset?: number; + }; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + filter?: components["schemas"]["Selector"]; + }; + }; + }; + responses: { + /** @description Paginated list of items */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["Resource"][]; + /** @description Maximum number of items returned */ + limit: number; + /** @description Number of items skipped */ + offset: number; + /** @description Total number of items available */ + total: number; + }; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getResourceByIdentifier: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description Identifier of the resource */ + resourceIdentifier: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The requested resource */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Resource"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getDeploymentsForResource: { + parameters: { + query?: { + /** @description Maximum number of items to return */ + limit?: number; + /** @description Number of items to skip */ + offset?: number; + }; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description Identifier of the resource */ + resourceIdentifier: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Paginated list of items */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["Deployment"][]; + /** @description Maximum number of items returned */ + limit: number; + /** @description Number of items skipped */ + offset: number; + /** @description Total number of items available */ + total: number; + }; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getRelationshipsForResource: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description Identifier of the resource */ + resourceIdentifier: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The requested relationships */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + [key: string]: components["schemas"]["EntityRelation"][]; + }; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getReleaseTargetsForResource: { + parameters: { + query?: { + /** @description Maximum number of items to return */ + limit?: number; + /** @description Number of items to skip */ + offset?: number; + }; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description Identifier of the resource */ + resourceIdentifier: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Paginated list of items */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["ReleaseTargetWithState"][]; + /** @description Maximum number of items returned */ + limit: number; + /** @description Number of items skipped */ + offset: number; + /** @description Total number of items available */ + total: number; + }; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getReleaseTargetForResourceInDeployment: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description Identifier of the resource */ + resourceIdentifier: string; + /** @description ID of the deployment */ + deploymentId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The requested release target */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ReleaseTarget"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getVariablesForResource: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description Identifier of the resource */ + resourceIdentifier: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The requested variables */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ResourceVariable"][]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getEngineStatus: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The status of the engine */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + healthy?: boolean; + message?: string; + }; }; - /** - * Get release verifications - * @description Returns all verifications for jobs belonging to this release. - */ - get: operations["getReleaseVerifications"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/resource-providers": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** Get all resource providers */ - get: operations["getResourceProviders"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/resource-providers/cache-batch": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** - * Cache a large resource batch for deferred processing - * @description Stores resources in memory and returns a batch ID. The batch is processed when a corresponding Kafka event is received. Uses Ristretto cache with 5-minute TTL. - */ - post: operations["cacheBatch"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/resource-providers/name/{name}": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** Get a resource provider by name */ - get: operations["getResourceProviderByName"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/resources/kinds": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; + }; + }; + }; + listSystems: { + parameters: { + query?: { + /** @description Number of items to skip */ + offset?: number; + /** @description Maximum number of items to return */ + limit?: number; + }; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description A list of systems */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["System"][]; + /** @description Maximum number of items returned */ + limit: number; + /** @description Number of items skipped */ + offset: number; + /** @description Total number of items available */ + total: number; + }; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getSystem: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description ID of the system */ + systemId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The requested system with its environments and deployments */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description Deployments associated with the system */ + deployments: components["schemas"]["Deployment"][]; + /** @description Environments associated with the system */ + environments: components["schemas"]["Environment"][]; + system: components["schemas"]["System"]; + }; }; - /** - * Get kinds for a workspace - * @description Returns a list of all resource kinds in a workspace. - */ - get: operations["getKindsForWorkspace"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/resources/query": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** - * Query resources with CEL expression - * @description Returns paginated resources that match the provided CEL expression. Use the "resource" variable in your expression to access resource properties. - */ - post: operations["queryResources"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/resources/{resourceIdentifier}": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; }; - /** - * Get resource by identifier - * @description Returns a specific resource by its identifier. - */ - get: operations["getResourceByIdentifier"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/resources/{resourceIdentifier}/deployments": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; + content: { + "application/json": components["schemas"]["ErrorResponse"]; }; - /** - * Get deployments for a resource - * @description Returns a paginated list of deployments that match the given resource. - */ - get: operations["getDeploymentsForResource"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/resources/{resourceIdentifier}/relationships": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; }; - /** - * Get relationships for a resource - * @description Returns all relationships for the specified resource. - */ - get: operations["getRelationshipsForResource"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/resources/{resourceIdentifier}/release-targets": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get release targets for a resource - * @description Returns a list of release targets for a resource. - */ - get: operations["getReleaseTargetsForResource"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/resources/{resourceIdentifier}/release-targets/deployment/{deploymentId}": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get release target for a resource in a deployment - * @description Returns a release target for a resource in a deployment. - */ - get: operations["getReleaseTargetForResourceInDeployment"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/resources/{resourceIdentifier}/variables": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get variables for a resource - * @description Returns a list of variables for a resource - */ - get: operations["getVariablesForResource"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/status": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get engine status - * @description Returns the status of the engine. - */ - get: operations["getEngineStatus"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/systems": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * List systems - * @description Returns a list of systems for a workspace. - */ - get: operations["listSystems"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/systems/{systemId}": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get system - * @description Returns a specific system by ID. - */ - get: operations["getSystem"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/systems/{systemId}/deployments/{deploymentId}": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get deployment system link - * @description Returns a specific deployment system link by ID. - */ - get: operations["getDeploymentSystemLink"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/systems/{systemId}/environments/{environmentId}": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get environment system link - * @description Returns a specific environment system link by ID. - */ - get: operations["getEnvironmentSystemLink"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/workflows": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * List workflows - * @description Returns a list of workflows. - */ - get: operations["listWorkflows"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/workflows/{workflowId}": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get a workflow - * @description Gets a workflow by ID. - */ - get: operations["getWorkflow"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/workflows/{workflowId}/runs": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** - * Get all workflow runs for a workflow - * @description Gets all workflow runs for a workflow by ID. - */ - get: operations["getWorkflowRuns"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; -} -export type webhooks = Record; -export interface components { - schemas: { - AnyApprovalRule: { - /** Format: int32 */ - minApprovals: number; - }; - /** @enum {string} */ - ApprovalStatus: "approved" | "rejected"; - ArgoCDJobAgentConfig: { - /** @description ArgoCD API token. */ - apiKey: string; - /** @description ArgoCD server address (host[:port] or URL). */ - serverUrl: string; - /** @description ArgoCD application template. */ - template: string; - }; - BooleanValue: boolean; - CelMatcher: { - cel: string; - }; - CelSelector: { - cel: string; - }; - DatadogMetricProvider: { - /** - * @description Datadog aggregator - * @default last - * @enum {string} - */ - aggregator: "avg" | "min" | "max" | "sum" | "last" | "percentile" | "mean" | "l2norm" | "area"; - /** - * @description Datadog API key (supports Go templates for variable references) - * @example {{.variables.dd_api_key}} - */ - apiKey: string; - /** - * @description Datadog Application key (supports Go templates for variable references) - * @example {{.variables.dd_app_key}} - */ - appKey: string; - /** @description Datadog formula (supports Go templates) */ - formula?: string; - /** - * Format: int64 - * @example 30 - */ - intervalSeconds?: number; - /** - * @description Datadog metrics queries (supports Go templates) - * @example { - * "q": "sum:requests.error.rate{service:{{.resource.name}}}" - * } - */ - queries: { - [key: string]: string; - }; - /** - * @description Datadog site URL (e.g., datadoghq.com, datadoghq.eu, us3.datadoghq.com) - * @default datadoghq.com - */ - site: string; - /** - * @description Provider type (enum property replaced by openapi-typescript) - * @enum {string} - */ - type: "datadog"; - }; - DeployDecision: { - policyResults: components["schemas"]["PolicyEvaluation"][]; - }; - Deployment: { - description?: string; - id: string; - jobAgentConfig: components["schemas"]["JobAgentConfig"]; - jobAgentId?: string; - jobAgents?: components["schemas"]["DeploymentJobAgent"][]; - metadata: { - [key: string]: string; - }; - name: string; - resourceSelector?: components["schemas"]["Selector"]; - slug: string; - }; - DeploymentAndSystems: { - deployment: components["schemas"]["Deployment"]; - systems: components["schemas"]["System"][]; - }; - DeploymentDependencyRule: { - /** @description CEL expression to match upstream deployment(s) that must have a successful release before this deployment can proceed. */ - dependsOn: string; - }; - DeploymentJobAgent: { - config: components["schemas"]["JobAgentConfig"]; - ref: string; - /** @description CEL expression to determine if the job agent should be used */ - selector: string; - }; - DeploymentVariable: { - defaultValue?: components["schemas"]["LiteralValue"]; - deploymentId: string; - description?: string; - id: string; - key: string; - }; - DeploymentVariableValue: { - deploymentVariableId: string; - id: string; - /** Format: int64 */ - priority: number; - resourceSelector?: components["schemas"]["Selector"]; - value: components["schemas"]["Value"]; - }; - DeploymentVariableWithValues: { - values: components["schemas"]["DeploymentVariableValue"][]; - variable: components["schemas"]["DeploymentVariable"]; - }; - DeploymentVersion: { - config: { - [key: string]: unknown; - }; - /** Format: date-time */ - createdAt: string; - deploymentId: string; - id: string; - jobAgentConfig: components["schemas"]["JobAgentConfig"]; - message?: string; - metadata: { - [key: string]: string; - }; - name: string; - status: components["schemas"]["DeploymentVersionStatus"]; - tag: string; - }; - /** @enum {string} */ - DeploymentVersionStatus: "unspecified" | "building" | "ready" | "failed" | "rejected" | "paused"; - DeploymentWindowRule: { - /** - * @description If true, deployments are only allowed during the window. If false, deployments are blocked during the window (deny window) - * @default true - */ - allowWindow: boolean; - /** - * Format: int32 - * @description Duration of each deployment window in minutes - */ - durationMinutes: number; - /** @description RFC 5545 recurrence rule defining when deployment windows start (e.g., FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR;BYHOUR=9) */ - rrule: string; - /** @description IANA timezone for the rrule (e.g., America/New_York). Defaults to UTC if not specified */ - timezone?: string; - }; - DeploymentWithVariablesAndSystems: { - deployment: components["schemas"]["Deployment"]; - systems: components["schemas"]["System"][]; - variables: components["schemas"]["DeploymentVariableWithValues"][]; - }; - DispatchContext: { - deployment?: components["schemas"]["Deployment"]; - environment?: components["schemas"]["Environment"]; - jobAgent: components["schemas"]["JobAgent"]; - jobAgentConfig: components["schemas"]["JobAgentConfig"]; - release?: components["schemas"]["Release"]; - resource?: components["schemas"]["Resource"]; - variables?: { - [key: string]: components["schemas"]["LiteralValue"]; - }; - version?: components["schemas"]["DeploymentVersion"]; - workflow?: components["schemas"]["Workflow"]; - workflowJob?: components["schemas"]["WorkflowJob"]; - workflowRun?: components["schemas"]["WorkflowRun"]; - }; - EntityRelation: { - direction: components["schemas"]["RelationDirection"]; - entity: components["schemas"]["RelatableEntity"]; - /** @description ID of the related entity */ - entityId: string; - entityType: components["schemas"]["RelatableEntityType"]; - rule: components["schemas"]["RelationshipRule"]; - }; - Environment: { - /** Format: date-time */ - createdAt: string; - description?: string; - id: string; - metadata: { - [key: string]: string; - }; - name: string; - resourceSelector?: components["schemas"]["Selector"]; - }; - EnvironmentProgressionRule: { - dependsOnEnvironmentSelector: components["schemas"]["Selector"]; - /** - * Format: int32 - * @description Maximum age of dependency deployment before blocking progression (prevents stale promotions) - */ - maximumAgeHours?: number; - /** - * Format: int32 - * @description Minimum time to wait after the depends on environment is in a success state before the current environment can be deployed - * @default 0 - */ - minimumSockTimeMinutes: number; - /** - * Format: float - * @default 100 - */ - minimumSuccessPercentage: number; - successStatuses?: components["schemas"]["JobStatus"][]; - }; - EnvironmentSummary: { - id: string; - name: string; - }; - EnvironmentWithSystems: components["schemas"]["Environment"] & { - systems: components["schemas"]["System"][]; - }; - ErrorResponse: { - /** @example Workspace not found */ - error?: string; - }; - EvaluateReleaseTargetRequest: { - releaseTarget: components["schemas"]["ReleaseTarget"]; - version: components["schemas"]["DeploymentVersion"]; - }; - EvaluationScope: { - environmentId?: string; - versionId?: string; - }; - GithubEntity: { - installationId: number; - slug: string; - }; - GithubJobAgentConfig: { - /** - * Format: int - * @description GitHub app installation ID. - */ - installationId: number; - /** @description GitHub repository owner. */ - owner: string; - /** @description Git ref to run the workflow on (defaults to "main" if omitted). */ - ref?: string; - /** @description GitHub repository name. */ - repo: string; - /** - * Format: int64 - * @description GitHub Actions workflow ID. - */ - workflowId: number; - }; - GradualRolloutRule: { - /** - * @description Strategy for scheduling deployments to release targets. "linear": Each target is deployed at a fixed interval of timeScaleInterval seconds. "linear-normalized": Deployments are spaced evenly so that the last target is scheduled at or before timeScaleInterval seconds. See rolloutType algorithm documentation for details. - * @enum {string} - */ - rolloutType: "linear" | "linear-normalized"; - /** - * Format: int32 - * @description Base time interval in seconds used to compute the delay between deployments to release targets. - */ - timeScaleInterval: number; - }; - HTTPMetricProvider: { - /** @description Request body (supports Go templates) */ - body?: string; - /** @description HTTP headers (values support Go templates) */ - headers?: { - [key: string]: string; - }; - /** - * @description HTTP method - * @default GET - * @enum {string} - */ - method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS"; - /** - * @description Request timeout (duration string, e.g., "30s") - * @default 30s - */ - timeout: string; - /** - * @description Provider type (enum property replaced by openapi-typescript) - * @enum {string} - */ - type: "http"; - /** - * @description HTTP endpoint URL (supports Go templates) - * @example http://{{ .resource.name }}.{{ .environment.name }}/health - */ - url: string; - }; - IntegerValue: number; - Job: { - /** Format: date-time */ - completedAt?: string; - /** Format: date-time */ - createdAt: string; - dispatchContext?: components["schemas"]["DispatchContext"]; - externalId?: string; - id: string; - jobAgentConfig: components["schemas"]["JobAgentConfig"]; - jobAgentId: string; - message?: string; - metadata: { - [key: string]: string; - }; - releaseId: string; - /** Format: date-time */ - startedAt?: string; - status: components["schemas"]["JobStatus"]; - traceToken?: string; - /** Format: date-time */ - updatedAt: string; - workflowJobId: string; - }; - JobAgent: { - config: components["schemas"]["JobAgentConfig"]; - id: string; - metadata?: { - [key: string]: string; - }; - name: string; - type: string; - workspaceId: string; - }; - JobAgentConfig: { - [key: string]: unknown; - }; - /** @enum {string} */ - JobStatus: "cancelled" | "skipped" | "inProgress" | "actionRequired" | "pending" | "failure" | "invalidJobAgent" | "invalidIntegration" | "externalRunNotFound" | "successful"; - JobSummary: { - id: string; - /** @description External links extracted from job metadata */ - links?: { - [key: string]: string; - }; - message?: string; - status: components["schemas"]["JobStatus"]; - verifications: components["schemas"]["JobVerification"][]; - }; - JobUpdateEvent: { - agentId?: string; - externalId?: string; - fieldsToUpdate?: ("completedAt" | "createdAt" | "dispatchContext" | "externalId" | "id" | "jobAgentConfig" | "jobAgentId" | "message" | "metadata" | "releaseId" | "startedAt" | "status" | "traceToken" | "updatedAt" | "workflowJobId")[]; - id?: string; - job: components["schemas"]["Job"]; - } & (unknown | unknown); - JobVerification: { - /** - * Format: date-time - * @description When verification was created - */ - createdAt: string; - id: string; - jobId: string; - /** @description Summary message of verification result */ - message?: string; - /** @description Metrics associated with this verification */ - metrics: components["schemas"]["VerificationMetricStatus"][]; - }; - /** @enum {string} */ - JobVerificationStatus: "running" | "passed" | "failed" | "cancelled"; - JobWithRelease: { - deployment?: components["schemas"]["Deployment"]; - environment?: components["schemas"]["Environment"]; - job: components["schemas"]["Job"]; - release: components["schemas"]["Release"]; - resource?: components["schemas"]["Resource"]; - }; - JobWithVerifications: { - job: components["schemas"]["Job"]; - verifications: components["schemas"]["JobVerification"][]; - }; - JsonSelector: { - json: { - [key: string]: unknown; - }; - }; - LiteralValue: components["schemas"]["BooleanValue"] | components["schemas"]["NumberValue"] | components["schemas"]["IntegerValue"] | components["schemas"]["StringValue"] | components["schemas"]["ObjectValue"] | components["schemas"]["NullValue"]; - MetricProvider: components["schemas"]["HTTPMetricProvider"] | components["schemas"]["SleepMetricProvider"] | components["schemas"]["DatadogMetricProvider"] | components["schemas"]["PrometheusMetricProvider"] | components["schemas"]["TerraformCloudRunMetricProvider"]; - /** @enum {boolean} */ - NullValue: true; - NumberValue: number; - ObjectValue: { - object: { - [key: string]: unknown; - }; - }; - Policy: { - createdAt: string; - description?: string; - enabled: boolean; - id: string; - /** @description Arbitrary metadata for the policy (record) */ - metadata: { - [key: string]: string; - }; - name: string; - priority: number; - rules: components["schemas"]["PolicyRule"][]; - /** @description CEL expression for matching release targets. Use "true" to match all targets. */ - selector: string; - workspaceId: string; - }; - PolicyEvaluation: { - policy?: components["schemas"]["Policy"]; - ruleResults: components["schemas"]["RuleEvaluation"][]; - summary?: string; - }; - PolicyRule: { - anyApproval?: components["schemas"]["AnyApprovalRule"]; - createdAt: string; - deploymentDependency?: components["schemas"]["DeploymentDependencyRule"]; - deploymentWindow?: components["schemas"]["DeploymentWindowRule"]; - environmentProgression?: components["schemas"]["EnvironmentProgressionRule"]; - gradualRollout?: components["schemas"]["GradualRolloutRule"]; - id: string; - policyId: string; - retry?: components["schemas"]["RetryRule"]; - rollback?: components["schemas"]["RollbackRule"]; - verification?: components["schemas"]["VerificationRule"]; - versionCooldown?: components["schemas"]["VersionCooldownRule"]; - versionSelector?: components["schemas"]["VersionSelectorRule"]; - }; - PolicySkip: { - /** - * Format: date-time - * @description When this skip was created - */ - createdAt: string; - /** @description User ID who created this skip */ - createdBy: string; - /** @description Environment this skip applies to. If null, applies to all environments. */ - environmentId?: string; - /** - * Format: date-time - * @description When this skip expires. If null, skip never expires. - */ - expiresAt?: string; - /** @description Unique identifier for the skip */ - id: string; - /** @description Required reason for why this skip is needed (e.g., incident ticket, emergency situation) */ - reason: string; - /** @description Resource this skip applies to. If null, applies to all resources (in the environment if specified, or globally). */ - resourceId?: string; - /** @description Rule ID this skip applies to */ - ruleId: string; - /** @description Deployment version this skip applies to */ - versionId: string; - /** @description Workspace this skip belongs to */ - workspaceId: string; - }; - PrometheusMetricProvider: { - /** - * @description Prometheus server address (supports Go templates) - * @example http://prometheus.example.com:9090 - */ - address: string; - /** @description Authentication configuration for Prometheus */ - authentication?: { - /** - * @description Bearer token for authentication (supports Go templates for variable references) - * @example {{.variables.prometheus_token}} - */ - bearerToken?: string; - /** @description OAuth2 client credentials flow */ - oauth2?: { - /** @description OAuth2 client ID (supports Go templates) */ - clientId: string; - /** @description OAuth2 client secret (supports Go templates) */ - clientSecret: string; - /** @description OAuth2 scopes */ - scopes?: string[]; - /** @description Token endpoint URL */ - tokenUrl: string; - }; - }; - /** @description Additional HTTP headers for the Prometheus request (values support Go templates) */ - headers?: { - /** @example X-Scope-OrgID */ - key: string; - /** @example tenant_a */ - value: string; - }[]; - /** - * @description Skip TLS certificate verification - * @default false - */ - insecure: boolean; - /** - * @description PromQL query expression (supports Go templates) - * @example sum(irate(istio_requests_total{reporter="source",destination_service=~"{{.resource.name}}",response_code!~"5.*"}[5m])) - */ - query: string; - /** @description If provided, a range query (/api/v1/query_range) is used instead of an instant query (/api/v1/query) */ - rangeQuery?: { - /** - * @description How far back from now for the query end, as a Prometheus duration (e.g., "0s" for now, "1m" for 1 minute ago). Defaults to "0s" (now) if unset. - * @example 0s - */ - end?: string; - /** - * @description How far back from now to start the query, as a Prometheus duration (e.g., "5m", "1h"). Defaults to 10 * step if unset. - * @example 5m - */ - start?: string; - /** - * @description Query resolution step width as a Prometheus duration (e.g., "15s", "1m", "500ms") - * @example 1m - */ - step: string; - }; - /** - * Format: int64 - * @description Query timeout in seconds - * @example 30 - */ - timeout?: number; - /** - * @description Provider type (enum property replaced by openapi-typescript) - * @enum {string} - */ - type: "prometheus"; - }; - PropertiesMatcher: { - properties: components["schemas"]["PropertyMatcher"][]; - }; - PropertyMatcher: { - fromProperty: string[]; - /** @enum {string} */ - operator: "equals" | "notEquals" | "contains" | "startsWith" | "endsWith" | "regex"; - toProperty: string[]; - }; - ReferenceValue: { - path: string[]; - reference: string; - }; - RelatableEntity: components["schemas"]["Deployment"] | components["schemas"]["Environment"] | components["schemas"]["Resource"]; - /** @enum {string} */ - RelatableEntityType: "deployment" | "environment" | "resource"; - /** @enum {string} */ - RelationDirection: "from" | "to"; - RelationshipRule: { - description?: string; - fromSelector?: components["schemas"]["Selector"]; - fromType: components["schemas"]["RelatableEntityType"]; - id: string; - matcher: components["schemas"]["CelMatcher"] | components["schemas"]["PropertiesMatcher"]; - metadata: { - [key: string]: string; - }; - name: string; - reference: string; - relationshipType: string; - toSelector?: components["schemas"]["Selector"]; - toType: components["schemas"]["RelatableEntityType"]; - workspaceId: string; - }; - Release: { - createdAt: string; - encryptedVariables: string[]; - releaseTarget: components["schemas"]["ReleaseTarget"]; - variables: { - [key: string]: components["schemas"]["LiteralValue"]; - }; - version: components["schemas"]["DeploymentVersion"]; - }; - ReleaseTarget: { - deploymentId: string; - environmentId: string; - resourceId: string; - }; - ReleaseTargetAndState: { - releaseTarget: components["schemas"]["ReleaseTarget"]; - state: components["schemas"]["ReleaseTargetState"]; - }; - ReleaseTargetPreview: { - deployment: components["schemas"]["Deployment"]; - environment: components["schemas"]["Environment"]; - system: components["schemas"]["System"]; - }; - ReleaseTargetState: { - currentRelease?: components["schemas"]["Release"]; - desiredRelease?: components["schemas"]["Release"]; - latestJob?: components["schemas"]["JobWithVerifications"]; - }; - ReleaseTargetSummary: { - currentVersion?: components["schemas"]["VersionSummary"]; - desiredVersion?: components["schemas"]["VersionSummary"]; - environment: components["schemas"]["EnvironmentSummary"]; - latestJob?: components["schemas"]["JobSummary"]; - releaseTarget: components["schemas"]["ReleaseTarget"]; - resource: components["schemas"]["ResourceSummary"]; - }; - ReleaseTargetWithState: { - deployment: components["schemas"]["Deployment"]; - environment: components["schemas"]["Environment"]; - releaseTarget: components["schemas"]["ReleaseTarget"]; - resource: components["schemas"]["Resource"]; - state: components["schemas"]["ReleaseTargetState"]; - }; - ResolvedPolicy: { - environmentIds: string[]; - policy: components["schemas"]["Policy"]; - releaseTargets: components["schemas"]["ReleaseTarget"][]; - }; - Resource: { - config: { - [key: string]: unknown; - }; - /** Format: date-time */ - createdAt: string; - /** Format: date-time */ - deletedAt?: string; - id: string; - identifier: string; - kind: string; - /** Format: date-time */ - lockedAt?: string; - metadata: { - [key: string]: string; - }; - name: string; - providerId?: string; - /** Format: date-time */ - updatedAt?: string; - version: string; - workspaceId: string; - }; - ResourcePreviewRequest: { - config: { - [key: string]: unknown; - }; - identifier: string; - kind: string; - metadata: { - [key: string]: string; - }; - name: string; - version: string; - }; - ResourceProvider: { - /** Format: date-time */ - createdAt: string; - id: string; - metadata: { - [key: string]: string; - }; - name: string; - /** Format: uuid */ - workspaceId: string; - }; - ResourceSummary: { - id: string; - identifier: string; - kind: string; - name: string; - version: string; - }; - ResourceVariable: { - key: string; - resourceId: string; - value: components["schemas"]["Value"]; - }; - ResourceVariablesBulkUpdateEvent: { - resourceId: string; - variables: { - [key: string]: unknown; - }; - }; - RetryRule: { - /** - * Format: int32 - * @description Minimum seconds to wait between retry attempts. If null, retries are allowed immediately after job completion. - */ - backoffSeconds?: number; - /** - * @description Backoff strategy: "linear" uses constant backoffSeconds delay, "exponential" doubles the delay with each retry (backoffSeconds * 2^(attempt-1)). - * @default linear - * @enum {string} - */ - backoffStrategy: "linear" | "exponential"; - /** - * Format: int32 - * @description Maximum backoff time in seconds (cap for exponential backoff). If null, no maximum is enforced. - */ - maxBackoffSeconds?: number; - /** - * Format: int32 - * @description Maximum number of retries allowed. 0 means no retries (1 attempt total), 3 means up to 4 attempts (1 initial + 3 retries). - */ - maxRetries: number; - /** @description Job statuses that count toward the retry limit. If null or empty, defaults to ["failure", "invalidIntegration", "invalidJobAgent"] for maxRetries > 0, or ["failure", "invalidIntegration", "invalidJobAgent", "successful"] for maxRetries = 0. Cancelled and skipped jobs never count by default (allows redeployment after cancellation). Example: ["failure", "cancelled"] will only count failed/cancelled jobs. */ - retryOnStatuses?: components["schemas"]["JobStatus"][]; - }; - RollbackRule: { - /** @description Job statuses that will trigger a rollback */ - onJobStatuses?: components["schemas"]["JobStatus"][]; - /** - * @description If true, a release target will be rolled back if the verification fails - * @default false - */ - onVerificationFailure: boolean; - }; - RuleEvaluation: { - /** @description Whether the rule requires an action (e.g., approval, wait) */ - actionRequired: boolean; - /** - * @description Type of action required - * @enum {string} - */ - actionType?: "approval" | "wait"; - /** @description Whether the rule allows the deployment */ - allowed: boolean; - /** @description Additional details about the rule evaluation */ - details: { - [key: string]: unknown; - }; - /** @description Human-readable explanation of the rule result */ - message: string; - /** - * Format: date-time - * @description The time when this rule should be re-evaluated (e.g., when soak time will be complete, when gradual rollout schedule is due) - */ - nextEvaluationTime?: string; - /** @description The ID of the rule that was evaluated */ - ruleId: string; - /** - * Format: date-time - * @description The time when the rule requirement was satisfied (e.g., when approvals were met, soak time completed) - */ - satisfiedAt?: string; - }; - Selector: components["schemas"]["JsonSelector"] | components["schemas"]["CelSelector"]; - SensitiveValue: { - valueHash: string; - }; - SleepMetricProvider: { - /** - * Format: int32 - * @example 30 - */ - durationSeconds: number; - /** - * @description Provider type (enum property replaced by openapi-typescript) - * @enum {string} - */ - type: "sleep"; - }; - StringValue: string; - System: { - description?: string; - id: string; - metadata?: { - [key: string]: string; - }; - name: string; - workspaceId: string; - }; - SystemDeploymentLink: { - deploymentId: string; - systemId: string; - }; - SystemEnvironmentLink: { - environmentId: string; - systemId: string; - }; - TerraformCloudJobAgentConfig: { - /** @description Terraform Cloud address (e.g. https://app.terraform.io). */ - address: string; - /** @description Terraform Cloud organization name. */ - organization: string; - /** @description Terraform Cloud workspace template. */ - template: string; - /** @description Terraform Cloud API token. */ - token: string; - }; - TerraformCloudRunMetricProvider: { - /** - * @description Terraform Cloud address - * @example https://app.terraform.io - */ - address: string; - /** - * @description Terraform Cloud run ID - * @example run-1234567890 - */ - runId: string; - /** - * @description Terraform Cloud token - * @example {{.variables.terraform_cloud_token}} - */ - token: string; - /** - * @description Provider type (enum property replaced by openapi-typescript) - * @enum {string} - */ - type: "terraformCloudRun"; - }; - TestRunnerJobAgentConfig: { - /** - * Format: int - * @description Delay in seconds before resolving the job. - */ - delaySeconds?: number; - /** @description Optional message to include in the job output. */ - message?: string; - /** @description Final status to set (e.g. "successful", "failure"). */ - status?: string; - }; - UserApprovalRecord: { - createdAt: string; - environmentId: string; - reason?: string; - status: components["schemas"]["ApprovalStatus"]; - userId: string; - versionId: string; - }; - Value: components["schemas"]["LiteralValue"] | components["schemas"]["ReferenceValue"] | components["schemas"]["SensitiveValue"]; - VerificationMeasurement: { - /** @description Raw measurement data */ - data?: { - [key: string]: unknown; - }; - /** - * Format: date-time - * @description When measurement was taken - */ - measuredAt: string; - /** @description Measurement result message */ - message?: string; - status: components["schemas"]["VerificationMeasurementStatus"]; - }; - /** - * @description Status of a verification measurement - * @enum {string} - */ - VerificationMeasurementStatus: "passed" | "failed" | "inconclusive"; - VerificationMetricSpec: { - /** @description Number of measurements to take */ - count: number; - /** - * @description CEL expression to evaluate measurement failure (e.g., "result.statusCode == 500"), if not provided, a failure is just the opposite of the success condition - * @example result.statusCode == 500 - */ - failureCondition?: string; - /** - * @description Stop after this many consecutive failures (0 = no limit) - * @default 0 - */ - failureThreshold: number; - /** - * Format: int32 - * @description Interval between measurements in seconds - * @example 30 - */ - intervalSeconds: number; - /** @description Name of the verification metric */ - name: string; - provider: components["schemas"]["MetricProvider"]; - /** - * @description CEL expression to evaluate measurement success (e.g., "result.statusCode == 200") - * @example result.statusCode == 200 - */ - successCondition: string; - /** - * @description Minimum number of consecutive successful measurements required to consider the metric successful - * @example 0 - */ - successThreshold?: number; - }; - VerificationMetricStatus: components["schemas"]["VerificationMetricSpec"] & { - /** @description Individual verification measurements taken for this metric */ - measurements: components["schemas"]["VerificationMeasurement"][]; - }; - VerificationRule: { - /** @description Metrics to verify */ - metrics: components["schemas"]["VerificationMetricSpec"][]; - /** - * @description When to trigger verification - * @default jobSuccess - * @enum {string} - */ - triggerOn: "jobCreated" | "jobStarted" | "jobSuccess" | "jobFailure"; - }; - VersionCooldownRule: { - /** - * Format: int32 - * @description Minimum time in seconds that must pass since the currently deployed (or in-progress) version was created before allowing another deployment. This enables batching of frequent upstream releases into periodic deployments. - */ - intervalSeconds: number; - }; - VersionSelectorRule: { - /** @description Human-readable description of what this version selector does. Example: "Only deploy v2.x versions to staging environments" */ - description?: string; - selector: components["schemas"]["Selector"]; - }; - VersionSummary: { - id: string; - name: string; - tag: string; - }; - Workflow: { - id: string; - inputs: components["schemas"]["WorkflowInput"][]; - jobs: components["schemas"]["WorkflowJobTemplate"][]; - name: string; - }; - WorkflowArrayInput: components["schemas"]["WorkflowManualArrayInput"] | components["schemas"]["WorkflowSelectorArrayInput"]; - WorkflowBooleanInput: { - default?: boolean; - key: string; - /** @enum {string} */ - type: "boolean"; - }; - WorkflowInput: components["schemas"]["WorkflowStringInput"] | components["schemas"]["WorkflowNumberInput"] | components["schemas"]["WorkflowBooleanInput"] | components["schemas"]["WorkflowArrayInput"] | components["schemas"]["WorkflowObjectInput"]; - WorkflowJob: { - /** @description Configuration for the job agent */ - config: { - [key: string]: unknown; - }; - id: string; - index: number; - /** @description Reference to the job agent */ - ref: string; - workflowRunId: string; - }; - WorkflowJobAgentConfig: { - config: { - [key: string]: unknown; - }; - id: string; - }; - WorkflowJobMatrix: { - [key: string]: { - [key: string]: unknown; - }[] | string; - }; - WorkflowJobTemplate: { - /** @description Configuration for the job agent */ - config: { - [key: string]: unknown; - }; - id: string; - /** @description CEL expression to determine if the job should run */ - if?: string; - matrix?: components["schemas"]["WorkflowJobMatrix"]; - name: string; - /** @description Reference to the job agent */ - ref: string; - workflowId: string; - }; - WorkflowJobWithJobs: components["schemas"]["WorkflowJob"] & { - jobs: components["schemas"]["Job"][]; - }; - WorkflowManualArrayInput: { - default?: { - [key: string]: unknown; - }[]; - key: string; - /** @enum {string} */ - type: "array"; - }; - WorkflowNumberInput: { - default?: number; - key: string; - /** @enum {string} */ - type: "number"; - }; - WorkflowObjectInput: { - default?: { - [key: string]: unknown; - }; - key: string; - /** @enum {string} */ - type: "object"; - }; - WorkflowRun: { - id: string; - inputs: { - [key: string]: unknown; - }; - workflowId: string; - }; - WorkflowRunWithJobs: components["schemas"]["WorkflowRun"] & { - jobs: components["schemas"]["WorkflowJobWithJobs"][]; - }; - WorkflowSelectorArrayInput: { - key: string; - selector: { - default?: components["schemas"]["Selector"]; - /** @enum {string} */ - entityType: "resource" | "environment" | "deployment"; - }; - /** @enum {string} */ - type: "array"; - }; - WorkflowStringInput: { - default?: string; - key: string; - /** @enum {string} */ - type: "string"; + content: { + "application/json": components["schemas"]["ErrorResponse"]; }; + }; }; - responses: never; + }; + getDeploymentSystemLink: { parameters: { - /** @description Type of the entity (deployment, environment, or resource) */ - relatableEntityType: components["schemas"]["RelatableEntityType"]; - }; - requestBodies: never; - headers: never; - pathItems: never; -} -export type $defs = Record; -export interface operations { - validateResourceSelector: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: { - content: { - "application/json": { - resourceSelector?: components["schemas"]["Selector"]; - }; - }; - }; - responses: { - /** @description The validated resource selector */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - errors: string[]; - valid: boolean; - }; - }; - }; - }; - }; - listWorkspaceIds: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description A list of workspace IDs */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - workspaceIds?: string[]; - }; - }; - }; - }; - }; - getDeploymentVariableValue: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description ID of the deployment variable value */ - valueId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description The requested deployment variable value */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["DeploymentVariableValue"]; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getDeploymentVariable: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description ID of the deployment variable */ - variableId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description The requested deployment variable */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["DeploymentVariableWithValues"]; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getDeploymentVersionJobsList: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description ID of the deployment version */ - versionId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Jobs list grouped by environment and release target */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - environment: components["schemas"]["Environment"]; - releaseTargets: { - deployment: components["schemas"]["Deployment"]; - deploymentId: string; - environment: components["schemas"]["Environment"]; - environmentId: string; - id: string; - jobs: { - /** Format: date-time */ - createdAt: string; - externalId?: string; - id: string; - metadata: { - [key: string]: string; - }; - status: components["schemas"]["JobStatus"]; - }[]; - resource: components["schemas"]["Resource"]; - resourceId: string; - }[]; - }[]; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - listDeployments: { - parameters: { - query?: { - /** @description Maximum number of items to return */ - limit?: number; - /** @description Number of items to skip */ - offset?: number; - }; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Paginated list of items */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - items: components["schemas"]["DeploymentAndSystems"][]; - /** @description Maximum number of items returned */ - limit: number; - /** @description Number of items skipped */ - offset: number; - /** @description Total number of items available */ - total: number; - }; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getDeployment: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description ID of the deployment */ - deploymentId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description The requested deployment */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["DeploymentWithVariablesAndSystems"]; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getPoliciesForDeployment: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description ID of the deployment */ - deploymentId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description A list of resolved policies */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - items: components["schemas"]["ResolvedPolicy"][]; - }; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getReleaseTargetsForDeployment: { - parameters: { - query?: { - /** @description Maximum number of items to return */ - limit?: number; - /** @description Number of items to skip */ - offset?: number; - /** @description Filter by resource name */ - query?: string; - }; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description ID of the deployment */ - deploymentId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Paginated list of items */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - items: components["schemas"]["ReleaseTargetSummary"][]; - /** @description Maximum number of items returned */ - limit: number; - /** @description Number of items skipped */ - offset: number; - /** @description Total number of items available */ - total: number; - }; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getDeploymentResources: { - parameters: { - query?: { - /** @description Maximum number of items to return */ - limit?: number; - /** @description Number of items to skip */ - offset?: number; - }; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description ID of the deployment */ - deploymentId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Paginated list of items */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - items: components["schemas"]["Resource"][]; - /** @description Maximum number of items returned */ - limit: number; - /** @description Number of items skipped */ - offset: number; - /** @description Total number of items available */ - total: number; - }; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getVersionsForDeployment: { - parameters: { - query?: { - /** @description Maximum number of items to return */ - limit?: number; - /** @description Number of items to skip */ - offset?: number; - }; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description ID of the deployment */ - deploymentId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Paginated list of items */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - items: components["schemas"]["DeploymentVersion"][]; - /** @description Maximum number of items returned */ - limit: number; - /** @description Number of items skipped */ - offset: number; - /** @description Total number of items available */ - total: number; - }; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getDeploymentVersion: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description ID of the deployment version */ - deploymentVersionId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description OK response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["DeploymentVersion"]; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getRelatedEntities: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description Type of the entity (deployment, environment, or resource) */ - relatableEntityType: components["parameters"]["relatableEntityType"]; - /** @description ID of the entity */ - entityId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Related entities grouped by relationship reference */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - relations?: { - [key: string]: components["schemas"]["EntityRelation"][]; - }; - }; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - listEnvironments: { - parameters: { - query?: { - /** @description Number of items to skip */ - offset?: number; - /** @description Maximum number of items to return */ - limit?: number; - }; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description A list of environments */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - items: components["schemas"]["Environment"][]; - /** @description Maximum number of items returned */ - limit: number; - /** @description Number of items skipped */ - offset: number; - /** @description Total number of items available */ - total: number; - }; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getEnvironment: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description ID of the environment */ - environmentId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description The requested environment */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["EnvironmentWithSystems"]; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getReleaseTargetsForEnvironment: { - parameters: { - query?: { - /** @description Maximum number of items to return */ - limit?: number; - /** @description Number of items to skip */ - offset?: number; - }; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description ID of the environment */ - environmentId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Paginated list of items */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - items: components["schemas"]["ReleaseTargetWithState"][]; - /** @description Maximum number of items returned */ - limit: number; - /** @description Number of items skipped */ - offset: number; - /** @description Total number of items available */ - total: number; - }; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getEnvironmentResources: { - parameters: { - query?: { - /** @description Maximum number of items to return */ - limit?: number; - /** @description Number of items to skip */ - offset?: number; - }; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description ID of the environment */ - environmentId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Paginated list of items */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - items: components["schemas"]["Resource"][]; - /** @description Maximum number of items returned */ - limit: number; - /** @description Number of items skipped */ - offset: number; - /** @description Total number of items available */ - total: number; - }; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getGitHubEntityByInstallationId: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description Installation ID of the GitHub entity */ - installationId: number; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description OK response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["GithubEntity"]; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getJobAgents: { - parameters: { - query?: { - /** @description Maximum number of items to return */ - limit?: number; - /** @description Number of items to skip */ - offset?: number; - }; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Paginated list of items */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - items: components["schemas"]["JobAgent"][]; - /** @description Maximum number of items returned */ - limit: number; - /** @description Number of items skipped */ - offset: number; - /** @description Total number of items available */ - total: number; - }; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getJobAgent: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description ID of the job agent */ - jobAgentId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description The requested job agent */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["JobAgent"]; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getDeploymentsForJobAgent: { - parameters: { - query?: { - /** @description Maximum number of items to return */ - limit?: number; - /** @description Number of items to skip */ - offset?: number; - }; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description ID of the job agent */ - jobAgentId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Paginated list of items */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - items: components["schemas"]["Deployment"][]; - /** @description Maximum number of items returned */ - limit: number; - /** @description Number of items skipped */ - offset: number; - /** @description Total number of items available */ - total: number; - }; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getJobsForJobAgent: { - parameters: { - query?: { - /** @description Maximum number of items to return */ - limit?: number; - /** @description Number of items to skip */ - offset?: number; - }; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description ID of the job agent */ - jobAgentId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Paginated list of items */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - items: components["schemas"]["Job"][]; - /** @description Maximum number of items returned */ - limit: number; - /** @description Number of items skipped */ - offset: number; - /** @description Total number of items available */ - total: number; - }; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getJobs: { - parameters: { - query?: { - /** @description Maximum number of items to return */ - limit?: number; - /** @description Number of items to skip */ - offset?: number; - /** @description ID of the resource */ - resourceId?: string; - /** @description ID of the environment */ - environmentId?: string; - /** @description ID of the deployment */ - deploymentId?: string; - }; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Paginated list of items */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - items: components["schemas"]["Job"][]; - /** @description Maximum number of items returned */ - limit: number; - /** @description Number of items skipped */ - offset: number; - /** @description Total number of items available */ - total: number; - }; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getJob: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description ID of the job */ - jobId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Get job */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Job"]; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getJobWithRelease: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description ID of the job */ - jobId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Get job with release */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["JobWithRelease"]; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - listPolicies: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description A list of policies */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - policies?: components["schemas"]["Policy"][]; - }; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - evaluatePolicies: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - }; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["EvaluationScope"]; - }; - }; - responses: { - /** @description OK response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - decision?: components["schemas"]["DeployDecision"]; - }; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getPolicy: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description ID of the policy */ - policyId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description The requested policy */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Policy"]; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getReleaseTargetsForPolicy: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description ID of the policy */ - policyId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description A list of release targets */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - releaseTargets?: components["schemas"]["ReleaseTarget"][]; - }; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getRule: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description ID of the policy */ - policyId: string; - /** @description ID of the rule */ - ruleId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description OK response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["PolicyRule"]; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - listPolicySkips: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description A list of policy skips */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - skips?: components["schemas"]["PolicySkip"][]; - }; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getPolicySkipsForEnvironmentAndVersion: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description ID of the environment */ - environmentId: string; - /** @description ID of the deployment version */ - deploymentVersionId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description A list of policy skips */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - items?: components["schemas"]["PolicySkip"][]; - }; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getPolicySkip: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description Policy skip ID */ - policySkipId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description The requested policy skip */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["PolicySkip"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getRelationshipRules: { - parameters: { - query?: { - /** @description Number of items to skip */ - offset?: number; - /** @description Maximum number of items to return */ - limit?: number; - }; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Paginated list of items */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - items: components["schemas"]["RelationshipRule"][]; - /** @description Maximum number of items returned */ - limit: number; - /** @description Number of items skipped */ - offset: number; - /** @description Total number of items available */ - total: number; - }; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getRelationshipRule: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description ID of the relationship rule */ - relationshipRuleId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description OK response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["RelationshipRule"]; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - evaluateReleaseTarget: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - }; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["EvaluateReleaseTargetRequest"]; - }; - }; - responses: { - /** @description Policy evaluation results for the release target */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - decision?: components["schemas"]["DeployDecision"]; - /** @description The number of policies evaluated */ - policiesEvaluated?: number; - }; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - previewReleaseTargetsForResource: { - parameters: { - query?: { - /** @description Maximum number of items to return */ - limit?: number; - /** @description Number of items to skip */ - offset?: number; - }; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - }; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["ResourcePreviewRequest"]; - }; - }; - responses: { - /** @description Paginated list of items */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - items: components["schemas"]["ReleaseTargetPreview"][]; - /** @description Maximum number of items returned */ - limit: number; - /** @description Number of items skipped */ - offset: number; - /** @description Total number of items available */ - total: number; - }; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getReleaseTargetStates: { - parameters: { - query?: { - /** @description Maximum number of items to return */ - limit?: number; - /** @description Number of items to skip */ - offset?: number; - }; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - }; - cookie?: never; - }; - requestBody: { - content: { - "application/json": { - deploymentId: string; - environmentId: string; - }; - }; - }; - responses: { - /** @description Paginated list of items */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - items: components["schemas"]["ReleaseTargetAndState"][]; - /** @description Maximum number of items returned */ - limit: number; - /** @description Number of items skipped */ - offset: number; - /** @description Total number of items available */ - total: number; - }; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getReleaseTargetDesiredRelease: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description Key of the release target */ - releaseTargetKey: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description The desired release for the release target */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - desiredRelease?: components["schemas"]["Release"]; - }; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getJobsForReleaseTarget: { - parameters: { - query?: { - /** @description Maximum number of items to return */ - limit?: number; - /** @description Number of items to skip */ - offset?: number; - /** @description CEL expression to filter the results */ - cel?: string; - }; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description Key of the release target */ - releaseTargetKey: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Paginated list of items */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - items: components["schemas"]["Job"][]; - /** @description Maximum number of items returned */ - limit: number; - /** @description Number of items skipped */ - offset: number; - /** @description Total number of items available */ - total: number; - }; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getPoliciesForReleaseTarget: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description Key of the release target */ - releaseTargetKey: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description A list of policies */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - policies?: components["schemas"]["Policy"][]; - }; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getReleaseTargetState: { - parameters: { - query?: { - /** @description Whether to bypass the cache */ - bypassCache?: boolean; - }; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description Key of the release target */ - releaseTargetKey: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description The state for the release target */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ReleaseTargetState"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getRelease: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description ID of the release */ - releaseId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description The requested release */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Release"]; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getReleaseVerifications: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description ID of the release */ - releaseId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description List of verifications for the release */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["JobVerification"][]; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getResourceProviders: { - parameters: { - query?: { - /** @description Maximum number of items to return */ - limit?: number; - /** @description Number of items to skip */ - offset?: number; - }; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Paginated list of items */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - items: components["schemas"]["ResourceProvider"][]; - /** @description Maximum number of items returned */ - limit: number; - /** @description Number of items skipped */ - offset: number; - /** @description Total number of items available */ - total: number; - }; - }; - }; - }; - }; - cacheBatch: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - }; - cookie?: never; - }; - requestBody: { - content: { - "application/json": { - /** @description The ID of the resource provider */ - providerId: string; - /** @description Array of resources to cache */ - resources: components["schemas"]["Resource"][]; - }; - }; - }; - responses: { - /** @description Batch cached successfully */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @description Unique ID for this cached batch */ - batchId?: string; - /** @description Number of resources cached */ - resourceCount?: number; - }; - }; - }; - }; - }; - getResourceProviderByName: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description Name of the resource provider */ - name: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description OK response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ResourceProvider"]; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getKindsForWorkspace: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description The requested kinds */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": string[]; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - queryResources: { - parameters: { - query?: { - /** @description Maximum number of items to return */ - limit?: number; - /** @description Number of items to skip */ - offset?: number; - }; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - }; - cookie?: never; - }; - requestBody: { - content: { - "application/json": { - filter?: components["schemas"]["Selector"]; - }; - }; - }; - responses: { - /** @description Paginated list of items */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - items: components["schemas"]["Resource"][]; - /** @description Maximum number of items returned */ - limit: number; - /** @description Number of items skipped */ - offset: number; - /** @description Total number of items available */ - total: number; - }; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getResourceByIdentifier: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description Identifier of the resource */ - resourceIdentifier: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description The requested resource */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Resource"]; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getDeploymentsForResource: { - parameters: { - query?: { - /** @description Maximum number of items to return */ - limit?: number; - /** @description Number of items to skip */ - offset?: number; - }; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description Identifier of the resource */ - resourceIdentifier: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Paginated list of items */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - items: components["schemas"]["Deployment"][]; - /** @description Maximum number of items returned */ - limit: number; - /** @description Number of items skipped */ - offset: number; - /** @description Total number of items available */ - total: number; - }; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getRelationshipsForResource: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description Identifier of the resource */ - resourceIdentifier: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description The requested relationships */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - [key: string]: components["schemas"]["EntityRelation"][]; - }; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getReleaseTargetsForResource: { - parameters: { - query?: { - /** @description Maximum number of items to return */ - limit?: number; - /** @description Number of items to skip */ - offset?: number; - }; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description Identifier of the resource */ - resourceIdentifier: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Paginated list of items */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - items: components["schemas"]["ReleaseTargetWithState"][]; - /** @description Maximum number of items returned */ - limit: number; - /** @description Number of items skipped */ - offset: number; - /** @description Total number of items available */ - total: number; - }; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getReleaseTargetForResourceInDeployment: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description Identifier of the resource */ - resourceIdentifier: string; - /** @description ID of the deployment */ - deploymentId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description The requested release target */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ReleaseTarget"]; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getVariablesForResource: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description Identifier of the resource */ - resourceIdentifier: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description The requested variables */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ResourceVariable"][]; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getEngineStatus: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description The status of the engine */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - healthy?: boolean; - message?: string; - }; - }; - }; - }; - }; - listSystems: { - parameters: { - query?: { - /** @description Number of items to skip */ - offset?: number; - /** @description Maximum number of items to return */ - limit?: number; - }; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description A list of systems */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - items: components["schemas"]["System"][]; - /** @description Maximum number of items returned */ - limit: number; - /** @description Number of items skipped */ - offset: number; - /** @description Total number of items available */ - total: number; - }; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getSystem: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description ID of the system */ - systemId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description The requested system with its environments and deployments */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @description Deployments associated with the system */ - deployments: components["schemas"]["Deployment"][]; - /** @description Environments associated with the system */ - environments: components["schemas"]["Environment"][]; - system: components["schemas"]["System"]; - }; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getDeploymentSystemLink: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description ID of the system */ - systemId: string; - /** @description ID of the deployment */ - deploymentId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description The requested deployment system link */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["SystemDeploymentLink"]; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getEnvironmentSystemLink: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description ID of the system */ - systemId: string; - /** @description ID of the environment */ - environmentId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description The requested environment system link */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["SystemEnvironmentLink"]; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - listWorkflows: { - parameters: { - query?: { - /** @description Maximum number of items to return */ - limit?: number; - /** @description Number of items to skip */ - offset?: number; - }; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Paginated list of items */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - items: components["schemas"]["Workflow"][]; - /** @description Maximum number of items returned */ - limit: number; - /** @description Number of items skipped */ - offset: number; - /** @description Total number of items available */ - total: number; - }; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getWorkflow: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description ID of the workflow */ - workflowId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Get workflow */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Workflow"]; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getWorkflowRuns: { - parameters: { - query?: { - /** @description Maximum number of items to return */ - limit?: number; - /** @description Number of items to skip */ - offset?: number; - }; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description ID of the workflow */ - workflowId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Paginated list of items */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - items: components["schemas"]["WorkflowRunWithJobs"][]; - /** @description Maximum number of items returned */ - limit: number; - /** @description Number of items skipped */ - offset: number; - /** @description Total number of items available */ - total: number; - }; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description ID of the system */ + systemId: string; + /** @description ID of the deployment */ + deploymentId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The requested deployment system link */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SystemDeploymentLink"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getEnvironmentSystemLink: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description ID of the system */ + systemId: string; + /** @description ID of the environment */ + environmentId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The requested environment system link */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SystemEnvironmentLink"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + listWorkflows: { + parameters: { + query?: { + /** @description Maximum number of items to return */ + limit?: number; + /** @description Number of items to skip */ + offset?: number; + }; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Paginated list of items */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["Workflow"][]; + /** @description Maximum number of items returned */ + limit: number; + /** @description Number of items skipped */ + offset: number; + /** @description Total number of items available */ + total: number; + }; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getWorkflow: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description ID of the workflow */ + workflowId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Get workflow */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Workflow"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getWorkflowRuns: { + parameters: { + query?: { + /** @description Maximum number of items to return */ + limit?: number; + /** @description Number of items to skip */ + offset?: number; + }; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description ID of the workflow */ + workflowId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Paginated list of items */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["WorkflowRunWithJobs"][]; + /** @description Maximum number of items returned */ + limit: number; + /** @description Number of items skipped */ + offset: number; + /** @description Total number of items available */ + total: number; + }; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; }