From 786a1e93311f88efe7e062674bd4031249edfb8a Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Thu, 7 May 2026 11:47:14 -0400 Subject: [PATCH 01/69] fix: small optimization, no need to query if there are no claims --- pkg/repository/idempotency.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pkg/repository/idempotency.go b/pkg/repository/idempotency.go index 6323986037..5024e35ceb 100644 --- a/pkg/repository/idempotency.go +++ b/pkg/repository/idempotency.go @@ -45,6 +45,12 @@ type KeyClaimantPair struct { } func claimIdempotencyKeys(context context.Context, queries *sqlcv1.Queries, tx sqlcv1.DBTX, tenantId uuid.UUID, claims []KeyClaimantPair) (map[KeyClaimantPair]WasSuccessfullyClaimed, error) { + keyToClaimStatus := make(map[KeyClaimantPair]WasSuccessfullyClaimed) + + if len(claims) == 0 { + return keyToClaimStatus, nil + } + keys := make([]string, len(claims)) claimedByExternalIds := make([]uuid.UUID, len(claims)) @@ -63,8 +69,6 @@ func claimIdempotencyKeys(context context.Context, queries *sqlcv1.Queries, tx s return nil, err } - keyToClaimStatus := make(map[KeyClaimantPair]WasSuccessfullyClaimed) - for _, claimResult := range claimResults { if claimResult.ClaimedByExternalID == nil { continue From 09b749e0da088c23eefff3b80d80716cd0690f17 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Thu, 7 May 2026 14:06:24 -0400 Subject: [PATCH 02/69] feat: add idempotency key expr to proto, workflow version --- api-contracts/v1/workflows.proto | 1 + .../migrations/20260507180517_v1_0_106.sql | 9 ++++ .../services/shared/proto/v1/workflows.pb.go | 43 ++++++++++++------- pkg/repository/sqlcv1/models.go | 1 + pkg/repository/sqlcv1/workflows.sql.go | 12 ++++-- pkg/repository/workflow.go | 2 + sql/schema/v0.sql | 1 + 7 files changed, 50 insertions(+), 19 deletions(-) create mode 100644 cmd/hatchet-migrate/migrate/migrations/20260507180517_v1_0_106.sql diff --git a/api-contracts/v1/workflows.proto b/api-contracts/v1/workflows.proto index e155664f61..5dc5a1b4d6 100644 --- a/api-contracts/v1/workflows.proto +++ b/api-contracts/v1/workflows.proto @@ -111,6 +111,7 @@ message CreateWorkflowVersionRequest { repeated Concurrency concurrency_arr = 12; // (optional) the workflow concurrency options repeated DefaultFilter default_filters = 13; // (optional) the default filters for the workflow optional bytes input_json_schema = 14; // (optional) the JSON schema for the workflow input + optional string idempotency_key_expr = 15; // (optional) a CEL expression for determining the idempotency key for workflow runs } diff --git a/cmd/hatchet-migrate/migrate/migrations/20260507180517_v1_0_106.sql b/cmd/hatchet-migrate/migrate/migrations/20260507180517_v1_0_106.sql new file mode 100644 index 0000000000..1b535fbea7 --- /dev/null +++ b/cmd/hatchet-migrate/migrate/migrations/20260507180517_v1_0_106.sql @@ -0,0 +1,9 @@ +-- +goose Up +-- +goose StatementBegin +ALTER TABLE "WorkflowVersion" ADD COLUMN "idempotencyKeyExpression" TEXT; +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +ALTER TABLE "WorkflowVersion" DROP COLUMN "idempotencyKeyExpression"; +-- +goose StatementEnd diff --git a/internal/services/shared/proto/v1/workflows.pb.go b/internal/services/shared/proto/v1/workflows.pb.go index bf3d29cb9a..042ecf1ff9 100644 --- a/internal/services/shared/proto/v1/workflows.pb.go +++ b/internal/services/shared/proto/v1/workflows.pb.go @@ -789,14 +789,15 @@ type CreateWorkflowVersionRequest struct { CronTriggers []string `protobuf:"bytes,5,rep,name=cron_triggers,json=cronTriggers,proto3" json:"cron_triggers,omitempty"` // (optional) cron triggers for the workflow Tasks []*CreateTaskOpts `protobuf:"bytes,6,rep,name=tasks,proto3" json:"tasks,omitempty"` // (required) the workflow jobs // Deprecated: use concurrency_arr instead - Concurrency *Concurrency `protobuf:"bytes,7,opt,name=concurrency,proto3" json:"concurrency,omitempty"` // (optional) the workflow concurrency options - CronInput *string `protobuf:"bytes,8,opt,name=cron_input,json=cronInput,proto3,oneof" json:"cron_input,omitempty"` // (optional) the input for the cron trigger - OnFailureTask *CreateTaskOpts `protobuf:"bytes,9,opt,name=on_failure_task,json=onFailureTask,proto3,oneof" json:"on_failure_task,omitempty"` // (optional) the job to run on failure - Sticky *StickyStrategy `protobuf:"varint,10,opt,name=sticky,proto3,enum=v1.StickyStrategy,oneof" json:"sticky,omitempty"` // (optional) the sticky strategy for assigning tasks to workers - DefaultPriority *int32 `protobuf:"varint,11,opt,name=default_priority,json=defaultPriority,proto3,oneof" json:"default_priority,omitempty"` // (optional) the default priority for the workflow - ConcurrencyArr []*Concurrency `protobuf:"bytes,12,rep,name=concurrency_arr,json=concurrencyArr,proto3" json:"concurrency_arr,omitempty"` // (optional) the workflow concurrency options - DefaultFilters []*DefaultFilter `protobuf:"bytes,13,rep,name=default_filters,json=defaultFilters,proto3" json:"default_filters,omitempty"` // (optional) the default filters for the workflow - InputJsonSchema []byte `protobuf:"bytes,14,opt,name=input_json_schema,json=inputJsonSchema,proto3,oneof" json:"input_json_schema,omitempty"` // (optional) the JSON schema for the workflow input + Concurrency *Concurrency `protobuf:"bytes,7,opt,name=concurrency,proto3" json:"concurrency,omitempty"` // (optional) the workflow concurrency options + CronInput *string `protobuf:"bytes,8,opt,name=cron_input,json=cronInput,proto3,oneof" json:"cron_input,omitempty"` // (optional) the input for the cron trigger + OnFailureTask *CreateTaskOpts `protobuf:"bytes,9,opt,name=on_failure_task,json=onFailureTask,proto3,oneof" json:"on_failure_task,omitempty"` // (optional) the job to run on failure + Sticky *StickyStrategy `protobuf:"varint,10,opt,name=sticky,proto3,enum=v1.StickyStrategy,oneof" json:"sticky,omitempty"` // (optional) the sticky strategy for assigning tasks to workers + DefaultPriority *int32 `protobuf:"varint,11,opt,name=default_priority,json=defaultPriority,proto3,oneof" json:"default_priority,omitempty"` // (optional) the default priority for the workflow + ConcurrencyArr []*Concurrency `protobuf:"bytes,12,rep,name=concurrency_arr,json=concurrencyArr,proto3" json:"concurrency_arr,omitempty"` // (optional) the workflow concurrency options + DefaultFilters []*DefaultFilter `protobuf:"bytes,13,rep,name=default_filters,json=defaultFilters,proto3" json:"default_filters,omitempty"` // (optional) the default filters for the workflow + InputJsonSchema []byte `protobuf:"bytes,14,opt,name=input_json_schema,json=inputJsonSchema,proto3,oneof" json:"input_json_schema,omitempty"` // (optional) the JSON schema for the workflow input + IdempotencyKeyExpr *string `protobuf:"bytes,15,opt,name=idempotency_key_expr,json=idempotencyKeyExpr,proto3,oneof" json:"idempotency_key_expr,omitempty"` // (optional) a CEL expression for determining the idempotency key for workflow runs } func (x *CreateWorkflowVersionRequest) Reset() { @@ -929,6 +930,13 @@ func (x *CreateWorkflowVersionRequest) GetInputJsonSchema() []byte { return nil } +func (x *CreateWorkflowVersionRequest) GetIdempotencyKeyExpr() string { + if x != nil && x.IdempotencyKeyExpr != nil { + return *x.IdempotencyKeyExpr + } + return "" +} + type DefaultFilter struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1670,7 +1678,7 @@ var file_v1_workflows_proto_rawDesc = []byte{ 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x6e, 0x6f, 0x64, 0x65, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x49, 0x64, 0x22, - 0xdd, 0x05, 0x0a, 0x1c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, + 0xad, 0x06, 0x0a, 0x1c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, @@ -1710,12 +1718,17 @@ var file_v1_workflows_proto_rawDesc = []byte{ 0x6c, 0x74, 0x65, 0x72, 0x73, 0x12, 0x2f, 0x0a, 0x11, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x04, 0x52, 0x0f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x4a, 0x73, 0x6f, 0x6e, 0x53, 0x63, 0x68, - 0x65, 0x6d, 0x61, 0x88, 0x01, 0x01, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x63, 0x72, 0x6f, 0x6e, 0x5f, - 0x69, 0x6e, 0x70, 0x75, 0x74, 0x42, 0x12, 0x0a, 0x10, 0x5f, 0x6f, 0x6e, 0x5f, 0x66, 0x61, 0x69, - 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x74, 0x61, 0x73, 0x6b, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x73, 0x74, - 0x69, 0x63, 0x6b, 0x79, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, - 0x5f, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x42, 0x14, 0x0a, 0x12, 0x5f, 0x69, 0x6e, - 0x70, 0x75, 0x74, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x22, + 0x65, 0x6d, 0x61, 0x88, 0x01, 0x01, 0x12, 0x35, 0x0a, 0x14, 0x69, 0x64, 0x65, 0x6d, 0x70, 0x6f, + 0x74, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x65, 0x78, 0x70, 0x72, 0x18, 0x0f, + 0x20, 0x01, 0x28, 0x09, 0x48, 0x05, 0x52, 0x12, 0x69, 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, 0x65, + 0x6e, 0x63, 0x79, 0x4b, 0x65, 0x79, 0x45, 0x78, 0x70, 0x72, 0x88, 0x01, 0x01, 0x42, 0x0d, 0x0a, + 0x0b, 0x5f, 0x63, 0x72, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x42, 0x12, 0x0a, 0x10, + 0x5f, 0x6f, 0x6e, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x74, 0x61, 0x73, 0x6b, + 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x73, 0x74, 0x69, 0x63, 0x6b, 0x79, 0x42, 0x13, 0x0a, 0x11, 0x5f, + 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, + 0x42, 0x14, 0x0a, 0x12, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x5f, + 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x42, 0x17, 0x0a, 0x15, 0x5f, 0x69, 0x64, 0x65, 0x6d, 0x70, + 0x6f, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x65, 0x78, 0x70, 0x72, 0x22, 0x70, 0x0a, 0x0d, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, diff --git a/pkg/repository/sqlcv1/models.go b/pkg/repository/sqlcv1/models.go index cc1defa700..8b24b16375 100644 --- a/pkg/repository/sqlcv1/models.go +++ b/pkg/repository/sqlcv1/models.go @@ -3997,4 +3997,5 @@ type WorkflowVersion struct { DefaultPriority pgtype.Int4 `json:"defaultPriority"` CreateWorkflowVersionOpts []byte `json:"createWorkflowVersionOpts"` InputJsonSchema []byte `json:"inputJsonSchema"` + IdempotencyKeyExpression pgtype.Text `json:"idempotencyKeyExpression"` } diff --git a/pkg/repository/sqlcv1/workflows.sql.go b/pkg/repository/sqlcv1/workflows.sql.go index 5006f9590c..0bdad4eaba 100644 --- a/pkg/repository/sqlcv1/workflows.sql.go +++ b/pkg/repository/sqlcv1/workflows.sql.go @@ -929,7 +929,7 @@ INSERT INTO "WorkflowVersion" ( $10 :: integer, $11::jsonb, $12::jsonb -) RETURNING id, "createdAt", "updatedAt", "deletedAt", version, "order", "workflowId", checksum, "scheduleTimeout", "onFailureJobId", sticky, kind, "defaultPriority", "createWorkflowVersionOpts", "inputJsonSchema" +) RETURNING id, "createdAt", "updatedAt", "deletedAt", version, "order", "workflowId", checksum, "scheduleTimeout", "onFailureJobId", sticky, kind, "defaultPriority", "createWorkflowVersionOpts", "inputJsonSchema", "idempotencyKeyExpression" ` type CreateWorkflowVersionParams struct { @@ -979,6 +979,7 @@ func (q *Queries) CreateWorkflowVersion(ctx context.Context, db DBTX, arg Create &i.DefaultPriority, &i.CreateWorkflowVersionOpts, &i.InputJsonSchema, + &i.IdempotencyKeyExpression, ) return &i, err } @@ -1219,7 +1220,7 @@ func (q *Queries) GetWorkflowShape(ctx context.Context, db DBTX, workflowversion const getWorkflowVersionById = `-- name: GetWorkflowVersionById :one SELECT - wv.id, wv."createdAt", wv."updatedAt", wv."deletedAt", wv.version, wv."order", wv."workflowId", wv.checksum, wv."scheduleTimeout", wv."onFailureJobId", wv.sticky, wv.kind, wv."defaultPriority", wv."createWorkflowVersionOpts", wv."inputJsonSchema", + wv.id, wv."createdAt", wv."updatedAt", wv."deletedAt", wv.version, wv."order", wv."workflowId", wv.checksum, wv."scheduleTimeout", wv."onFailureJobId", wv.sticky, wv.kind, wv."defaultPriority", wv."createWorkflowVersionOpts", wv."inputJsonSchema", wv."idempotencyKeyExpression", w.id, w."createdAt", w."updatedAt", w."deletedAt", w."tenantId", w.name, w.description, w."isPaused" FROM "WorkflowVersion" as wv @@ -1254,6 +1255,7 @@ func (q *Queries) GetWorkflowVersionById(ctx context.Context, db DBTX, id uuid.U &i.WorkflowVersion.DefaultPriority, &i.WorkflowVersion.CreateWorkflowVersionOpts, &i.WorkflowVersion.InputJsonSchema, + &i.WorkflowVersion.IdempotencyKeyExpression, &i.Workflow.ID, &i.Workflow.CreatedAt, &i.Workflow.UpdatedAt, @@ -1342,7 +1344,7 @@ func (q *Queries) GetWorkflowVersionEventTriggerRefs(ctx context.Context, db DBT const getWorkflowVersionForEngine = `-- name: GetWorkflowVersionForEngine :many SELECT - workflowversions.id, workflowversions."createdAt", workflowversions."updatedAt", workflowversions."deletedAt", workflowversions.version, workflowversions."order", workflowversions."workflowId", workflowversions.checksum, workflowversions."scheduleTimeout", workflowversions."onFailureJobId", workflowversions.sticky, workflowversions.kind, workflowversions."defaultPriority", workflowversions."createWorkflowVersionOpts", workflowversions."inputJsonSchema", + workflowversions.id, workflowversions."createdAt", workflowversions."updatedAt", workflowversions."deletedAt", workflowversions.version, workflowversions."order", workflowversions."workflowId", workflowversions.checksum, workflowversions."scheduleTimeout", workflowversions."onFailureJobId", workflowversions.sticky, workflowversions.kind, workflowversions."defaultPriority", workflowversions."createWorkflowVersionOpts", workflowversions."inputJsonSchema", workflowversions."idempotencyKeyExpression", w."name" as "workflowName", wc."limitStrategy" as "concurrencyLimitStrategy", wc."maxRuns" as "concurrencyMaxRuns", @@ -1400,6 +1402,7 @@ func (q *Queries) GetWorkflowVersionForEngine(ctx context.Context, db DBTX, arg &i.WorkflowVersion.DefaultPriority, &i.WorkflowVersion.CreateWorkflowVersionOpts, &i.WorkflowVersion.InputJsonSchema, + &i.WorkflowVersion.IdempotencyKeyExpression, &i.WorkflowName, &i.ConcurrencyLimitStrategy, &i.ConcurrencyMaxRuns, @@ -1466,7 +1469,7 @@ const linkOnFailureJob = `-- name: LinkOnFailureJob :one UPDATE "WorkflowVersion" SET "onFailureJobId" = $1::uuid WHERE "id" = $2::uuid -RETURNING id, "createdAt", "updatedAt", "deletedAt", version, "order", "workflowId", checksum, "scheduleTimeout", "onFailureJobId", sticky, kind, "defaultPriority", "createWorkflowVersionOpts", "inputJsonSchema" +RETURNING id, "createdAt", "updatedAt", "deletedAt", version, "order", "workflowId", checksum, "scheduleTimeout", "onFailureJobId", sticky, kind, "defaultPriority", "createWorkflowVersionOpts", "inputJsonSchema", "idempotencyKeyExpression" ` type LinkOnFailureJobParams struct { @@ -1493,6 +1496,7 @@ func (q *Queries) LinkOnFailureJob(ctx context.Context, db DBTX, arg LinkOnFailu &i.DefaultPriority, &i.CreateWorkflowVersionOpts, &i.InputJsonSchema, + &i.IdempotencyKeyExpression, ) return &i, err } diff --git a/pkg/repository/workflow.go b/pkg/repository/workflow.go index c5da84e117..a75c446cb3 100644 --- a/pkg/repository/workflow.go +++ b/pkg/repository/workflow.go @@ -56,6 +56,8 @@ type CreateWorkflowVersionOpts struct { DefaultFilters []types.DefaultFilter `json:"defaultFilters,omitempty" validate:"omitempty,dive"` InputJsonSchema []byte `json:"inputJsonSchema,omitempty"` + + IdempotencyKeyExpr *string `json:"idempotencyKeyExpr,omitempty" validate:"omitempty,celworkflowrunstr"` } type CreateConcurrencyOpts struct { diff --git a/sql/schema/v0.sql b/sql/schema/v0.sql index 4b2d657fb4..da40e62625 100644 --- a/sql/schema/v0.sql +++ b/sql/schema/v0.sql @@ -1085,6 +1085,7 @@ CREATE TABLE "defaultPriority" INTEGER, "createWorkflowVersionOpts" JSONB, "inputJsonSchema" JSONB, + "idempotencyKeyExpression" TEXT, CONSTRAINT "WorkflowVersion_pkey" PRIMARY KEY ("id") ); From 85c9c1f3149218ff9c7bc748047df355c195f61d Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Thu, 7 May 2026 14:07:45 -0400 Subject: [PATCH 03/69] feat: wiring --- pkg/repository/sqlcv1/workflows.sql | 6 ++++-- pkg/repository/sqlcv1/workflows.sql.go | 8 ++++++-- pkg/repository/workflow.go | 7 +++++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/pkg/repository/sqlcv1/workflows.sql b/pkg/repository/sqlcv1/workflows.sql index d77b3638cf..b6b7112eb4 100644 --- a/pkg/repository/sqlcv1/workflows.sql +++ b/pkg/repository/sqlcv1/workflows.sql @@ -121,7 +121,8 @@ INSERT INTO "WorkflowVersion" ( "kind", "defaultPriority", "createWorkflowVersionOpts", - "inputJsonSchema" + "inputJsonSchema", + "idempotencyKeyExpression" ) VALUES ( @id::uuid, coalesce(sqlc.narg('createdAt')::timestamp, CURRENT_TIMESTAMP), @@ -136,7 +137,8 @@ INSERT INTO "WorkflowVersion" ( coalesce(sqlc.narg('kind')::"WorkflowKind", 'DAG'), sqlc.narg('defaultPriority') :: integer, sqlc.narg('createWorkflowVersionOpts')::jsonb, - sqlc.narg('inputJsonSchema')::jsonb + sqlc.narg('inputJsonSchema')::jsonb, + sqlc.narg('idempotencyKeyExpression')::text ) RETURNING *; -- name: CreateJob :one diff --git a/pkg/repository/sqlcv1/workflows.sql.go b/pkg/repository/sqlcv1/workflows.sql.go index 0bdad4eaba..01fa20a953 100644 --- a/pkg/repository/sqlcv1/workflows.sql.go +++ b/pkg/repository/sqlcv1/workflows.sql.go @@ -913,7 +913,8 @@ INSERT INTO "WorkflowVersion" ( "kind", "defaultPriority", "createWorkflowVersionOpts", - "inputJsonSchema" + "inputJsonSchema", + "idempotencyKeyExpression" ) VALUES ( $1::uuid, coalesce($2::timestamp, CURRENT_TIMESTAMP), @@ -928,7 +929,8 @@ INSERT INTO "WorkflowVersion" ( coalesce($9::"WorkflowKind", 'DAG'), $10 :: integer, $11::jsonb, - $12::jsonb + $12::jsonb, + $13::text ) RETURNING id, "createdAt", "updatedAt", "deletedAt", version, "order", "workflowId", checksum, "scheduleTimeout", "onFailureJobId", sticky, kind, "defaultPriority", "createWorkflowVersionOpts", "inputJsonSchema", "idempotencyKeyExpression" ` @@ -945,6 +947,7 @@ type CreateWorkflowVersionParams struct { DefaultPriority pgtype.Int4 `json:"defaultPriority"` CreateWorkflowVersionOpts []byte `json:"createWorkflowVersionOpts"` InputJsonSchema []byte `json:"inputJsonSchema"` + IdempotencyKeyExpression pgtype.Text `json:"idempotencyKeyExpression"` } func (q *Queries) CreateWorkflowVersion(ctx context.Context, db DBTX, arg CreateWorkflowVersionParams) (*WorkflowVersion, error) { @@ -961,6 +964,7 @@ func (q *Queries) CreateWorkflowVersion(ctx context.Context, db DBTX, arg Create arg.DefaultPriority, arg.CreateWorkflowVersionOpts, arg.InputJsonSchema, + arg.IdempotencyKeyExpression, ) var i WorkflowVersion err := row.Scan( diff --git a/pkg/repository/workflow.go b/pkg/repository/workflow.go index a75c446cb3..a0558bb32c 100644 --- a/pkg/repository/workflow.go +++ b/pkg/repository/workflow.go @@ -429,6 +429,13 @@ func (r *workflowRepository) createWorkflowVersionTxs(ctx context.Context, tx sq } } + if opts.IdempotencyKeyExpr != nil { + createParams.IdempotencyKeyExpression = pgtype.Text{ + String: *opts.IdempotencyKeyExpr, + Valid: true, + } + } + if opts.DefaultPriority != nil { createParams.DefaultPriority = pgtype.Int4{ Int32: *opts.DefaultPriority, From 912d471e4c66c5d77a02693531d604db5f26f12e Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Thu, 7 May 2026 14:23:15 -0400 Subject: [PATCH 04/69] fix: wiring to db --- internal/services/admin/v1/server.go | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/internal/services/admin/v1/server.go b/internal/services/admin/v1/server.go index 6873d51c2c..ebd4d822c0 100644 --- a/internal/services/admin/v1/server.go +++ b/internal/services/admin/v1/server.go @@ -898,18 +898,19 @@ func getCreateWorkflowOpts(req *contracts.CreateWorkflowVersionRequest) (*v1.Cre } return &v1.CreateWorkflowVersionOpts{ - Name: req.Name, - Concurrency: concurrency, - Description: &req.Description, - EventTriggers: req.EventTriggers, - CronTriggers: req.CronTriggers, - CronInput: cronInput, - Tasks: tasks, - OnFailure: onFailureTask, - Sticky: sticky, - DefaultPriority: req.DefaultPriority, - DefaultFilters: defaultFilters, - InputJsonSchema: req.InputJsonSchema, + Name: req.Name, + Concurrency: concurrency, + Description: &req.Description, + EventTriggers: req.EventTriggers, + CronTriggers: req.CronTriggers, + CronInput: cronInput, + Tasks: tasks, + OnFailure: onFailureTask, + Sticky: sticky, + DefaultPriority: req.DefaultPriority, + DefaultFilters: defaultFilters, + InputJsonSchema: req.InputJsonSchema, + IdempotencyKeyExpr: req.IdempotencyKeyExpr, }, nil } From 247dd6ccf5ca416da51962039a62a73cd4379503 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Thu, 7 May 2026 14:30:16 -0400 Subject: [PATCH 05/69] feat: add ttl col --- api-contracts/v1/workflows.proto | 8 +- .../migrations/20260507180517_v1_0_106.sql | 10 +- .../services/shared/proto/v1/workflows.pb.go | 683 ++++++++++-------- pkg/repository/sqlcv1/workflows.sql | 6 +- pkg/repository/sqlcv1/workflows.sql.go | 8 +- pkg/repository/workflow.go | 13 +- 6 files changed, 413 insertions(+), 315 deletions(-) diff --git a/api-contracts/v1/workflows.proto b/api-contracts/v1/workflows.proto index 5dc5a1b4d6..794a8c61cc 100644 --- a/api-contracts/v1/workflows.proto +++ b/api-contracts/v1/workflows.proto @@ -111,7 +111,13 @@ message CreateWorkflowVersionRequest { repeated Concurrency concurrency_arr = 12; // (optional) the workflow concurrency options repeated DefaultFilter default_filters = 13; // (optional) the default filters for the workflow optional bytes input_json_schema = 14; // (optional) the JSON schema for the workflow input - optional string idempotency_key_expr = 15; // (optional) a CEL expression for determining the idempotency key for workflow runs + + optional IdempotencyConfig idempotency = 15; // (optional) idempotency configuration for the workflow +} + +message IdempotencyConfig { + string expression = 1; // a CEL expression for determining the idempotency key for workflow runs + int64 ttl_ms = 2; // time-to-live for idempotency keys in milliseconds } diff --git a/cmd/hatchet-migrate/migrate/migrations/20260507180517_v1_0_106.sql b/cmd/hatchet-migrate/migrate/migrations/20260507180517_v1_0_106.sql index 1b535fbea7..b9fa9354ac 100644 --- a/cmd/hatchet-migrate/migrate/migrations/20260507180517_v1_0_106.sql +++ b/cmd/hatchet-migrate/migrate/migrations/20260507180517_v1_0_106.sql @@ -1,9 +1,15 @@ -- +goose Up -- +goose StatementBegin -ALTER TABLE "WorkflowVersion" ADD COLUMN "idempotencyKeyExpression" TEXT; +ALTER TABLE "WorkflowVersion" + ADD COLUMN "idempotencyKeyExpression" TEXT, + ADD COLUMN "idempotencyKeyTtl" BIGINT + ; -- +goose StatementEnd -- +goose Down -- +goose StatementBegin -ALTER TABLE "WorkflowVersion" DROP COLUMN "idempotencyKeyExpression"; +ALTER TABLE "WorkflowVersion" + DROP COLUMN "idempotencyKeyExpression", + DROP COLUMN "idempotencyKeyTtl" +; -- +goose StatementEnd diff --git a/internal/services/shared/proto/v1/workflows.pb.go b/internal/services/shared/proto/v1/workflows.pb.go index 042ecf1ff9..889f100fb8 100644 --- a/internal/services/shared/proto/v1/workflows.pb.go +++ b/internal/services/shared/proto/v1/workflows.pb.go @@ -789,15 +789,15 @@ type CreateWorkflowVersionRequest struct { CronTriggers []string `protobuf:"bytes,5,rep,name=cron_triggers,json=cronTriggers,proto3" json:"cron_triggers,omitempty"` // (optional) cron triggers for the workflow Tasks []*CreateTaskOpts `protobuf:"bytes,6,rep,name=tasks,proto3" json:"tasks,omitempty"` // (required) the workflow jobs // Deprecated: use concurrency_arr instead - Concurrency *Concurrency `protobuf:"bytes,7,opt,name=concurrency,proto3" json:"concurrency,omitempty"` // (optional) the workflow concurrency options - CronInput *string `protobuf:"bytes,8,opt,name=cron_input,json=cronInput,proto3,oneof" json:"cron_input,omitempty"` // (optional) the input for the cron trigger - OnFailureTask *CreateTaskOpts `protobuf:"bytes,9,opt,name=on_failure_task,json=onFailureTask,proto3,oneof" json:"on_failure_task,omitempty"` // (optional) the job to run on failure - Sticky *StickyStrategy `protobuf:"varint,10,opt,name=sticky,proto3,enum=v1.StickyStrategy,oneof" json:"sticky,omitempty"` // (optional) the sticky strategy for assigning tasks to workers - DefaultPriority *int32 `protobuf:"varint,11,opt,name=default_priority,json=defaultPriority,proto3,oneof" json:"default_priority,omitempty"` // (optional) the default priority for the workflow - ConcurrencyArr []*Concurrency `protobuf:"bytes,12,rep,name=concurrency_arr,json=concurrencyArr,proto3" json:"concurrency_arr,omitempty"` // (optional) the workflow concurrency options - DefaultFilters []*DefaultFilter `protobuf:"bytes,13,rep,name=default_filters,json=defaultFilters,proto3" json:"default_filters,omitempty"` // (optional) the default filters for the workflow - InputJsonSchema []byte `protobuf:"bytes,14,opt,name=input_json_schema,json=inputJsonSchema,proto3,oneof" json:"input_json_schema,omitempty"` // (optional) the JSON schema for the workflow input - IdempotencyKeyExpr *string `protobuf:"bytes,15,opt,name=idempotency_key_expr,json=idempotencyKeyExpr,proto3,oneof" json:"idempotency_key_expr,omitempty"` // (optional) a CEL expression for determining the idempotency key for workflow runs + Concurrency *Concurrency `protobuf:"bytes,7,opt,name=concurrency,proto3" json:"concurrency,omitempty"` // (optional) the workflow concurrency options + CronInput *string `protobuf:"bytes,8,opt,name=cron_input,json=cronInput,proto3,oneof" json:"cron_input,omitempty"` // (optional) the input for the cron trigger + OnFailureTask *CreateTaskOpts `protobuf:"bytes,9,opt,name=on_failure_task,json=onFailureTask,proto3,oneof" json:"on_failure_task,omitempty"` // (optional) the job to run on failure + Sticky *StickyStrategy `protobuf:"varint,10,opt,name=sticky,proto3,enum=v1.StickyStrategy,oneof" json:"sticky,omitempty"` // (optional) the sticky strategy for assigning tasks to workers + DefaultPriority *int32 `protobuf:"varint,11,opt,name=default_priority,json=defaultPriority,proto3,oneof" json:"default_priority,omitempty"` // (optional) the default priority for the workflow + ConcurrencyArr []*Concurrency `protobuf:"bytes,12,rep,name=concurrency_arr,json=concurrencyArr,proto3" json:"concurrency_arr,omitempty"` // (optional) the workflow concurrency options + DefaultFilters []*DefaultFilter `protobuf:"bytes,13,rep,name=default_filters,json=defaultFilters,proto3" json:"default_filters,omitempty"` // (optional) the default filters for the workflow + InputJsonSchema []byte `protobuf:"bytes,14,opt,name=input_json_schema,json=inputJsonSchema,proto3,oneof" json:"input_json_schema,omitempty"` // (optional) the JSON schema for the workflow input + Idempotency *IdempotencyConfig `protobuf:"bytes,15,opt,name=idempotency,proto3,oneof" json:"idempotency,omitempty"` // (optional) idempotency configuration for the workflow } func (x *CreateWorkflowVersionRequest) Reset() { @@ -930,13 +930,68 @@ func (x *CreateWorkflowVersionRequest) GetInputJsonSchema() []byte { return nil } -func (x *CreateWorkflowVersionRequest) GetIdempotencyKeyExpr() string { - if x != nil && x.IdempotencyKeyExpr != nil { - return *x.IdempotencyKeyExpr +func (x *CreateWorkflowVersionRequest) GetIdempotency() *IdempotencyConfig { + if x != nil { + return x.Idempotency + } + return nil +} + +type IdempotencyConfig struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Expression string `protobuf:"bytes,1,opt,name=expression,proto3" json:"expression,omitempty"` // a CEL expression for determining the idempotency key for workflow runs + TtlMs int64 `protobuf:"varint,2,opt,name=ttl_ms,json=ttlMs,proto3" json:"ttl_ms,omitempty"` // time-to-live for idempotency keys in milliseconds +} + +func (x *IdempotencyConfig) Reset() { + *x = IdempotencyConfig{} + if protoimpl.UnsafeEnabled { + mi := &file_v1_workflows_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *IdempotencyConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*IdempotencyConfig) ProtoMessage() {} + +func (x *IdempotencyConfig) ProtoReflect() protoreflect.Message { + mi := &file_v1_workflows_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use IdempotencyConfig.ProtoReflect.Descriptor instead. +func (*IdempotencyConfig) Descriptor() ([]byte, []int) { + return file_v1_workflows_proto_rawDescGZIP(), []int{10} +} + +func (x *IdempotencyConfig) GetExpression() string { + if x != nil { + return x.Expression } return "" } +func (x *IdempotencyConfig) GetTtlMs() int64 { + if x != nil { + return x.TtlMs + } + return 0 +} + type DefaultFilter struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -950,7 +1005,7 @@ type DefaultFilter struct { func (x *DefaultFilter) Reset() { *x = DefaultFilter{} if protoimpl.UnsafeEnabled { - mi := &file_v1_workflows_proto_msgTypes[10] + mi := &file_v1_workflows_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -963,7 +1018,7 @@ func (x *DefaultFilter) String() string { func (*DefaultFilter) ProtoMessage() {} func (x *DefaultFilter) ProtoReflect() protoreflect.Message { - mi := &file_v1_workflows_proto_msgTypes[10] + mi := &file_v1_workflows_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -976,7 +1031,7 @@ func (x *DefaultFilter) ProtoReflect() protoreflect.Message { // Deprecated: Use DefaultFilter.ProtoReflect.Descriptor instead. func (*DefaultFilter) Descriptor() ([]byte, []int) { - return file_v1_workflows_proto_rawDescGZIP(), []int{10} + return file_v1_workflows_proto_rawDescGZIP(), []int{11} } func (x *DefaultFilter) GetExpression() string { @@ -1013,7 +1068,7 @@ type Concurrency struct { func (x *Concurrency) Reset() { *x = Concurrency{} if protoimpl.UnsafeEnabled { - mi := &file_v1_workflows_proto_msgTypes[11] + mi := &file_v1_workflows_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1026,7 +1081,7 @@ func (x *Concurrency) String() string { func (*Concurrency) ProtoMessage() {} func (x *Concurrency) ProtoReflect() protoreflect.Message { - mi := &file_v1_workflows_proto_msgTypes[11] + mi := &file_v1_workflows_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1039,7 +1094,7 @@ func (x *Concurrency) ProtoReflect() protoreflect.Message { // Deprecated: Use Concurrency.ProtoReflect.Descriptor instead. func (*Concurrency) Descriptor() ([]byte, []int) { - return file_v1_workflows_proto_rawDescGZIP(), []int{11} + return file_v1_workflows_proto_rawDescGZIP(), []int{12} } func (x *Concurrency) GetExpression() string { @@ -1089,7 +1144,7 @@ type CreateTaskOpts struct { func (x *CreateTaskOpts) Reset() { *x = CreateTaskOpts{} if protoimpl.UnsafeEnabled { - mi := &file_v1_workflows_proto_msgTypes[12] + mi := &file_v1_workflows_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1102,7 +1157,7 @@ func (x *CreateTaskOpts) String() string { func (*CreateTaskOpts) ProtoMessage() {} func (x *CreateTaskOpts) ProtoReflect() protoreflect.Message { - mi := &file_v1_workflows_proto_msgTypes[12] + mi := &file_v1_workflows_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1115,7 +1170,7 @@ func (x *CreateTaskOpts) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateTaskOpts.ProtoReflect.Descriptor instead. func (*CreateTaskOpts) Descriptor() ([]byte, []int) { - return file_v1_workflows_proto_rawDescGZIP(), []int{12} + return file_v1_workflows_proto_rawDescGZIP(), []int{13} } func (x *CreateTaskOpts) GetReadableId() string { @@ -1239,7 +1294,7 @@ type CreateTaskRateLimit struct { func (x *CreateTaskRateLimit) Reset() { *x = CreateTaskRateLimit{} if protoimpl.UnsafeEnabled { - mi := &file_v1_workflows_proto_msgTypes[13] + mi := &file_v1_workflows_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1252,7 +1307,7 @@ func (x *CreateTaskRateLimit) String() string { func (*CreateTaskRateLimit) ProtoMessage() {} func (x *CreateTaskRateLimit) ProtoReflect() protoreflect.Message { - mi := &file_v1_workflows_proto_msgTypes[13] + mi := &file_v1_workflows_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1265,7 +1320,7 @@ func (x *CreateTaskRateLimit) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateTaskRateLimit.ProtoReflect.Descriptor instead. func (*CreateTaskRateLimit) Descriptor() ([]byte, []int) { - return file_v1_workflows_proto_rawDescGZIP(), []int{13} + return file_v1_workflows_proto_rawDescGZIP(), []int{14} } func (x *CreateTaskRateLimit) GetKey() string { @@ -1323,7 +1378,7 @@ type CreateWorkflowVersionResponse struct { func (x *CreateWorkflowVersionResponse) Reset() { *x = CreateWorkflowVersionResponse{} if protoimpl.UnsafeEnabled { - mi := &file_v1_workflows_proto_msgTypes[14] + mi := &file_v1_workflows_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1336,7 +1391,7 @@ func (x *CreateWorkflowVersionResponse) String() string { func (*CreateWorkflowVersionResponse) ProtoMessage() {} func (x *CreateWorkflowVersionResponse) ProtoReflect() protoreflect.Message { - mi := &file_v1_workflows_proto_msgTypes[14] + mi := &file_v1_workflows_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1349,7 +1404,7 @@ func (x *CreateWorkflowVersionResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateWorkflowVersionResponse.ProtoReflect.Descriptor instead. func (*CreateWorkflowVersionResponse) Descriptor() ([]byte, []int) { - return file_v1_workflows_proto_rawDescGZIP(), []int{14} + return file_v1_workflows_proto_rawDescGZIP(), []int{15} } func (x *CreateWorkflowVersionResponse) GetId() string { @@ -1377,7 +1432,7 @@ type GetRunDetailsRequest struct { func (x *GetRunDetailsRequest) Reset() { *x = GetRunDetailsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_v1_workflows_proto_msgTypes[15] + mi := &file_v1_workflows_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1390,7 +1445,7 @@ func (x *GetRunDetailsRequest) String() string { func (*GetRunDetailsRequest) ProtoMessage() {} func (x *GetRunDetailsRequest) ProtoReflect() protoreflect.Message { - mi := &file_v1_workflows_proto_msgTypes[15] + mi := &file_v1_workflows_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1403,7 +1458,7 @@ func (x *GetRunDetailsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRunDetailsRequest.ProtoReflect.Descriptor instead. func (*GetRunDetailsRequest) Descriptor() ([]byte, []int) { - return file_v1_workflows_proto_rawDescGZIP(), []int{15} + return file_v1_workflows_proto_rawDescGZIP(), []int{16} } func (x *GetRunDetailsRequest) GetExternalId() string { @@ -1429,7 +1484,7 @@ type TaskRunDetail struct { func (x *TaskRunDetail) Reset() { *x = TaskRunDetail{} if protoimpl.UnsafeEnabled { - mi := &file_v1_workflows_proto_msgTypes[16] + mi := &file_v1_workflows_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1442,7 +1497,7 @@ func (x *TaskRunDetail) String() string { func (*TaskRunDetail) ProtoMessage() {} func (x *TaskRunDetail) ProtoReflect() protoreflect.Message { - mi := &file_v1_workflows_proto_msgTypes[16] + mi := &file_v1_workflows_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1455,7 +1510,7 @@ func (x *TaskRunDetail) ProtoReflect() protoreflect.Message { // Deprecated: Use TaskRunDetail.ProtoReflect.Descriptor instead. func (*TaskRunDetail) Descriptor() ([]byte, []int) { - return file_v1_workflows_proto_rawDescGZIP(), []int{16} + return file_v1_workflows_proto_rawDescGZIP(), []int{17} } func (x *TaskRunDetail) GetExternalId() string { @@ -1516,7 +1571,7 @@ type GetRunDetailsResponse struct { func (x *GetRunDetailsResponse) Reset() { *x = GetRunDetailsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_v1_workflows_proto_msgTypes[17] + mi := &file_v1_workflows_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1529,7 +1584,7 @@ func (x *GetRunDetailsResponse) String() string { func (*GetRunDetailsResponse) ProtoMessage() {} func (x *GetRunDetailsResponse) ProtoReflect() protoreflect.Message { - mi := &file_v1_workflows_proto_msgTypes[17] + mi := &file_v1_workflows_proto_msgTypes[18] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1542,7 +1597,7 @@ func (x *GetRunDetailsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRunDetailsResponse.ProtoReflect.Descriptor instead. func (*GetRunDetailsResponse) Descriptor() ([]byte, []int) { - return file_v1_workflows_proto_rawDescGZIP(), []int{17} + return file_v1_workflows_proto_rawDescGZIP(), []int{18} } func (x *GetRunDetailsResponse) GetInput() []byte { @@ -1678,7 +1733,7 @@ var file_v1_workflows_proto_rawDesc = []byte{ 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x6e, 0x6f, 0x64, 0x65, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x49, 0x64, 0x22, - 0xad, 0x06, 0x0a, 0x1c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, + 0xab, 0x06, 0x0a, 0x1c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, @@ -1718,213 +1773,217 @@ var file_v1_workflows_proto_rawDesc = []byte{ 0x6c, 0x74, 0x65, 0x72, 0x73, 0x12, 0x2f, 0x0a, 0x11, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x04, 0x52, 0x0f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x4a, 0x73, 0x6f, 0x6e, 0x53, 0x63, 0x68, - 0x65, 0x6d, 0x61, 0x88, 0x01, 0x01, 0x12, 0x35, 0x0a, 0x14, 0x69, 0x64, 0x65, 0x6d, 0x70, 0x6f, - 0x74, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x65, 0x78, 0x70, 0x72, 0x18, 0x0f, - 0x20, 0x01, 0x28, 0x09, 0x48, 0x05, 0x52, 0x12, 0x69, 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, 0x65, - 0x6e, 0x63, 0x79, 0x4b, 0x65, 0x79, 0x45, 0x78, 0x70, 0x72, 0x88, 0x01, 0x01, 0x42, 0x0d, 0x0a, - 0x0b, 0x5f, 0x63, 0x72, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x42, 0x12, 0x0a, 0x10, - 0x5f, 0x6f, 0x6e, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x74, 0x61, 0x73, 0x6b, - 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x73, 0x74, 0x69, 0x63, 0x6b, 0x79, 0x42, 0x13, 0x0a, 0x11, 0x5f, - 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, - 0x42, 0x14, 0x0a, 0x12, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x5f, - 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x42, 0x17, 0x0a, 0x15, 0x5f, 0x69, 0x64, 0x65, 0x6d, 0x70, - 0x6f, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x65, 0x78, 0x70, 0x72, 0x22, - 0x70, 0x0a, 0x0d, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, - 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, - 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, - 0x61, 0x64, 0x88, 0x01, 0x01, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, - 0x64, 0x22, 0xb7, 0x01, 0x0a, 0x0b, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, - 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x12, 0x1e, 0x0a, 0x08, 0x6d, 0x61, 0x78, 0x5f, 0x72, 0x75, 0x6e, 0x73, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x07, 0x6d, 0x61, 0x78, 0x52, 0x75, 0x6e, 0x73, 0x88, 0x01, - 0x01, 0x12, 0x48, 0x0a, 0x0e, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, - 0x65, 0x67, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x76, 0x31, 0x2e, 0x43, - 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x53, - 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x48, 0x01, 0x52, 0x0d, 0x6c, 0x69, 0x6d, 0x69, 0x74, - 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x88, 0x01, 0x01, 0x42, 0x0b, 0x0a, 0x09, 0x5f, - 0x6d, 0x61, 0x78, 0x5f, 0x72, 0x75, 0x6e, 0x73, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x6c, 0x69, 0x6d, - 0x69, 0x74, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x22, 0x85, 0x07, 0x0a, 0x0e, - 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x4f, 0x70, 0x74, 0x73, 0x12, 0x1f, - 0x0a, 0x0b, 0x72, 0x65, 0x61, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x61, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x49, 0x64, 0x12, - 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, - 0x75, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, - 0x74, 0x12, 0x16, 0x0a, 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x72, - 0x65, 0x6e, 0x74, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x72, 0x65, - 0x6e, 0x74, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x72, 0x65, 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x38, 0x0a, - 0x0b, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x18, 0x07, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, - 0x73, 0x6b, 0x52, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x52, 0x0a, 0x72, 0x61, 0x74, - 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x12, 0x49, 0x0a, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x65, - 0x72, 0x5f, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, - 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x4f, 0x70, - 0x74, 0x73, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4c, 0x61, 0x62, 0x65, - 0x6c, 0x73, 0x12, 0x2a, 0x0a, 0x0e, 0x62, 0x61, 0x63, 0x6b, 0x6f, 0x66, 0x66, 0x5f, 0x66, 0x61, - 0x63, 0x74, 0x6f, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x02, 0x48, 0x00, 0x52, 0x0d, 0x62, 0x61, - 0x63, 0x6b, 0x6f, 0x66, 0x66, 0x46, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x88, 0x01, 0x01, 0x12, 0x33, - 0x0a, 0x13, 0x62, 0x61, 0x63, 0x6b, 0x6f, 0x66, 0x66, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x73, 0x65, - 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x48, 0x01, 0x52, 0x11, 0x62, - 0x61, 0x63, 0x6b, 0x6f, 0x66, 0x66, 0x4d, 0x61, 0x78, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, - 0x88, 0x01, 0x01, 0x12, 0x31, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, - 0x63, 0x79, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, - 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, - 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x37, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x76, 0x31, 0x2e, - 0x54, 0x61, 0x73, 0x6b, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x48, 0x02, - 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x88, 0x01, 0x01, 0x12, - 0x2e, 0x0a, 0x10, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, - 0x6f, 0x75, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x0f, 0x73, 0x63, 0x68, - 0x65, 0x64, 0x75, 0x6c, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x88, 0x01, 0x01, 0x12, - 0x1d, 0x0a, 0x0a, 0x69, 0x73, 0x5f, 0x64, 0x75, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x0e, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x73, 0x44, 0x75, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x49, - 0x0a, 0x0d, 0x73, 0x6c, 0x6f, 0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x18, - 0x0f, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x54, 0x61, 0x73, 0x6b, 0x4f, 0x70, 0x74, 0x73, 0x2e, 0x53, 0x6c, 0x6f, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, 0x73, 0x6c, 0x6f, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x1a, 0x58, 0x0a, 0x11, 0x57, 0x6f, 0x72, - 0x6b, 0x65, 0x72, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, - 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, - 0x12, 0x2d, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x17, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x73, 0x69, 0x72, 0x65, 0x64, 0x57, 0x6f, 0x72, 0x6b, - 0x65, 0x72, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, - 0x02, 0x38, 0x01, 0x1a, 0x3f, 0x0a, 0x11, 0x53, 0x6c, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x3a, 0x02, 0x38, 0x01, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x6f, 0x66, 0x66, - 0x5f, 0x66, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x42, 0x16, 0x0a, 0x14, 0x5f, 0x62, 0x61, 0x63, 0x6b, - 0x6f, 0x66, 0x66, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x42, - 0x0d, 0x0a, 0x0b, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x13, - 0x0a, 0x11, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, - 0x6f, 0x75, 0x74, 0x22, 0xb8, 0x02, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, - 0x73, 0x6b, 0x52, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, - 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x19, 0x0a, - 0x05, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x05, - 0x75, 0x6e, 0x69, 0x74, 0x73, 0x88, 0x01, 0x01, 0x12, 0x1e, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x5f, - 0x65, 0x78, 0x70, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x07, 0x6b, 0x65, - 0x79, 0x45, 0x78, 0x70, 0x72, 0x88, 0x01, 0x01, 0x12, 0x22, 0x0a, 0x0a, 0x75, 0x6e, 0x69, 0x74, - 0x73, 0x5f, 0x65, 0x78, 0x70, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x09, - 0x75, 0x6e, 0x69, 0x74, 0x73, 0x45, 0x78, 0x70, 0x72, 0x88, 0x01, 0x01, 0x12, 0x2f, 0x0a, 0x11, - 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x5f, 0x65, 0x78, 0x70, - 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x0f, 0x6c, 0x69, 0x6d, 0x69, 0x74, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x45, 0x78, 0x70, 0x72, 0x88, 0x01, 0x01, 0x12, 0x36, 0x0a, - 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, - 0x15, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x44, 0x75, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x04, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x42, - 0x0b, 0x0a, 0x09, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x65, 0x78, 0x70, 0x72, 0x42, 0x0d, 0x0a, 0x0b, - 0x5f, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x5f, 0x65, 0x78, 0x70, 0x72, 0x42, 0x14, 0x0a, 0x12, 0x5f, - 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x5f, 0x65, 0x78, 0x70, - 0x72, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x50, - 0x0a, 0x1d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, - 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, - 0x1f, 0x0a, 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x49, 0x64, - 0x22, 0x37, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x52, 0x75, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x74, 0x65, - 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, - 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x49, 0x64, 0x22, 0xe4, 0x01, 0x0a, 0x0d, 0x54, 0x61, - 0x73, 0x6b, 0x52, 0x75, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x65, - 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0d, 0x2e, 0x76, - 0x31, 0x2e, 0x52, 0x75, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x12, 0x19, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x88, 0x01, 0x01, 0x12, 0x1b, - 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x01, - 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x88, 0x01, 0x01, 0x12, 0x1f, 0x0a, 0x0b, 0x72, - 0x65, 0x61, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x72, 0x65, 0x61, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, - 0x69, 0x73, 0x5f, 0x65, 0x76, 0x69, 0x63, 0x74, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x09, 0x69, 0x73, 0x45, 0x76, 0x69, 0x63, 0x74, 0x65, 0x64, 0x42, 0x08, 0x0a, 0x06, 0x5f, - 0x65, 0x72, 0x72, 0x6f, 0x72, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, - 0x22, 0xce, 0x02, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x52, 0x75, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, - 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, - 0x70, 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x69, 0x6e, 0x70, 0x75, 0x74, - 0x12, 0x25, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, - 0x32, 0x0d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x75, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x44, 0x0a, 0x09, 0x74, 0x61, 0x73, 0x6b, 0x5f, - 0x72, 0x75, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x76, 0x31, 0x2e, - 0x47, 0x65, 0x74, 0x52, 0x75, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x75, 0x6e, 0x73, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x52, 0x08, 0x74, 0x61, 0x73, 0x6b, 0x52, 0x75, 0x6e, 0x73, 0x12, 0x12, 0x0a, - 0x04, 0x64, 0x6f, 0x6e, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x64, 0x6f, 0x6e, - 0x65, 0x12, 0x2f, 0x0a, 0x13, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, - 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x12, - 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x73, 0x5f, 0x65, 0x76, 0x69, 0x63, 0x74, 0x65, 0x64, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x73, 0x45, 0x76, 0x69, 0x63, 0x74, 0x65, - 0x64, 0x1a, 0x4e, 0x0a, 0x0d, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x75, 0x6e, 0x73, 0x45, 0x6e, 0x74, + 0x65, 0x6d, 0x61, 0x88, 0x01, 0x01, 0x12, 0x3c, 0x0a, 0x0b, 0x69, 0x64, 0x65, 0x6d, 0x70, 0x6f, + 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x76, 0x31, + 0x2e, 0x49, 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x48, 0x05, 0x52, 0x0b, 0x69, 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x63, + 0x79, 0x88, 0x01, 0x01, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x63, 0x72, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, + 0x70, 0x75, 0x74, 0x42, 0x12, 0x0a, 0x10, 0x5f, 0x6f, 0x6e, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x75, + 0x72, 0x65, 0x5f, 0x74, 0x61, 0x73, 0x6b, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x73, 0x74, 0x69, 0x63, + 0x6b, 0x79, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x70, + 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x42, 0x14, 0x0a, 0x12, 0x5f, 0x69, 0x6e, 0x70, 0x75, + 0x74, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x42, 0x0e, 0x0a, + 0x0c, 0x5f, 0x69, 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x22, 0x4a, 0x0a, + 0x11, 0x49, 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x12, 0x15, 0x0a, 0x06, 0x74, 0x74, 0x6c, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x05, 0x74, 0x74, 0x6c, 0x4d, 0x73, 0x22, 0x70, 0x0a, 0x0d, 0x44, 0x65, 0x66, + 0x61, 0x75, 0x6c, 0x74, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x78, + 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, + 0x6f, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, + 0x12, 0x1d, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0c, 0x48, 0x00, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x88, 0x01, 0x01, 0x42, + 0x0a, 0x0a, 0x08, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0xb7, 0x01, 0x0a, 0x0b, + 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x65, + 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x08, 0x6d, + 0x61, 0x78, 0x5f, 0x72, 0x75, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, + 0x07, 0x6d, 0x61, 0x78, 0x52, 0x75, 0x6e, 0x73, 0x88, 0x01, 0x01, 0x12, 0x48, 0x0a, 0x0e, 0x6c, + 0x69, 0x6d, 0x69, 0x74, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, + 0x65, 0x6e, 0x63, 0x79, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, + 0x79, 0x48, 0x01, 0x52, 0x0d, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, + 0x67, 0x79, 0x88, 0x01, 0x01, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x72, 0x75, + 0x6e, 0x73, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x5f, 0x73, 0x74, 0x72, + 0x61, 0x74, 0x65, 0x67, 0x79, 0x22, 0x85, 0x07, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x54, 0x61, 0x73, 0x6b, 0x4f, 0x70, 0x74, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x61, 0x64, + 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, + 0x65, 0x61, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x69, + 0x6e, 0x70, 0x75, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x69, 0x6e, 0x70, + 0x75, 0x74, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x05, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x18, 0x0a, + 0x07, 0x72, 0x65, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, + 0x72, 0x65, 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x38, 0x0a, 0x0b, 0x72, 0x61, 0x74, 0x65, 0x5f, + 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x76, + 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x61, 0x74, 0x65, + 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x52, 0x0a, 0x72, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, + 0x73, 0x12, 0x49, 0x0a, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x5f, 0x6c, 0x61, 0x62, 0x65, + 0x6c, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x4f, 0x70, 0x74, 0x73, 0x2e, 0x57, 0x6f, 0x72, + 0x6b, 0x65, 0x72, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, + 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x2a, 0x0a, 0x0e, + 0x62, 0x61, 0x63, 0x6b, 0x6f, 0x66, 0x66, 0x5f, 0x66, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x02, 0x48, 0x00, 0x52, 0x0d, 0x62, 0x61, 0x63, 0x6b, 0x6f, 0x66, 0x66, 0x46, + 0x61, 0x63, 0x74, 0x6f, 0x72, 0x88, 0x01, 0x01, 0x12, 0x33, 0x0a, 0x13, 0x62, 0x61, 0x63, 0x6b, + 0x6f, 0x66, 0x66, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, + 0x0a, 0x20, 0x01, 0x28, 0x05, 0x48, 0x01, 0x52, 0x11, 0x62, 0x61, 0x63, 0x6b, 0x6f, 0x66, 0x66, + 0x4d, 0x61, 0x78, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x88, 0x01, 0x01, 0x12, 0x31, 0x0a, + 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x0b, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, + 0x6e, 0x63, 0x79, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, + 0x12, 0x37, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0c, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x43, 0x6f, + 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x48, 0x02, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x64, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x88, 0x01, 0x01, 0x12, 0x2e, 0x0a, 0x10, 0x73, 0x63, 0x68, + 0x65, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x0d, 0x20, + 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x0f, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x54, + 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x73, 0x5f, + 0x64, 0x75, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, + 0x73, 0x44, 0x75, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x49, 0x0a, 0x0d, 0x73, 0x6c, 0x6f, 0x74, + 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x24, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x4f, + 0x70, 0x74, 0x73, 0x2e, 0x53, 0x6c, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, 0x73, 0x6c, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x73, 0x1a, 0x58, 0x0a, 0x11, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4c, 0x61, 0x62, + 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2d, 0x0a, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x76, 0x31, 0x2e, 0x44, + 0x65, 0x73, 0x69, 0x72, 0x65, 0x64, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4c, 0x61, 0x62, 0x65, + 0x6c, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x3f, 0x0a, + 0x11, 0x53, 0x6c, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x03, 0x6b, 0x65, 0x79, 0x12, 0x27, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x75, 0x6e, - 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, - 0x01, 0x2a, 0x24, 0x0a, 0x0e, 0x53, 0x74, 0x69, 0x63, 0x6b, 0x79, 0x53, 0x74, 0x72, 0x61, 0x74, - 0x65, 0x67, 0x79, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x4f, 0x46, 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, - 0x04, 0x48, 0x41, 0x52, 0x44, 0x10, 0x01, 0x2a, 0x5d, 0x0a, 0x11, 0x52, 0x61, 0x74, 0x65, 0x4c, - 0x69, 0x6d, 0x69, 0x74, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, - 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x4d, 0x49, 0x4e, 0x55, - 0x54, 0x45, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x4f, 0x55, 0x52, 0x10, 0x02, 0x12, 0x07, - 0x0a, 0x03, 0x44, 0x41, 0x59, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x45, 0x45, 0x4b, 0x10, - 0x04, 0x12, 0x09, 0x0a, 0x05, 0x4d, 0x4f, 0x4e, 0x54, 0x48, 0x10, 0x05, 0x12, 0x08, 0x0a, 0x04, - 0x59, 0x45, 0x41, 0x52, 0x10, 0x06, 0x2a, 0x5b, 0x0a, 0x09, 0x52, 0x75, 0x6e, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x12, 0x0a, 0x0a, 0x06, 0x51, 0x55, 0x45, 0x55, 0x45, 0x44, 0x10, 0x00, 0x12, - 0x0b, 0x0a, 0x07, 0x52, 0x55, 0x4e, 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, - 0x43, 0x4f, 0x4d, 0x50, 0x4c, 0x45, 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x46, - 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x03, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x41, 0x4e, 0x43, 0x45, - 0x4c, 0x4c, 0x45, 0x44, 0x10, 0x04, 0x12, 0x0b, 0x0a, 0x07, 0x45, 0x56, 0x49, 0x43, 0x54, 0x45, - 0x44, 0x10, 0x05, 0x2a, 0x7f, 0x0a, 0x18, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, - 0x63, 0x79, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, - 0x16, 0x0a, 0x12, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x5f, 0x49, 0x4e, 0x5f, 0x50, 0x52, 0x4f, - 0x47, 0x52, 0x45, 0x53, 0x53, 0x10, 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x44, 0x52, 0x4f, 0x50, 0x5f, - 0x4e, 0x45, 0x57, 0x45, 0x53, 0x54, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x51, 0x55, 0x45, 0x55, - 0x45, 0x5f, 0x4e, 0x45, 0x57, 0x45, 0x53, 0x54, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x47, 0x52, - 0x4f, 0x55, 0x50, 0x5f, 0x52, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x52, 0x4f, 0x42, 0x49, 0x4e, 0x10, - 0x03, 0x12, 0x11, 0x0a, 0x0d, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x5f, 0x4e, 0x45, 0x57, 0x45, - 0x53, 0x54, 0x10, 0x04, 0x32, 0xcf, 0x03, 0x0a, 0x0c, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x53, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x52, 0x0a, 0x0b, 0x50, 0x75, 0x74, 0x57, 0x6f, 0x72, 0x6b, - 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x20, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x11, + 0x0a, 0x0f, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x6f, 0x66, 0x66, 0x5f, 0x66, 0x61, 0x63, 0x74, 0x6f, + 0x72, 0x42, 0x16, 0x0a, 0x14, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x6f, 0x66, 0x66, 0x5f, 0x6d, 0x61, + 0x78, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x63, 0x6f, + 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x73, 0x63, 0x68, + 0x65, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x22, 0xb8, 0x02, + 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x61, 0x74, 0x65, + 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x19, 0x0a, 0x05, 0x75, 0x6e, 0x69, 0x74, 0x73, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x05, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x88, + 0x01, 0x01, 0x12, 0x1e, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x5f, 0x65, 0x78, 0x70, 0x72, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x45, 0x78, 0x70, 0x72, 0x88, + 0x01, 0x01, 0x12, 0x22, 0x0a, 0x0a, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x5f, 0x65, 0x78, 0x70, 0x72, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x09, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x45, + 0x78, 0x70, 0x72, 0x88, 0x01, 0x01, 0x12, 0x2f, 0x0a, 0x11, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x5f, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x5f, 0x65, 0x78, 0x70, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x48, 0x03, 0x52, 0x0f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, + 0x45, 0x78, 0x70, 0x72, 0x88, 0x01, 0x01, 0x12, 0x36, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x76, 0x31, 0x2e, 0x52, + 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x48, 0x04, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x42, + 0x08, 0x0a, 0x06, 0x5f, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x6b, 0x65, + 0x79, 0x5f, 0x65, 0x78, 0x70, 0x72, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x75, 0x6e, 0x69, 0x74, 0x73, + 0x5f, 0x65, 0x78, 0x70, 0x72, 0x42, 0x14, 0x0a, 0x12, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x5f, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x5f, 0x65, 0x78, 0x70, 0x72, 0x42, 0x0b, 0x0a, 0x09, 0x5f, + 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x50, 0x0a, 0x1d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x0b, 0x43, 0x61, 0x6e, - 0x63, 0x65, 0x6c, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x12, 0x16, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, - 0x6e, 0x63, 0x65, 0x6c, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x17, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x54, 0x61, 0x73, 0x6b, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x0b, 0x52, 0x65, 0x70, - 0x6c, 0x61, 0x79, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x12, 0x16, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, - 0x70, 0x6c, 0x61, 0x79, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x17, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x79, 0x54, 0x61, 0x73, 0x6b, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x12, 0x54, 0x72, 0x69, - 0x67, 0x67, 0x65, 0x72, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x75, 0x6e, 0x12, - 0x1d, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x57, 0x6f, 0x72, 0x6b, - 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, - 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x57, 0x6f, 0x72, 0x6b, 0x66, - 0x6c, 0x6f, 0x77, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, - 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x52, 0x75, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, - 0x18, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x75, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, - 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x76, 0x31, 0x2e, 0x47, + 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x77, 0x6f, 0x72, + 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x49, 0x64, 0x22, 0x37, 0x0a, 0x14, 0x47, 0x65, + 0x74, 0x52, 0x75, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, + 0x6c, 0x49, 0x64, 0x22, 0xe4, 0x01, 0x0a, 0x0d, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x75, 0x6e, 0x44, + 0x65, 0x74, 0x61, 0x69, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, + 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x78, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x75, 0x6e, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x19, 0x0a, + 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x88, 0x01, 0x01, 0x12, 0x1b, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, + 0x75, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x01, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, + 0x75, 0x74, 0x88, 0x01, 0x01, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x61, 0x64, 0x61, 0x62, 0x6c, + 0x65, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x61, 0x64, + 0x61, 0x62, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x73, 0x5f, 0x65, 0x76, 0x69, + 0x63, 0x74, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x73, 0x45, 0x76, + 0x69, 0x63, 0x74, 0x65, 0x64, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x42, + 0x09, 0x0a, 0x07, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0xce, 0x02, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x52, 0x75, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x11, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x44, 0x75, - 0x72, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x1c, 0x2e, 0x76, 0x31, 0x2e, 0x42, - 0x72, 0x61, 0x6e, 0x63, 0x68, 0x44, 0x75, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x61, 0x73, 0x6b, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x72, 0x61, - 0x6e, 0x63, 0x68, 0x44, 0x75, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x42, 0x5a, 0x40, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x74, 0x63, 0x68, 0x65, 0x74, 0x2d, 0x64, 0x65, 0x76, - 0x2f, 0x68, 0x61, 0x74, 0x63, 0x68, 0x65, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x6c, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x73, 0x68, 0x61, 0x72, 0x65, - 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x05, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x25, 0x0a, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0d, 0x2e, 0x76, 0x31, 0x2e, + 0x52, 0x75, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x12, 0x44, 0x0a, 0x09, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x72, 0x75, 0x6e, 0x73, 0x18, 0x03, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x75, 0x6e, + 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, + 0x54, 0x61, 0x73, 0x6b, 0x52, 0x75, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x74, + 0x61, 0x73, 0x6b, 0x52, 0x75, 0x6e, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x6f, 0x6e, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x64, 0x6f, 0x6e, 0x65, 0x12, 0x2f, 0x0a, 0x13, 0x61, + 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x12, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x61, 0x6c, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, + 0x69, 0x73, 0x5f, 0x65, 0x76, 0x69, 0x63, 0x74, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x09, 0x69, 0x73, 0x45, 0x76, 0x69, 0x63, 0x74, 0x65, 0x64, 0x1a, 0x4e, 0x0a, 0x0d, 0x54, + 0x61, 0x73, 0x6b, 0x52, 0x75, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, + 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x27, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, + 0x76, 0x31, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x75, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x2a, 0x24, 0x0a, 0x0e, 0x53, + 0x74, 0x69, 0x63, 0x6b, 0x79, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x08, 0x0a, + 0x04, 0x53, 0x4f, 0x46, 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x41, 0x52, 0x44, 0x10, + 0x01, 0x2a, 0x5d, 0x0a, 0x11, 0x52, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x44, 0x75, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, + 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x4d, 0x49, 0x4e, 0x55, 0x54, 0x45, 0x10, 0x01, 0x12, 0x08, + 0x0a, 0x04, 0x48, 0x4f, 0x55, 0x52, 0x10, 0x02, 0x12, 0x07, 0x0a, 0x03, 0x44, 0x41, 0x59, 0x10, + 0x03, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x45, 0x45, 0x4b, 0x10, 0x04, 0x12, 0x09, 0x0a, 0x05, 0x4d, + 0x4f, 0x4e, 0x54, 0x48, 0x10, 0x05, 0x12, 0x08, 0x0a, 0x04, 0x59, 0x45, 0x41, 0x52, 0x10, 0x06, + 0x2a, 0x5b, 0x0a, 0x09, 0x52, 0x75, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0a, 0x0a, + 0x06, 0x51, 0x55, 0x45, 0x55, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x52, 0x55, 0x4e, + 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x4f, 0x4d, 0x50, 0x4c, 0x45, + 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, + 0x03, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x4c, 0x45, 0x44, 0x10, 0x04, + 0x12, 0x0b, 0x0a, 0x07, 0x45, 0x56, 0x49, 0x43, 0x54, 0x45, 0x44, 0x10, 0x05, 0x2a, 0x7f, 0x0a, + 0x18, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x4c, 0x69, 0x6d, 0x69, + 0x74, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x16, 0x0a, 0x12, 0x43, 0x41, 0x4e, + 0x43, 0x45, 0x4c, 0x5f, 0x49, 0x4e, 0x5f, 0x50, 0x52, 0x4f, 0x47, 0x52, 0x45, 0x53, 0x53, 0x10, + 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x44, 0x52, 0x4f, 0x50, 0x5f, 0x4e, 0x45, 0x57, 0x45, 0x53, 0x54, + 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x51, 0x55, 0x45, 0x55, 0x45, 0x5f, 0x4e, 0x45, 0x57, 0x45, + 0x53, 0x54, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x47, 0x52, 0x4f, 0x55, 0x50, 0x5f, 0x52, 0x4f, + 0x55, 0x4e, 0x44, 0x5f, 0x52, 0x4f, 0x42, 0x49, 0x4e, 0x10, 0x03, 0x12, 0x11, 0x0a, 0x0d, 0x43, + 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x5f, 0x4e, 0x45, 0x57, 0x45, 0x53, 0x54, 0x10, 0x04, 0x32, 0xcf, + 0x03, 0x0a, 0x0c, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, + 0x52, 0x0a, 0x0b, 0x50, 0x75, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x20, + 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, + 0x6f, 0x77, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x21, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, + 0x66, 0x6c, 0x6f, 0x77, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x0b, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x54, 0x61, 0x73, + 0x6b, 0x73, 0x12, 0x16, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x54, 0x61, + 0x73, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x76, 0x31, 0x2e, + 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x0b, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x79, 0x54, 0x61, 0x73, + 0x6b, 0x73, 0x12, 0x16, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x79, 0x54, 0x61, + 0x73, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x76, 0x31, 0x2e, + 0x52, 0x65, 0x70, 0x6c, 0x61, 0x79, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x12, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x57, 0x6f, + 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x75, 0x6e, 0x12, 0x1d, 0x2e, 0x76, 0x31, 0x2e, 0x54, + 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x75, + 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, + 0x69, 0x67, 0x67, 0x65, 0x72, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x75, 0x6e, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x52, + 0x75, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x18, 0x2e, 0x76, 0x31, 0x2e, 0x47, + 0x65, 0x74, 0x52, 0x75, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x75, 0x6e, 0x44, + 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, + 0x0a, 0x11, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x44, 0x75, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x54, + 0x61, 0x73, 0x6b, 0x12, 0x1c, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x44, + 0x75, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1d, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x44, 0x75, 0x72, + 0x61, 0x62, 0x6c, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x42, 0x42, 0x5a, 0x40, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, + 0x61, 0x74, 0x63, 0x68, 0x65, 0x74, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x68, 0x61, 0x74, 0x63, 0x68, + 0x65, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x73, 0x2f, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1940,7 +1999,7 @@ func file_v1_workflows_proto_rawDescGZIP() []byte { } var file_v1_workflows_proto_enumTypes = make([]protoimpl.EnumInfo, 4) -var file_v1_workflows_proto_msgTypes = make([]protoimpl.MessageInfo, 22) +var file_v1_workflows_proto_msgTypes = make([]protoimpl.MessageInfo, 23) var file_v1_workflows_proto_goTypes = []interface{}{ (StickyStrategy)(0), // 0: v1.StickyStrategy (RateLimitDuration)(0), // 1: v1.RateLimitDuration @@ -1956,64 +2015,66 @@ var file_v1_workflows_proto_goTypes = []interface{}{ (*BranchDurableTaskRequest)(nil), // 11: v1.BranchDurableTaskRequest (*BranchDurableTaskResponse)(nil), // 12: v1.BranchDurableTaskResponse (*CreateWorkflowVersionRequest)(nil), // 13: v1.CreateWorkflowVersionRequest - (*DefaultFilter)(nil), // 14: v1.DefaultFilter - (*Concurrency)(nil), // 15: v1.Concurrency - (*CreateTaskOpts)(nil), // 16: v1.CreateTaskOpts - (*CreateTaskRateLimit)(nil), // 17: v1.CreateTaskRateLimit - (*CreateWorkflowVersionResponse)(nil), // 18: v1.CreateWorkflowVersionResponse - (*GetRunDetailsRequest)(nil), // 19: v1.GetRunDetailsRequest - (*TaskRunDetail)(nil), // 20: v1.TaskRunDetail - (*GetRunDetailsResponse)(nil), // 21: v1.GetRunDetailsResponse - nil, // 22: v1.TriggerWorkflowRunRequest.DesiredWorkerLabelsEntry - nil, // 23: v1.CreateTaskOpts.WorkerLabelsEntry - nil, // 24: v1.CreateTaskOpts.SlotRequestsEntry - nil, // 25: v1.GetRunDetailsResponse.TaskRunsEntry - (*timestamppb.Timestamp)(nil), // 26: google.protobuf.Timestamp - (*TaskConditions)(nil), // 27: v1.TaskConditions - (*DesiredWorkerLabels)(nil), // 28: v1.DesiredWorkerLabels + (*IdempotencyConfig)(nil), // 14: v1.IdempotencyConfig + (*DefaultFilter)(nil), // 15: v1.DefaultFilter + (*Concurrency)(nil), // 16: v1.Concurrency + (*CreateTaskOpts)(nil), // 17: v1.CreateTaskOpts + (*CreateTaskRateLimit)(nil), // 18: v1.CreateTaskRateLimit + (*CreateWorkflowVersionResponse)(nil), // 19: v1.CreateWorkflowVersionResponse + (*GetRunDetailsRequest)(nil), // 20: v1.GetRunDetailsRequest + (*TaskRunDetail)(nil), // 21: v1.TaskRunDetail + (*GetRunDetailsResponse)(nil), // 22: v1.GetRunDetailsResponse + nil, // 23: v1.TriggerWorkflowRunRequest.DesiredWorkerLabelsEntry + nil, // 24: v1.CreateTaskOpts.WorkerLabelsEntry + nil, // 25: v1.CreateTaskOpts.SlotRequestsEntry + nil, // 26: v1.GetRunDetailsResponse.TaskRunsEntry + (*timestamppb.Timestamp)(nil), // 27: google.protobuf.Timestamp + (*TaskConditions)(nil), // 28: v1.TaskConditions + (*DesiredWorkerLabels)(nil), // 29: v1.DesiredWorkerLabels } var file_v1_workflows_proto_depIdxs = []int32{ 6, // 0: v1.CancelTasksRequest.filter:type_name -> v1.TasksFilter 6, // 1: v1.ReplayTasksRequest.filter:type_name -> v1.TasksFilter - 26, // 2: v1.TasksFilter.since:type_name -> google.protobuf.Timestamp - 26, // 3: v1.TasksFilter.until:type_name -> google.protobuf.Timestamp - 22, // 4: v1.TriggerWorkflowRunRequest.desired_worker_labels:type_name -> v1.TriggerWorkflowRunRequest.DesiredWorkerLabelsEntry - 16, // 5: v1.CreateWorkflowVersionRequest.tasks:type_name -> v1.CreateTaskOpts - 15, // 6: v1.CreateWorkflowVersionRequest.concurrency:type_name -> v1.Concurrency - 16, // 7: v1.CreateWorkflowVersionRequest.on_failure_task:type_name -> v1.CreateTaskOpts + 27, // 2: v1.TasksFilter.since:type_name -> google.protobuf.Timestamp + 27, // 3: v1.TasksFilter.until:type_name -> google.protobuf.Timestamp + 23, // 4: v1.TriggerWorkflowRunRequest.desired_worker_labels:type_name -> v1.TriggerWorkflowRunRequest.DesiredWorkerLabelsEntry + 17, // 5: v1.CreateWorkflowVersionRequest.tasks:type_name -> v1.CreateTaskOpts + 16, // 6: v1.CreateWorkflowVersionRequest.concurrency:type_name -> v1.Concurrency + 17, // 7: v1.CreateWorkflowVersionRequest.on_failure_task:type_name -> v1.CreateTaskOpts 0, // 8: v1.CreateWorkflowVersionRequest.sticky:type_name -> v1.StickyStrategy - 15, // 9: v1.CreateWorkflowVersionRequest.concurrency_arr:type_name -> v1.Concurrency - 14, // 10: v1.CreateWorkflowVersionRequest.default_filters:type_name -> v1.DefaultFilter - 3, // 11: v1.Concurrency.limit_strategy:type_name -> v1.ConcurrencyLimitStrategy - 17, // 12: v1.CreateTaskOpts.rate_limits:type_name -> v1.CreateTaskRateLimit - 23, // 13: v1.CreateTaskOpts.worker_labels:type_name -> v1.CreateTaskOpts.WorkerLabelsEntry - 15, // 14: v1.CreateTaskOpts.concurrency:type_name -> v1.Concurrency - 27, // 15: v1.CreateTaskOpts.conditions:type_name -> v1.TaskConditions - 24, // 16: v1.CreateTaskOpts.slot_requests:type_name -> v1.CreateTaskOpts.SlotRequestsEntry - 1, // 17: v1.CreateTaskRateLimit.duration:type_name -> v1.RateLimitDuration - 2, // 18: v1.TaskRunDetail.status:type_name -> v1.RunStatus - 2, // 19: v1.GetRunDetailsResponse.status:type_name -> v1.RunStatus - 25, // 20: v1.GetRunDetailsResponse.task_runs:type_name -> v1.GetRunDetailsResponse.TaskRunsEntry - 28, // 21: v1.TriggerWorkflowRunRequest.DesiredWorkerLabelsEntry.value:type_name -> v1.DesiredWorkerLabels - 28, // 22: v1.CreateTaskOpts.WorkerLabelsEntry.value:type_name -> v1.DesiredWorkerLabels - 20, // 23: v1.GetRunDetailsResponse.TaskRunsEntry.value:type_name -> v1.TaskRunDetail - 13, // 24: v1.AdminService.PutWorkflow:input_type -> v1.CreateWorkflowVersionRequest - 4, // 25: v1.AdminService.CancelTasks:input_type -> v1.CancelTasksRequest - 5, // 26: v1.AdminService.ReplayTasks:input_type -> v1.ReplayTasksRequest - 9, // 27: v1.AdminService.TriggerWorkflowRun:input_type -> v1.TriggerWorkflowRunRequest - 19, // 28: v1.AdminService.GetRunDetails:input_type -> v1.GetRunDetailsRequest - 11, // 29: v1.AdminService.BranchDurableTask:input_type -> v1.BranchDurableTaskRequest - 18, // 30: v1.AdminService.PutWorkflow:output_type -> v1.CreateWorkflowVersionResponse - 7, // 31: v1.AdminService.CancelTasks:output_type -> v1.CancelTasksResponse - 8, // 32: v1.AdminService.ReplayTasks:output_type -> v1.ReplayTasksResponse - 10, // 33: v1.AdminService.TriggerWorkflowRun:output_type -> v1.TriggerWorkflowRunResponse - 21, // 34: v1.AdminService.GetRunDetails:output_type -> v1.GetRunDetailsResponse - 12, // 35: v1.AdminService.BranchDurableTask:output_type -> v1.BranchDurableTaskResponse - 30, // [30:36] is the sub-list for method output_type - 24, // [24:30] is the sub-list for method input_type - 24, // [24:24] is the sub-list for extension type_name - 24, // [24:24] is the sub-list for extension extendee - 0, // [0:24] is the sub-list for field type_name + 16, // 9: v1.CreateWorkflowVersionRequest.concurrency_arr:type_name -> v1.Concurrency + 15, // 10: v1.CreateWorkflowVersionRequest.default_filters:type_name -> v1.DefaultFilter + 14, // 11: v1.CreateWorkflowVersionRequest.idempotency:type_name -> v1.IdempotencyConfig + 3, // 12: v1.Concurrency.limit_strategy:type_name -> v1.ConcurrencyLimitStrategy + 18, // 13: v1.CreateTaskOpts.rate_limits:type_name -> v1.CreateTaskRateLimit + 24, // 14: v1.CreateTaskOpts.worker_labels:type_name -> v1.CreateTaskOpts.WorkerLabelsEntry + 16, // 15: v1.CreateTaskOpts.concurrency:type_name -> v1.Concurrency + 28, // 16: v1.CreateTaskOpts.conditions:type_name -> v1.TaskConditions + 25, // 17: v1.CreateTaskOpts.slot_requests:type_name -> v1.CreateTaskOpts.SlotRequestsEntry + 1, // 18: v1.CreateTaskRateLimit.duration:type_name -> v1.RateLimitDuration + 2, // 19: v1.TaskRunDetail.status:type_name -> v1.RunStatus + 2, // 20: v1.GetRunDetailsResponse.status:type_name -> v1.RunStatus + 26, // 21: v1.GetRunDetailsResponse.task_runs:type_name -> v1.GetRunDetailsResponse.TaskRunsEntry + 29, // 22: v1.TriggerWorkflowRunRequest.DesiredWorkerLabelsEntry.value:type_name -> v1.DesiredWorkerLabels + 29, // 23: v1.CreateTaskOpts.WorkerLabelsEntry.value:type_name -> v1.DesiredWorkerLabels + 21, // 24: v1.GetRunDetailsResponse.TaskRunsEntry.value:type_name -> v1.TaskRunDetail + 13, // 25: v1.AdminService.PutWorkflow:input_type -> v1.CreateWorkflowVersionRequest + 4, // 26: v1.AdminService.CancelTasks:input_type -> v1.CancelTasksRequest + 5, // 27: v1.AdminService.ReplayTasks:input_type -> v1.ReplayTasksRequest + 9, // 28: v1.AdminService.TriggerWorkflowRun:input_type -> v1.TriggerWorkflowRunRequest + 20, // 29: v1.AdminService.GetRunDetails:input_type -> v1.GetRunDetailsRequest + 11, // 30: v1.AdminService.BranchDurableTask:input_type -> v1.BranchDurableTaskRequest + 19, // 31: v1.AdminService.PutWorkflow:output_type -> v1.CreateWorkflowVersionResponse + 7, // 32: v1.AdminService.CancelTasks:output_type -> v1.CancelTasksResponse + 8, // 33: v1.AdminService.ReplayTasks:output_type -> v1.ReplayTasksResponse + 10, // 34: v1.AdminService.TriggerWorkflowRun:output_type -> v1.TriggerWorkflowRunResponse + 22, // 35: v1.AdminService.GetRunDetails:output_type -> v1.GetRunDetailsResponse + 12, // 36: v1.AdminService.BranchDurableTask:output_type -> v1.BranchDurableTaskResponse + 31, // [31:37] is the sub-list for method output_type + 25, // [25:31] is the sub-list for method input_type + 25, // [25:25] is the sub-list for extension type_name + 25, // [25:25] is the sub-list for extension extendee + 0, // [0:25] is the sub-list for field type_name } func init() { file_v1_workflows_proto_init() } @@ -2145,7 +2206,7 @@ func file_v1_workflows_proto_init() { } } file_v1_workflows_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DefaultFilter); i { + switch v := v.(*IdempotencyConfig); i { case 0: return &v.state case 1: @@ -2157,7 +2218,7 @@ func file_v1_workflows_proto_init() { } } file_v1_workflows_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Concurrency); i { + switch v := v.(*DefaultFilter); i { case 0: return &v.state case 1: @@ -2169,7 +2230,7 @@ func file_v1_workflows_proto_init() { } } file_v1_workflows_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CreateTaskOpts); i { + switch v := v.(*Concurrency); i { case 0: return &v.state case 1: @@ -2181,7 +2242,7 @@ func file_v1_workflows_proto_init() { } } file_v1_workflows_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CreateTaskRateLimit); i { + switch v := v.(*CreateTaskOpts); i { case 0: return &v.state case 1: @@ -2193,7 +2254,7 @@ func file_v1_workflows_proto_init() { } } file_v1_workflows_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CreateWorkflowVersionResponse); i { + switch v := v.(*CreateTaskRateLimit); i { case 0: return &v.state case 1: @@ -2205,7 +2266,7 @@ func file_v1_workflows_proto_init() { } } file_v1_workflows_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetRunDetailsRequest); i { + switch v := v.(*CreateWorkflowVersionResponse); i { case 0: return &v.state case 1: @@ -2217,7 +2278,7 @@ func file_v1_workflows_proto_init() { } } file_v1_workflows_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TaskRunDetail); i { + switch v := v.(*GetRunDetailsRequest); i { case 0: return &v.state case 1: @@ -2229,6 +2290,18 @@ func file_v1_workflows_proto_init() { } } file_v1_workflows_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TaskRunDetail); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_v1_workflows_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetRunDetailsResponse); i { case 0: return &v.state @@ -2246,18 +2319,18 @@ func file_v1_workflows_proto_init() { file_v1_workflows_proto_msgTypes[2].OneofWrappers = []interface{}{} file_v1_workflows_proto_msgTypes[5].OneofWrappers = []interface{}{} file_v1_workflows_proto_msgTypes[9].OneofWrappers = []interface{}{} - file_v1_workflows_proto_msgTypes[10].OneofWrappers = []interface{}{} file_v1_workflows_proto_msgTypes[11].OneofWrappers = []interface{}{} file_v1_workflows_proto_msgTypes[12].OneofWrappers = []interface{}{} file_v1_workflows_proto_msgTypes[13].OneofWrappers = []interface{}{} - file_v1_workflows_proto_msgTypes[16].OneofWrappers = []interface{}{} + file_v1_workflows_proto_msgTypes[14].OneofWrappers = []interface{}{} + file_v1_workflows_proto_msgTypes[17].OneofWrappers = []interface{}{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_v1_workflows_proto_rawDesc, NumEnums: 4, - NumMessages: 22, + NumMessages: 23, NumExtensions: 0, NumServices: 1, }, diff --git a/pkg/repository/sqlcv1/workflows.sql b/pkg/repository/sqlcv1/workflows.sql index b6b7112eb4..65c4b28905 100644 --- a/pkg/repository/sqlcv1/workflows.sql +++ b/pkg/repository/sqlcv1/workflows.sql @@ -122,7 +122,8 @@ INSERT INTO "WorkflowVersion" ( "defaultPriority", "createWorkflowVersionOpts", "inputJsonSchema", - "idempotencyKeyExpression" + "idempotencyKeyExpression", + "idempotencyKeyTtl" ) VALUES ( @id::uuid, coalesce(sqlc.narg('createdAt')::timestamp, CURRENT_TIMESTAMP), @@ -138,7 +139,8 @@ INSERT INTO "WorkflowVersion" ( sqlc.narg('defaultPriority') :: integer, sqlc.narg('createWorkflowVersionOpts')::jsonb, sqlc.narg('inputJsonSchema')::jsonb, - sqlc.narg('idempotencyKeyExpression')::text + sqlc.narg('idempotencyKeyExpression')::text, + sqlc.narg('idempotencyKeyTtl')::bigint ) RETURNING *; -- name: CreateJob :one diff --git a/pkg/repository/sqlcv1/workflows.sql.go b/pkg/repository/sqlcv1/workflows.sql.go index 01fa20a953..eab95d2ab5 100644 --- a/pkg/repository/sqlcv1/workflows.sql.go +++ b/pkg/repository/sqlcv1/workflows.sql.go @@ -914,7 +914,8 @@ INSERT INTO "WorkflowVersion" ( "defaultPriority", "createWorkflowVersionOpts", "inputJsonSchema", - "idempotencyKeyExpression" + "idempotencyKeyExpression", + "idempotencyKeyTtl" ) VALUES ( $1::uuid, coalesce($2::timestamp, CURRENT_TIMESTAMP), @@ -930,7 +931,8 @@ INSERT INTO "WorkflowVersion" ( $10 :: integer, $11::jsonb, $12::jsonb, - $13::text + $13::text, + $14::bigint ) RETURNING id, "createdAt", "updatedAt", "deletedAt", version, "order", "workflowId", checksum, "scheduleTimeout", "onFailureJobId", sticky, kind, "defaultPriority", "createWorkflowVersionOpts", "inputJsonSchema", "idempotencyKeyExpression" ` @@ -948,6 +950,7 @@ type CreateWorkflowVersionParams struct { CreateWorkflowVersionOpts []byte `json:"createWorkflowVersionOpts"` InputJsonSchema []byte `json:"inputJsonSchema"` IdempotencyKeyExpression pgtype.Text `json:"idempotencyKeyExpression"` + IdempotencyKeyTtl pgtype.Int8 `json:"idempotencyKeyTtl"` } func (q *Queries) CreateWorkflowVersion(ctx context.Context, db DBTX, arg CreateWorkflowVersionParams) (*WorkflowVersion, error) { @@ -965,6 +968,7 @@ func (q *Queries) CreateWorkflowVersion(ctx context.Context, db DBTX, arg Create arg.CreateWorkflowVersionOpts, arg.InputJsonSchema, arg.IdempotencyKeyExpression, + arg.IdempotencyKeyTtl, ) var i WorkflowVersion err := row.Scan( diff --git a/pkg/repository/workflow.go b/pkg/repository/workflow.go index a0558bb32c..264130975e 100644 --- a/pkg/repository/workflow.go +++ b/pkg/repository/workflow.go @@ -57,7 +57,12 @@ type CreateWorkflowVersionOpts struct { InputJsonSchema []byte `json:"inputJsonSchema,omitempty"` - IdempotencyKeyExpr *string `json:"idempotencyKeyExpr,omitempty" validate:"omitempty,celworkflowrunstr"` + Idempotency *IdempotencyConfig `json:"idempotency,omitempty"` +} + +type IdempotencyConfig struct { + Expression string `json:"expression" validate:"required,celworkflowrunstr"` + TTLMs int64 `json:"ttlMs" validate:"required,min=1"` } type CreateConcurrencyOpts struct { @@ -429,11 +434,13 @@ func (r *workflowRepository) createWorkflowVersionTxs(ctx context.Context, tx sq } } - if opts.IdempotencyKeyExpr != nil { + if opts.Idempotency != nil { + idempotency := *opts.Idempotency createParams.IdempotencyKeyExpression = pgtype.Text{ - String: *opts.IdempotencyKeyExpr, + String: idempotency.Expression, Valid: true, } + createParams.IdempotencyKeyTtl = sqlchelpers.ToBigInt(&idempotency.TTLMs) } if opts.DefaultPriority != nil { From 92e3c37a9677b33fd3e82ea2e170e35922f67ed1 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Fri, 29 May 2026 14:45:14 -0400 Subject: [PATCH 06/69] chore: gen a whole bunch of python --- sdks/python/examples/simple/worker.py | 4 +- sdks/python/hatchet_sdk/clients/admin.py | 4 + .../hatchet_sdk/clients/rest/__init__.py | 5 + .../clients/rest/api/durable_tasks_api.py | 325 +++--------------- .../clients/rest/api/rate_limits_api.py | 280 +++++++++++++++ .../clients/rest/api/tenant_api.py | 38 +- .../clients/rest/api/webhook_api.py | 3 + .../clients/rest/api/worker_api.py | 77 ++++- .../clients/rest/models/__init__.py | 5 + .../clients/rest/models/feature_flag_id.py | 2 + .../hatchet_sdk/clients/rest/models/tenant.py | 13 + .../rest/models/v1_durable_event_log_entry.py | 45 ++- .../rest/models/v1_durable_wait_condition.py | 102 ++++++ .../models/v1_durable_wait_condition_kind.py | 1 + .../clients/rest/models/v1_wait_item.py | 119 +++++++ .../hatchet_sdk/clients/rest/models/worker.py | 17 +- .../clients/rest/models/worker_status.py | 35 ++ .../hatchet_sdk/contracts/v1/workflows_pb2.py | 70 ++-- .../contracts/v1/workflows_pb2.pyi | 14 +- sdks/python/hatchet_sdk/hatchet.py | 12 + sdks/python/hatchet_sdk/runnables/types.py | 1 + sdks/python/hatchet_sdk/runnables/workflow.py | 3 + sdks/python/poetry.lock | 277 ++++++++------- 23 files changed, 981 insertions(+), 471 deletions(-) create mode 100644 sdks/python/hatchet_sdk/clients/rest/models/v1_durable_wait_condition.py create mode 100644 sdks/python/hatchet_sdk/clients/rest/models/v1_wait_item.py create mode 100644 sdks/python/hatchet_sdk/clients/rest/models/worker_status.py diff --git a/sdks/python/examples/simple/worker.py b/sdks/python/examples/simple/worker.py index f7970eb74b..ee67ee8afd 100644 --- a/sdks/python/examples/simple/worker.py +++ b/sdks/python/examples/simple/worker.py @@ -4,7 +4,7 @@ hatchet = Hatchet() -@hatchet.task() +@hatchet.task(idempotency_key_expression="input.some_id") def simple(input: EmptyModel, ctx: Context) -> dict[str, str]: return {"result": "Hello, world!"} @@ -18,7 +18,7 @@ async def simple_durable(input: EmptyModel, ctx: DurableContext) -> dict[str, st def main() -> None: worker = hatchet.worker( "test-worker", - workflows=[simple, simple_durable], + workflows=[simple], ) worker.start() diff --git a/sdks/python/hatchet_sdk/clients/admin.py b/sdks/python/hatchet_sdk/clients/admin.py index e47400a1c4..64aa599712 100644 --- a/sdks/python/hatchet_sdk/clients/admin.py +++ b/sdks/python/hatchet_sdk/clients/admin.py @@ -294,6 +294,10 @@ def put_workflow( self.client = AdminServiceStub(conn) put_workflow = tenacity_retry(self.client.PutWorkflow, self.config.tenacity) + # print( + # "Putting workflow with idempotency key expression:", + # workflow.idempotency_key_expr, + # ) return cast( workflow_protos.CreateWorkflowVersionResponse, put_workflow( diff --git a/sdks/python/hatchet_sdk/clients/rest/__init__.py b/sdks/python/hatchet_sdk/clients/rest/__init__.py index 20e169532f..08d61c8636 100644 --- a/sdks/python/hatchet_sdk/clients/rest/__init__.py +++ b/sdks/python/hatchet_sdk/clients/rest/__init__.py @@ -314,6 +314,9 @@ from hatchet_sdk.clients.rest.models.v1_durable_event_log_kind import ( V1DurableEventLogKind, ) +from hatchet_sdk.clients.rest.models.v1_durable_wait_condition import ( + V1DurableWaitCondition, +) from hatchet_sdk.clients.rest.models.v1_durable_wait_condition_kind import ( V1DurableWaitConditionKind, ) @@ -362,6 +365,7 @@ from hatchet_sdk.clients.rest.models.v1_update_webhook_request import ( V1UpdateWebhookRequest, ) +from hatchet_sdk.clients.rest.models.v1_wait_item import V1WaitItem from hatchet_sdk.clients.rest.models.v1_webhook import V1Webhook from hatchet_sdk.clients.rest.models.v1_webhook_api_key_auth import V1WebhookAPIKeyAuth from hatchet_sdk.clients.rest.models.v1_webhook_auth_type import V1WebhookAuthType @@ -409,6 +413,7 @@ from hatchet_sdk.clients.rest.models.worker_runtime_info import WorkerRuntimeInfo from hatchet_sdk.clients.rest.models.worker_runtime_sdks import WorkerRuntimeSDKs from hatchet_sdk.clients.rest.models.worker_slot_config import WorkerSlotConfig +from hatchet_sdk.clients.rest.models.worker_status import WorkerStatus from hatchet_sdk.clients.rest.models.worker_type import WorkerType from hatchet_sdk.clients.rest.models.workflow import Workflow from hatchet_sdk.clients.rest.models.workflow_concurrency import WorkflowConcurrency diff --git a/sdks/python/hatchet_sdk/clients/rest/api/durable_tasks_api.py b/sdks/python/hatchet_sdk/clients/rest/api/durable_tasks_api.py index b9de2731fa..933032efbf 100644 --- a/sdks/python/hatchet_sdk/clients/rest/api/durable_tasks_api.py +++ b/sdks/python/hatchet_sdk/clients/rest/api/durable_tasks_api.py @@ -16,8 +16,8 @@ from typing import Any, Dict, List, Optional, Tuple, Union from typing_extensions import Annotated -from pydantic import Field -from typing import List +from pydantic import Field, StrictInt +from typing import List, Optional from typing_extensions import Annotated from hatchet_sdk.clients.rest.models.v1_durable_event_log_entry import ( V1DurableEventLogEntry, @@ -52,6 +52,14 @@ def v1_durable_task_event_log_list( description="The durable task external id", ), ], + offset: Annotated[ + Optional[StrictInt], + Field(description="The number of event log entries to skip"), + ] = None, + limit: Annotated[ + Optional[StrictInt], + Field(description="The number of event log entries to limit by"), + ] = None, _request_timeout: Union[ None, Annotated[StrictFloat, Field(gt=0)], @@ -70,6 +78,10 @@ def v1_durable_task_event_log_list( :param durable_task: The durable task external id (required) :type durable_task: str + :param offset: The number of event log entries to skip + :type offset: int + :param limit: The number of event log entries to limit by + :type limit: int :param _request_timeout: timeout setting for this request. If one number provided, it will be total request timeout. It can also be a pair (tuple) of @@ -94,6 +106,8 @@ def v1_durable_task_event_log_list( _param = self._v1_durable_task_event_log_list_serialize( durable_task=durable_task, + offset=offset, + limit=limit, _request_auth=_request_auth, _content_type=_content_type, _headers=_headers, @@ -127,6 +141,14 @@ def v1_durable_task_event_log_list_with_http_info( description="The durable task external id", ), ], + offset: Annotated[ + Optional[StrictInt], + Field(description="The number of event log entries to skip"), + ] = None, + limit: Annotated[ + Optional[StrictInt], + Field(description="The number of event log entries to limit by"), + ] = None, _request_timeout: Union[ None, Annotated[StrictFloat, Field(gt=0)], @@ -145,6 +167,10 @@ def v1_durable_task_event_log_list_with_http_info( :param durable_task: The durable task external id (required) :type durable_task: str + :param offset: The number of event log entries to skip + :type offset: int + :param limit: The number of event log entries to limit by + :type limit: int :param _request_timeout: timeout setting for this request. If one number provided, it will be total request timeout. It can also be a pair (tuple) of @@ -169,6 +195,8 @@ def v1_durable_task_event_log_list_with_http_info( _param = self._v1_durable_task_event_log_list_serialize( durable_task=durable_task, + offset=offset, + limit=limit, _request_auth=_request_auth, _content_type=_content_type, _headers=_headers, @@ -202,6 +230,14 @@ def v1_durable_task_event_log_list_without_preload_content( description="The durable task external id", ), ], + offset: Annotated[ + Optional[StrictInt], + Field(description="The number of event log entries to skip"), + ] = None, + limit: Annotated[ + Optional[StrictInt], + Field(description="The number of event log entries to limit by"), + ] = None, _request_timeout: Union[ None, Annotated[StrictFloat, Field(gt=0)], @@ -220,6 +256,10 @@ def v1_durable_task_event_log_list_without_preload_content( :param durable_task: The durable task external id (required) :type durable_task: str + :param offset: The number of event log entries to skip + :type offset: int + :param limit: The number of event log entries to limit by + :type limit: int :param _request_timeout: timeout setting for this request. If one number provided, it will be total request timeout. It can also be a pair (tuple) of @@ -244,6 +284,8 @@ def v1_durable_task_event_log_list_without_preload_content( _param = self._v1_durable_task_event_log_list_serialize( durable_task=durable_task, + offset=offset, + limit=limit, _request_auth=_request_auth, _content_type=_content_type, _headers=_headers, @@ -264,6 +306,8 @@ def v1_durable_task_event_log_list_without_preload_content( def _v1_durable_task_event_log_list_serialize( self, durable_task, + offset, + limit, _request_auth, _content_type, _headers, @@ -287,281 +331,14 @@ def _v1_durable_task_event_log_list_serialize( if durable_task is not None: _path_params["durable-task"] = durable_task # process the query parameters - # process the header parameters - # process the form parameters - # process the body parameter + if offset is not None: - # set the HTTP header `Accept` - if "Accept" not in _header_params: - _header_params["Accept"] = self.api_client.select_header_accept( - ["application/json"] - ) + _query_params.append(("offset", offset)) - # authentication setting - _auth_settings: List[str] = ["cookieAuth", "bearerAuth"] + if limit is not None: - return self.api_client.param_serialize( - method="GET", - resource_path="/api/v1/stable/durable-tasks/{durable-task}", - path_params=_path_params, - query_params=_query_params, - header_params=_header_params, - body=_body_params, - post_params=_form_params, - files=_files, - auth_settings=_auth_settings, - collection_formats=_collection_formats, - _host=_host, - _request_auth=_request_auth, - ) + _query_params.append(("limit", limit)) - @validate_call - def v1_durable_task_waits_list( - self, - durable_task: Annotated[ - str, - Field( - min_length=36, - strict=True, - max_length=36, - description="The durable task external id", - ), - ], - _request_timeout: Union[ - None, - Annotated[StrictFloat, Field(gt=0)], - Tuple[ - Annotated[StrictFloat, Field(gt=0)], Annotated[StrictFloat, Field(gt=0)] - ], - ] = None, - _request_auth: Optional[Dict[StrictStr, Any]] = None, - _content_type: Optional[StrictStr] = None, - _headers: Optional[Dict[StrictStr, Any]] = None, - _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, - ) -> List[V1DurableEventLogEntry]: - """List durable task in-flight waits - - Lists all currently in-flight (unsatisfied) waits for a durable task. - - :param durable_task: The durable task external id (required) - :type durable_task: str - :param _request_timeout: timeout setting for this request. If one - number provided, it will be total request - timeout. It can also be a pair (tuple) of - (connection, read) timeouts. - :type _request_timeout: int, tuple(int, int), optional - :param _request_auth: set to override the auth_settings for an a single - request; this effectively ignores the - authentication in the spec for a single request. - :type _request_auth: dict, optional - :param _content_type: force content-type for the request. - :type _content_type: str, Optional - :param _headers: set to override the headers for a single - request; this effectively ignores the headers - in the spec for a single request. - :type _headers: dict, optional - :param _host_index: set to override the host_index for a single - request; this effectively ignores the host_index - in the spec for a single request. - :type _host_index: int, optional - :return: Returns the result object. - """ # noqa: E501 - - _param = self._v1_durable_task_waits_list_serialize( - durable_task=durable_task, - _request_auth=_request_auth, - _content_type=_content_type, - _headers=_headers, - _host_index=_host_index, - ) - - _response_types_map: Dict[str, Optional[str]] = { - "200": "List[V1DurableEventLogEntry]", - "400": "APIErrors", - "403": "APIErrors", - "404": "APIErrors", - } - response_data = self.api_client.call_api( - *_param, _request_timeout=_request_timeout - ) - response_data.read() - return self.api_client.response_deserialize( - response_data=response_data, - response_types_map=_response_types_map, - ).data - - @validate_call - def v1_durable_task_waits_list_with_http_info( - self, - durable_task: Annotated[ - str, - Field( - min_length=36, - strict=True, - max_length=36, - description="The durable task external id", - ), - ], - _request_timeout: Union[ - None, - Annotated[StrictFloat, Field(gt=0)], - Tuple[ - Annotated[StrictFloat, Field(gt=0)], Annotated[StrictFloat, Field(gt=0)] - ], - ] = None, - _request_auth: Optional[Dict[StrictStr, Any]] = None, - _content_type: Optional[StrictStr] = None, - _headers: Optional[Dict[StrictStr, Any]] = None, - _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, - ) -> ApiResponse[List[V1DurableEventLogEntry]]: - """List durable task in-flight waits - - Lists all currently in-flight (unsatisfied) waits for a durable task. - - :param durable_task: The durable task external id (required) - :type durable_task: str - :param _request_timeout: timeout setting for this request. If one - number provided, it will be total request - timeout. It can also be a pair (tuple) of - (connection, read) timeouts. - :type _request_timeout: int, tuple(int, int), optional - :param _request_auth: set to override the auth_settings for an a single - request; this effectively ignores the - authentication in the spec for a single request. - :type _request_auth: dict, optional - :param _content_type: force content-type for the request. - :type _content_type: str, Optional - :param _headers: set to override the headers for a single - request; this effectively ignores the headers - in the spec for a single request. - :type _headers: dict, optional - :param _host_index: set to override the host_index for a single - request; this effectively ignores the host_index - in the spec for a single request. - :type _host_index: int, optional - :return: Returns the result object. - """ # noqa: E501 - - _param = self._v1_durable_task_waits_list_serialize( - durable_task=durable_task, - _request_auth=_request_auth, - _content_type=_content_type, - _headers=_headers, - _host_index=_host_index, - ) - - _response_types_map: Dict[str, Optional[str]] = { - "200": "List[V1DurableEventLogEntry]", - "400": "APIErrors", - "403": "APIErrors", - "404": "APIErrors", - } - response_data = self.api_client.call_api( - *_param, _request_timeout=_request_timeout - ) - response_data.read() - return self.api_client.response_deserialize( - response_data=response_data, - response_types_map=_response_types_map, - ) - - @validate_call - def v1_durable_task_waits_list_without_preload_content( - self, - durable_task: Annotated[ - str, - Field( - min_length=36, - strict=True, - max_length=36, - description="The durable task external id", - ), - ], - _request_timeout: Union[ - None, - Annotated[StrictFloat, Field(gt=0)], - Tuple[ - Annotated[StrictFloat, Field(gt=0)], Annotated[StrictFloat, Field(gt=0)] - ], - ] = None, - _request_auth: Optional[Dict[StrictStr, Any]] = None, - _content_type: Optional[StrictStr] = None, - _headers: Optional[Dict[StrictStr, Any]] = None, - _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, - ) -> RESTResponseType: - """List durable task in-flight waits - - Lists all currently in-flight (unsatisfied) waits for a durable task. - - :param durable_task: The durable task external id (required) - :type durable_task: str - :param _request_timeout: timeout setting for this request. If one - number provided, it will be total request - timeout. It can also be a pair (tuple) of - (connection, read) timeouts. - :type _request_timeout: int, tuple(int, int), optional - :param _request_auth: set to override the auth_settings for an a single - request; this effectively ignores the - authentication in the spec for a single request. - :type _request_auth: dict, optional - :param _content_type: force content-type for the request. - :type _content_type: str, Optional - :param _headers: set to override the headers for a single - request; this effectively ignores the headers - in the spec for a single request. - :type _headers: dict, optional - :param _host_index: set to override the host_index for a single - request; this effectively ignores the host_index - in the spec for a single request. - :type _host_index: int, optional - :return: Returns the result object. - """ # noqa: E501 - - _param = self._v1_durable_task_waits_list_serialize( - durable_task=durable_task, - _request_auth=_request_auth, - _content_type=_content_type, - _headers=_headers, - _host_index=_host_index, - ) - - _response_types_map: Dict[str, Optional[str]] = { - "200": "List[V1DurableEventLogEntry]", - "400": "APIErrors", - "403": "APIErrors", - "404": "APIErrors", - } - response_data = self.api_client.call_api( - *_param, _request_timeout=_request_timeout - ) - return response_data.response - - def _v1_durable_task_waits_list_serialize( - self, - durable_task, - _request_auth, - _content_type, - _headers, - _host_index, - ) -> RequestSerialized: - - _host = None - - _collection_formats: Dict[str, str] = {} - - _path_params: Dict[str, str] = {} - _query_params: List[Tuple[str, str]] = [] - _header_params: Dict[str, Optional[str]] = _headers or {} - _form_params: List[Tuple[str, str]] = [] - _files: Dict[ - str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]] - ] = {} - _body_params: Optional[bytes] = None - - # process the path parameters - if durable_task is not None: - _path_params["durable-task"] = durable_task - # process the query parameters # process the header parameters # process the form parameters # process the body parameter @@ -577,7 +354,7 @@ def _v1_durable_task_waits_list_serialize( return self.api_client.param_serialize( method="GET", - resource_path="/api/v1/stable/durable-tasks/{durable-task}/waits", + resource_path="/api/v1/stable/durable-tasks/{durable-task}", path_params=_path_params, query_params=_query_params, header_params=_header_params, diff --git a/sdks/python/hatchet_sdk/clients/rest/api/rate_limits_api.py b/sdks/python/hatchet_sdk/clients/rest/api/rate_limits_api.py index abe385a3e5..353b214eff 100644 --- a/sdks/python/hatchet_sdk/clients/rest/api/rate_limits_api.py +++ b/sdks/python/hatchet_sdk/clients/rest/api/rate_limits_api.py @@ -44,6 +44,286 @@ def __init__(self, api_client=None) -> None: api_client = ApiClient.get_default() self.api_client = api_client + @validate_call + def rate_limit_delete( + self, + tenant: Annotated[ + str, + Field( + min_length=36, strict=True, max_length=36, description="The tenant id" + ), + ], + key: Annotated[StrictStr, Field(description="The limit key")], + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], Annotated[StrictFloat, Field(gt=0)] + ], + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> None: + """Delete rate limit + + Delete a rate limit for a tenant. + + :param tenant: The tenant id (required) + :type tenant: str + :param key: The limit key (required) + :type key: str + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._rate_limit_delete_serialize( + tenant=tenant, + key=key, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index, + ) + + _response_types_map: Dict[str, Optional[str]] = { + "204": None, + "400": "APIErrors", + "403": "APIErrors", + } + response_data = self.api_client.call_api( + *_param, _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + @validate_call + def rate_limit_delete_with_http_info( + self, + tenant: Annotated[ + str, + Field( + min_length=36, strict=True, max_length=36, description="The tenant id" + ), + ], + key: Annotated[StrictStr, Field(description="The limit key")], + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], Annotated[StrictFloat, Field(gt=0)] + ], + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[None]: + """Delete rate limit + + Delete a rate limit for a tenant. + + :param tenant: The tenant id (required) + :type tenant: str + :param key: The limit key (required) + :type key: str + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._rate_limit_delete_serialize( + tenant=tenant, + key=key, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index, + ) + + _response_types_map: Dict[str, Optional[str]] = { + "204": None, + "400": "APIErrors", + "403": "APIErrors", + } + response_data = self.api_client.call_api( + *_param, _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + @validate_call + def rate_limit_delete_without_preload_content( + self, + tenant: Annotated[ + str, + Field( + min_length=36, strict=True, max_length=36, description="The tenant id" + ), + ], + key: Annotated[StrictStr, Field(description="The limit key")], + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], Annotated[StrictFloat, Field(gt=0)] + ], + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """Delete rate limit + + Delete a rate limit for a tenant. + + :param tenant: The tenant id (required) + :type tenant: str + :param key: The limit key (required) + :type key: str + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._rate_limit_delete_serialize( + tenant=tenant, + key=key, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index, + ) + + _response_types_map: Dict[str, Optional[str]] = { + "204": None, + "400": "APIErrors", + "403": "APIErrors", + } + response_data = self.api_client.call_api( + *_param, _request_timeout=_request_timeout + ) + return response_data.response + + def _rate_limit_delete_serialize( + self, + tenant, + key, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> RequestSerialized: + + _host = None + + _collection_formats: Dict[str, str] = {} + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[ + str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]] + ] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + if tenant is not None: + _path_params["tenant"] = tenant + # process the query parameters + if key is not None: + + _query_params.append(("key", key)) + + # process the header parameters + # process the form parameters + # process the body parameter + + # set the HTTP header `Accept` + if "Accept" not in _header_params: + _header_params["Accept"] = self.api_client.select_header_accept( + ["application/json"] + ) + + # authentication setting + _auth_settings: List[str] = ["cookieAuth", "bearerAuth"] + + return self.api_client.param_serialize( + method="DELETE", + resource_path="/api/v1/tenants/{tenant}/rate-limits", + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth, + ) + @validate_call def rate_limit_list( self, diff --git a/sdks/python/hatchet_sdk/clients/rest/api/tenant_api.py b/sdks/python/hatchet_sdk/clients/rest/api/tenant_api.py index a1ba9bd495..7d120d249a 100644 --- a/sdks/python/hatchet_sdk/clients/rest/api/tenant_api.py +++ b/sdks/python/hatchet_sdk/clients/rest/api/tenant_api.py @@ -17,7 +17,7 @@ from typing_extensions import Annotated from pydantic import Field, StrictStr -from typing import Dict, Optional +from typing import Dict, List, Optional from typing_extensions import Annotated from hatchet_sdk.clients.rest.models.accept_invite_request import AcceptInviteRequest from hatchet_sdk.clients.rest.models.create_tenant_alert_email_group_request import ( @@ -2553,6 +2553,12 @@ def tenant_get_task_stats( min_length=36, strict=True, max_length=36, description="The tenant ID" ), ], + task_names: Annotated[ + Optional[List[StrictStr]], + Field( + description="Task names that must appear in the response. Missing tasks are zero-filled so KEDA's metrics-api JSONPath always resolves." + ), + ] = None, _request_timeout: Union[ None, Annotated[StrictFloat, Field(gt=0)], @@ -2571,6 +2577,8 @@ def tenant_get_task_stats( :param tenant: The tenant ID (required) :type tenant: str + :param task_names: Task names that must appear in the response. Missing tasks are zero-filled so KEDA's metrics-api JSONPath always resolves. + :type task_names: List[str] :param _request_timeout: timeout setting for this request. If one number provided, it will be total request timeout. It can also be a pair (tuple) of @@ -2595,6 +2603,7 @@ def tenant_get_task_stats( _param = self._tenant_get_task_stats_serialize( tenant=tenant, + task_names=task_names, _request_auth=_request_auth, _content_type=_content_type, _headers=_headers, @@ -2625,6 +2634,12 @@ def tenant_get_task_stats_with_http_info( min_length=36, strict=True, max_length=36, description="The tenant ID" ), ], + task_names: Annotated[ + Optional[List[StrictStr]], + Field( + description="Task names that must appear in the response. Missing tasks are zero-filled so KEDA's metrics-api JSONPath always resolves." + ), + ] = None, _request_timeout: Union[ None, Annotated[StrictFloat, Field(gt=0)], @@ -2643,6 +2658,8 @@ def tenant_get_task_stats_with_http_info( :param tenant: The tenant ID (required) :type tenant: str + :param task_names: Task names that must appear in the response. Missing tasks are zero-filled so KEDA's metrics-api JSONPath always resolves. + :type task_names: List[str] :param _request_timeout: timeout setting for this request. If one number provided, it will be total request timeout. It can also be a pair (tuple) of @@ -2667,6 +2684,7 @@ def tenant_get_task_stats_with_http_info( _param = self._tenant_get_task_stats_serialize( tenant=tenant, + task_names=task_names, _request_auth=_request_auth, _content_type=_content_type, _headers=_headers, @@ -2697,6 +2715,12 @@ def tenant_get_task_stats_without_preload_content( min_length=36, strict=True, max_length=36, description="The tenant ID" ), ], + task_names: Annotated[ + Optional[List[StrictStr]], + Field( + description="Task names that must appear in the response. Missing tasks are zero-filled so KEDA's metrics-api JSONPath always resolves." + ), + ] = None, _request_timeout: Union[ None, Annotated[StrictFloat, Field(gt=0)], @@ -2715,6 +2739,8 @@ def tenant_get_task_stats_without_preload_content( :param tenant: The tenant ID (required) :type tenant: str + :param task_names: Task names that must appear in the response. Missing tasks are zero-filled so KEDA's metrics-api JSONPath always resolves. + :type task_names: List[str] :param _request_timeout: timeout setting for this request. If one number provided, it will be total request timeout. It can also be a pair (tuple) of @@ -2739,6 +2765,7 @@ def tenant_get_task_stats_without_preload_content( _param = self._tenant_get_task_stats_serialize( tenant=tenant, + task_names=task_names, _request_auth=_request_auth, _content_type=_content_type, _headers=_headers, @@ -2759,6 +2786,7 @@ def tenant_get_task_stats_without_preload_content( def _tenant_get_task_stats_serialize( self, tenant, + task_names, _request_auth, _content_type, _headers, @@ -2767,7 +2795,9 @@ def _tenant_get_task_stats_serialize( _host = None - _collection_formats: Dict[str, str] = {} + _collection_formats: Dict[str, str] = { + "taskNames": "multi", + } _path_params: Dict[str, str] = {} _query_params: List[Tuple[str, str]] = [] @@ -2782,6 +2812,10 @@ def _tenant_get_task_stats_serialize( if tenant is not None: _path_params["tenant"] = tenant # process the query parameters + if task_names is not None: + + _query_params.append(("taskNames", task_names)) + # process the header parameters # process the form parameters # process the body parameter diff --git a/sdks/python/hatchet_sdk/clients/rest/api/webhook_api.py b/sdks/python/hatchet_sdk/clients/rest/api/webhook_api.py index 89fc7448dd..428849ffe4 100644 --- a/sdks/python/hatchet_sdk/clients/rest/api/webhook_api.py +++ b/sdks/python/hatchet_sdk/clients/rest/api/webhook_api.py @@ -1339,6 +1339,7 @@ def v1_webhook_receive( _response_types_map: Dict[str, Optional[str]] = { "200": "V1WebhookResponse", + "204": None, "400": "APIErrors", "403": "APIErrors", } @@ -1414,6 +1415,7 @@ def v1_webhook_receive_with_http_info( _response_types_map: Dict[str, Optional[str]] = { "200": "V1WebhookResponse", + "204": None, "400": "APIErrors", "403": "APIErrors", } @@ -1489,6 +1491,7 @@ def v1_webhook_receive_without_preload_content( _response_types_map: Dict[str, Optional[str]] = { "200": "V1WebhookResponse", + "204": None, "400": "APIErrors", "403": "APIErrors", } diff --git a/sdks/python/hatchet_sdk/clients/rest/api/worker_api.py b/sdks/python/hatchet_sdk/clients/rest/api/worker_api.py index a882d1d25d..b14bfe9cf6 100644 --- a/sdks/python/hatchet_sdk/clients/rest/api/worker_api.py +++ b/sdks/python/hatchet_sdk/clients/rest/api/worker_api.py @@ -16,11 +16,13 @@ from typing import Any, Dict, List, Optional, Tuple, Union from typing_extensions import Annotated -from pydantic import Field +from pydantic import Field, StrictInt +from typing import List, Optional from typing_extensions import Annotated from hatchet_sdk.clients.rest.models.update_worker_request import UpdateWorkerRequest from hatchet_sdk.clients.rest.models.worker import Worker from hatchet_sdk.clients.rest.models.worker_list import WorkerList +from hatchet_sdk.clients.rest.models.worker_status import WorkerStatus from hatchet_sdk.clients.rest.api_client import ApiClient, RequestSerialized from hatchet_sdk.clients.rest.api_response import ApiResponse @@ -311,6 +313,15 @@ def worker_list( min_length=36, strict=True, max_length=36, description="The tenant id" ), ], + offset: Annotated[ + Optional[StrictInt], Field(description="The number to skip") + ] = None, + limit: Annotated[ + Optional[StrictInt], Field(description="The number to limit by") + ] = None, + statuses: Annotated[ + Optional[List[WorkerStatus]], Field(description="Filter by worker status") + ] = None, _request_timeout: Union[ None, Annotated[StrictFloat, Field(gt=0)], @@ -329,6 +340,12 @@ def worker_list( :param tenant: The tenant id (required) :type tenant: str + :param offset: The number to skip + :type offset: int + :param limit: The number to limit by + :type limit: int + :param statuses: Filter by worker status + :type statuses: List[WorkerStatus] :param _request_timeout: timeout setting for this request. If one number provided, it will be total request timeout. It can also be a pair (tuple) of @@ -353,6 +370,9 @@ def worker_list( _param = self._worker_list_serialize( tenant=tenant, + offset=offset, + limit=limit, + statuses=statuses, _request_auth=_request_auth, _content_type=_content_type, _headers=_headers, @@ -382,6 +402,15 @@ def worker_list_with_http_info( min_length=36, strict=True, max_length=36, description="The tenant id" ), ], + offset: Annotated[ + Optional[StrictInt], Field(description="The number to skip") + ] = None, + limit: Annotated[ + Optional[StrictInt], Field(description="The number to limit by") + ] = None, + statuses: Annotated[ + Optional[List[WorkerStatus]], Field(description="Filter by worker status") + ] = None, _request_timeout: Union[ None, Annotated[StrictFloat, Field(gt=0)], @@ -400,6 +429,12 @@ def worker_list_with_http_info( :param tenant: The tenant id (required) :type tenant: str + :param offset: The number to skip + :type offset: int + :param limit: The number to limit by + :type limit: int + :param statuses: Filter by worker status + :type statuses: List[WorkerStatus] :param _request_timeout: timeout setting for this request. If one number provided, it will be total request timeout. It can also be a pair (tuple) of @@ -424,6 +459,9 @@ def worker_list_with_http_info( _param = self._worker_list_serialize( tenant=tenant, + offset=offset, + limit=limit, + statuses=statuses, _request_auth=_request_auth, _content_type=_content_type, _headers=_headers, @@ -453,6 +491,15 @@ def worker_list_without_preload_content( min_length=36, strict=True, max_length=36, description="The tenant id" ), ], + offset: Annotated[ + Optional[StrictInt], Field(description="The number to skip") + ] = None, + limit: Annotated[ + Optional[StrictInt], Field(description="The number to limit by") + ] = None, + statuses: Annotated[ + Optional[List[WorkerStatus]], Field(description="Filter by worker status") + ] = None, _request_timeout: Union[ None, Annotated[StrictFloat, Field(gt=0)], @@ -471,6 +518,12 @@ def worker_list_without_preload_content( :param tenant: The tenant id (required) :type tenant: str + :param offset: The number to skip + :type offset: int + :param limit: The number to limit by + :type limit: int + :param statuses: Filter by worker status + :type statuses: List[WorkerStatus] :param _request_timeout: timeout setting for this request. If one number provided, it will be total request timeout. It can also be a pair (tuple) of @@ -495,6 +548,9 @@ def worker_list_without_preload_content( _param = self._worker_list_serialize( tenant=tenant, + offset=offset, + limit=limit, + statuses=statuses, _request_auth=_request_auth, _content_type=_content_type, _headers=_headers, @@ -514,6 +570,9 @@ def worker_list_without_preload_content( def _worker_list_serialize( self, tenant, + offset, + limit, + statuses, _request_auth, _content_type, _headers, @@ -522,7 +581,9 @@ def _worker_list_serialize( _host = None - _collection_formats: Dict[str, str] = {} + _collection_formats: Dict[str, str] = { + "statuses": "multi", + } _path_params: Dict[str, str] = {} _query_params: List[Tuple[str, str]] = [] @@ -537,6 +598,18 @@ def _worker_list_serialize( if tenant is not None: _path_params["tenant"] = tenant # process the query parameters + if offset is not None: + + _query_params.append(("offset", offset)) + + if limit is not None: + + _query_params.append(("limit", limit)) + + if statuses is not None: + + _query_params.append(("statuses", statuses)) + # process the header parameters # process the form parameters # process the body parameter diff --git a/sdks/python/hatchet_sdk/clients/rest/models/__init__.py b/sdks/python/hatchet_sdk/clients/rest/models/__init__.py index 03465833d5..9b4a7c2b9e 100644 --- a/sdks/python/hatchet_sdk/clients/rest/models/__init__.py +++ b/sdks/python/hatchet_sdk/clients/rest/models/__init__.py @@ -274,6 +274,9 @@ from hatchet_sdk.clients.rest.models.v1_durable_event_log_kind import ( V1DurableEventLogKind, ) +from hatchet_sdk.clients.rest.models.v1_durable_wait_condition import ( + V1DurableWaitCondition, +) from hatchet_sdk.clients.rest.models.v1_durable_wait_condition_kind import ( V1DurableWaitConditionKind, ) @@ -322,6 +325,7 @@ from hatchet_sdk.clients.rest.models.v1_update_webhook_request import ( V1UpdateWebhookRequest, ) +from hatchet_sdk.clients.rest.models.v1_wait_item import V1WaitItem from hatchet_sdk.clients.rest.models.v1_webhook import V1Webhook from hatchet_sdk.clients.rest.models.v1_webhook_api_key_auth import V1WebhookAPIKeyAuth from hatchet_sdk.clients.rest.models.v1_webhook_auth_type import V1WebhookAuthType @@ -369,6 +373,7 @@ from hatchet_sdk.clients.rest.models.worker_runtime_info import WorkerRuntimeInfo from hatchet_sdk.clients.rest.models.worker_runtime_sdks import WorkerRuntimeSDKs from hatchet_sdk.clients.rest.models.worker_slot_config import WorkerSlotConfig +from hatchet_sdk.clients.rest.models.worker_status import WorkerStatus from hatchet_sdk.clients.rest.models.worker_type import WorkerType from hatchet_sdk.clients.rest.models.workflow import Workflow from hatchet_sdk.clients.rest.models.workflow_concurrency import WorkflowConcurrency diff --git a/sdks/python/hatchet_sdk/clients/rest/models/feature_flag_id.py b/sdks/python/hatchet_sdk/clients/rest/models/feature_flag_id.py index 4ee3bce160..3eb473c288 100644 --- a/sdks/python/hatchet_sdk/clients/rest/models/feature_flag_id.py +++ b/sdks/python/hatchet_sdk/clients/rest/models/feature_flag_id.py @@ -28,6 +28,8 @@ class FeatureFlagId(str, Enum): TENANT_MINUS_LOG_MINUS_WORKFLOW_MINUS_FILTER_MINUS_ENABLED = ( "tenant-log-workflow-filter-enabled" ) + TRACE_MINUS_MINIMAP_MINUS_ENABLED = "trace-minimap-enabled" + ORGANIZATION_MINUS_SSO_MINUS_ENABLED = "organization-sso-enabled" @classmethod def from_json(cls, json_str: str) -> Self: diff --git a/sdks/python/hatchet_sdk/clients/rest/models/tenant.py b/sdks/python/hatchet_sdk/clients/rest/models/tenant.py index 2694e99c60..6381be0a0b 100644 --- a/sdks/python/hatchet_sdk/clients/rest/models/tenant.py +++ b/sdks/python/hatchet_sdk/clients/rest/models/tenant.py @@ -47,6 +47,15 @@ class Tenant(BaseModel): environment: Optional[TenantEnvironment] = Field( default=None, description="The environment type of the tenant." ) + server_url: Optional[StrictStr] = Field( + default=None, + description="The server URL for the tenant (includes scheme)", + alias="serverUrl", + ) + region: Optional[StrictStr] = Field( + default=None, + description="Control-plane shard region for the tenant (e.g. aws:us-west-2).", + ) __properties: ClassVar[List[str]] = [ "metadata", "name", @@ -55,6 +64,8 @@ class Tenant(BaseModel): "alertMemberEmails", "version", "environment", + "serverUrl", + "region", ] model_config = ConfigDict( @@ -121,6 +132,8 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: "alertMemberEmails": obj.get("alertMemberEmails"), "version": obj.get("version"), "environment": obj.get("environment"), + "serverUrl": obj.get("serverUrl"), + "region": obj.get("region"), } ) return _obj diff --git a/sdks/python/hatchet_sdk/clients/rest/models/v1_durable_event_log_entry.py b/sdks/python/hatchet_sdk/clients/rest/models/v1_durable_event_log_entry.py index 75212fc563..ed10f8195d 100644 --- a/sdks/python/hatchet_sdk/clients/rest/models/v1_durable_event_log_entry.py +++ b/sdks/python/hatchet_sdk/clients/rest/models/v1_durable_event_log_entry.py @@ -19,12 +19,11 @@ from datetime import datetime from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictInt, StrictStr from typing import Any, ClassVar, Dict, List, Optional +from typing_extensions import Annotated from hatchet_sdk.clients.rest.models.v1_durable_event_log_kind import ( V1DurableEventLogKind, ) -from hatchet_sdk.clients.rest.models.v1_durable_wait_condition_kind import ( - V1DurableWaitConditionKind, -) +from hatchet_sdk.clients.rest.models.v1_wait_item import V1WaitItem from typing import Optional, Set from typing_extensions import Self @@ -42,9 +41,7 @@ class V1DurableEventLogEntry(BaseModel): description="The branch id when this entry was first seen.", alias="branchId" ) kind: V1DurableEventLogKind - wait_condition_kind: Optional[V1DurableWaitConditionKind] = Field( - default=None, alias="waitConditionKind" - ) + wait_data: Optional[List[V1WaitItem]] = Field(default=None, alias="waitData") is_satisfied: StrictBool = Field( description="Whether this entry has been satisfied.", alias="isSatisfied" ) @@ -58,24 +55,30 @@ class V1DurableEventLogEntry(BaseModel): ) user_message: Optional[StrictStr] = Field( default=None, - description="A user-provided message or label, send when establishing a durable wait", + description="A user-provided message or label, sent when establishing a durable wait.", alias="userMessage", ) - human_readable_message: Optional[StrictStr] = Field( - default=None, - description="A human-readable message, generated by the engine, describing the current status of a durable task", - alias="humanReadableMessage", + task_external_id: Annotated[ + str, Field(min_length=36, strict=True, max_length=36) + ] = Field( + description="The external id of the durable task this event log entry is associated with.", + alias="taskExternalId", + ) + task_display_name: StrictStr = Field( + description="The display name of the durable task this event log entry is associated with.", + alias="taskDisplayName", ) __properties: ClassVar[List[str]] = [ "nodeId", "branchId", "kind", - "waitConditionKind", + "waitData", "isSatisfied", "satisfiedAt", "insertedAt", "userMessage", - "humanReadableMessage", + "taskExternalId", + "taskDisplayName", ] model_config = ConfigDict( @@ -115,6 +118,13 @@ def to_dict(self) -> Dict[str, Any]: exclude=excluded_fields, exclude_none=True, ) + # override the default output from pydantic by calling `to_dict()` of each item in wait_data (list) + _items = [] + if self.wait_data: + for _item_wait_data in self.wait_data: + if _item_wait_data: + _items.append(_item_wait_data.to_dict()) + _dict["waitData"] = _items return _dict @classmethod @@ -131,12 +141,17 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: "nodeId": obj.get("nodeId"), "branchId": obj.get("branchId"), "kind": obj.get("kind"), - "waitConditionKind": obj.get("waitConditionKind"), + "waitData": ( + [V1WaitItem.from_dict(_item) for _item in obj["waitData"]] + if obj.get("waitData") is not None + else None + ), "isSatisfied": obj.get("isSatisfied"), "satisfiedAt": obj.get("satisfiedAt"), "insertedAt": obj.get("insertedAt"), "userMessage": obj.get("userMessage"), - "humanReadableMessage": obj.get("humanReadableMessage"), + "taskExternalId": obj.get("taskExternalId"), + "taskDisplayName": obj.get("taskDisplayName"), } ) return _obj diff --git a/sdks/python/hatchet_sdk/clients/rest/models/v1_durable_wait_condition.py b/sdks/python/hatchet_sdk/clients/rest/models/v1_durable_wait_condition.py new file mode 100644 index 0000000000..fb97f32649 --- /dev/null +++ b/sdks/python/hatchet_sdk/clients/rest/models/v1_durable_wait_condition.py @@ -0,0 +1,102 @@ +# coding: utf-8 + +""" +Hatchet API + +The Hatchet API + +The version of the OpenAPI document: 1.0.0 +Generated by OpenAPI Generator (https://openapi-generator.tech) + +Do not edit the class manually. +""" # noqa: E501 + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr +from typing import Any, ClassVar, Dict, List, Optional +from hatchet_sdk.clients.rest.models.v1_durable_wait_condition_kind import ( + V1DurableWaitConditionKind, +) +from typing import Optional, Set +from typing_extensions import Self + + +class V1DurableWaitCondition(BaseModel): + """ + V1DurableWaitCondition + """ # noqa: E501 + + kind: V1DurableWaitConditionKind + sleep_duration_ms: Optional[StrictInt] = Field( + default=None, alias="sleepDurationMs" + ) + event_key: Optional[StrictStr] = Field(default=None, alias="eventKey") + workflow_name: Optional[StrictStr] = Field(default=None, alias="workflowName") + __properties: ClassVar[List[str]] = [ + "kind", + "sleepDurationMs", + "eventKey", + "workflowName", + ] + + model_config = ConfigDict( + populate_by_name=True, + validate_assignment=True, + protected_namespaces=(), + ) + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of V1DurableWaitCondition from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + excluded_fields: Set[str] = set([]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of V1DurableWaitCondition from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate( + { + "kind": obj.get("kind"), + "sleepDurationMs": obj.get("sleepDurationMs"), + "eventKey": obj.get("eventKey"), + "workflowName": obj.get("workflowName"), + } + ) + return _obj diff --git a/sdks/python/hatchet_sdk/clients/rest/models/v1_durable_wait_condition_kind.py b/sdks/python/hatchet_sdk/clients/rest/models/v1_durable_wait_condition_kind.py index db6db33364..cafd3d9c7c 100644 --- a/sdks/python/hatchet_sdk/clients/rest/models/v1_durable_wait_condition_kind.py +++ b/sdks/python/hatchet_sdk/clients/rest/models/v1_durable_wait_condition_kind.py @@ -27,6 +27,7 @@ class V1DurableWaitConditionKind(str, Enum): """ SLEEP = "SLEEP" USER_EVENT = "USER_EVENT" + CHILD_WORKFLOW = "CHILD_WORKFLOW" @classmethod def from_json(cls, json_str: str) -> Self: diff --git a/sdks/python/hatchet_sdk/clients/rest/models/v1_wait_item.py b/sdks/python/hatchet_sdk/clients/rest/models/v1_wait_item.py new file mode 100644 index 0000000000..838da534bf --- /dev/null +++ b/sdks/python/hatchet_sdk/clients/rest/models/v1_wait_item.py @@ -0,0 +1,119 @@ +# coding: utf-8 + +""" +Hatchet API + +The Hatchet API + +The version of the OpenAPI document: 1.0.0 +Generated by OpenAPI Generator (https://openapi-generator.tech) + +Do not edit the class manually. +""" # noqa: E501 + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr +from typing import Any, ClassVar, Dict, List, Optional +from hatchet_sdk.clients.rest.models.v1_durable_wait_condition import ( + V1DurableWaitCondition, +) +from hatchet_sdk.clients.rest.models.v1_durable_wait_condition_kind import ( + V1DurableWaitConditionKind, +) +from typing import Optional, Set +from typing_extensions import Self + + +class V1WaitItem(BaseModel): + """ + V1WaitItem + """ # noqa: E501 + + kind: Optional[V1DurableWaitConditionKind] = None + sleep_duration_ms: Optional[StrictInt] = Field( + default=None, alias="sleepDurationMs" + ) + event_key: Optional[StrictStr] = Field(default=None, alias="eventKey") + workflow_name: Optional[StrictStr] = Field(default=None, alias="workflowName") + var_or: Optional[List[V1DurableWaitCondition]] = Field(default=None, alias="or") + __properties: ClassVar[List[str]] = [ + "kind", + "sleepDurationMs", + "eventKey", + "workflowName", + "or", + ] + + model_config = ConfigDict( + populate_by_name=True, + validate_assignment=True, + protected_namespaces=(), + ) + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of V1WaitItem from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + excluded_fields: Set[str] = set([]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # override the default output from pydantic by calling `to_dict()` of each item in var_or (list) + _items = [] + if self.var_or: + for _item_var_or in self.var_or: + if _item_var_or: + _items.append(_item_var_or.to_dict()) + _dict["or"] = _items + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of V1WaitItem from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate( + { + "kind": obj.get("kind"), + "sleepDurationMs": obj.get("sleepDurationMs"), + "eventKey": obj.get("eventKey"), + "workflowName": obj.get("workflowName"), + "or": ( + [V1DurableWaitCondition.from_dict(_item) for _item in obj["or"]] + if obj.get("or") is not None + else None + ), + } + ) + return _obj diff --git a/sdks/python/hatchet_sdk/clients/rest/models/worker.py b/sdks/python/hatchet_sdk/clients/rest/models/worker.py index e150818795..b7af493a10 100644 --- a/sdks/python/hatchet_sdk/clients/rest/models/worker.py +++ b/sdks/python/hatchet_sdk/clients/rest/models/worker.py @@ -17,7 +17,7 @@ import json from datetime import datetime -from pydantic import BaseModel, ConfigDict, Field, StrictStr, field_validator +from pydantic import BaseModel, ConfigDict, Field, StrictStr from typing import Any, ClassVar, Dict, List, Optional from typing_extensions import Annotated from hatchet_sdk.clients.rest.models.api_resource_meta import APIResourceMeta @@ -27,6 +27,7 @@ from hatchet_sdk.clients.rest.models.worker_label import WorkerLabel from hatchet_sdk.clients.rest.models.worker_runtime_info import WorkerRuntimeInfo from hatchet_sdk.clients.rest.models.worker_slot_config import WorkerSlotConfig +from hatchet_sdk.clients.rest.models.worker_status import WorkerStatus from hatchet_sdk.clients.rest.models.worker_type import WorkerType from typing import Optional, Set from typing_extensions import Self @@ -66,7 +67,7 @@ class Worker(BaseModel): description="The recent step runs for the worker.", alias="recentStepRuns", ) - status: Optional[StrictStr] = Field( + status: Optional[WorkerStatus] = Field( default=None, description="The status of the worker." ) slot_config: Optional[Dict[str, WorkerSlotConfig]] = Field( @@ -110,18 +111,6 @@ class Worker(BaseModel): "runtimeInfo", ] - @field_validator("status") - def status_validate_enum(cls, value): - """Validates the enum""" - if value is None: - return value - - if value not in set(["ACTIVE", "INACTIVE", "PAUSED"]): - raise ValueError( - "must be one of enum values ('ACTIVE', 'INACTIVE', 'PAUSED')" - ) - return value - model_config = ConfigDict( populate_by_name=True, validate_assignment=True, diff --git a/sdks/python/hatchet_sdk/clients/rest/models/worker_status.py b/sdks/python/hatchet_sdk/clients/rest/models/worker_status.py new file mode 100644 index 0000000000..ea94ae7a6e --- /dev/null +++ b/sdks/python/hatchet_sdk/clients/rest/models/worker_status.py @@ -0,0 +1,35 @@ +# coding: utf-8 + +""" +Hatchet API + +The Hatchet API + +The version of the OpenAPI document: 1.0.0 +Generated by OpenAPI Generator (https://openapi-generator.tech) + +Do not edit the class manually. +""" # noqa: E501 + +from __future__ import annotations +import json +from enum import Enum +from typing_extensions import Self + + +class WorkerStatus(str, Enum): + """ + WorkerStatus + """ + + """ + allowed enum values + """ + ACTIVE = "ACTIVE" + INACTIVE = "INACTIVE" + PAUSED = "PAUSED" + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Create an instance of WorkerStatus from a JSON string""" + return cls(json.loads(json_str)) diff --git a/sdks/python/hatchet_sdk/contracts/v1/workflows_pb2.py b/sdks/python/hatchet_sdk/contracts/v1/workflows_pb2.py index 3a9ab133c1..226bd7ed10 100644 --- a/sdks/python/hatchet_sdk/contracts/v1/workflows_pb2.py +++ b/sdks/python/hatchet_sdk/contracts/v1/workflows_pb2.py @@ -27,7 +27,7 @@ from hatchet_sdk.contracts.v1.shared import trigger_pb2 as v1_dot_shared_dot_trigger__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x12v1/workflows.proto\x12\x02v1\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x19v1/shared/condition.proto\x1a\x17v1/shared/trigger.proto\"[\n\x12\x43\x61ncelTasksRequest\x12\x14\n\x0c\x65xternal_ids\x18\x01 \x03(\t\x12$\n\x06\x66ilter\x18\x02 \x01(\x0b\x32\x0f.v1.TasksFilterH\x00\x88\x01\x01\x42\t\n\x07_filter\"[\n\x12ReplayTasksRequest\x12\x14\n\x0c\x65xternal_ids\x18\x01 \x03(\t\x12$\n\x06\x66ilter\x18\x02 \x01(\x0b\x32\x0f.v1.TasksFilterH\x00\x88\x01\x01\x42\t\n\x07_filter\"\xb7\x01\n\x0bTasksFilter\x12\x10\n\x08statuses\x18\x01 \x03(\t\x12)\n\x05since\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12.\n\x05until\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampH\x00\x88\x01\x01\x12\x14\n\x0cworkflow_ids\x18\x04 \x03(\t\x12\x1b\n\x13\x61\x64\x64itional_metadata\x18\x05 \x03(\tB\x08\n\x06_until\".\n\x13\x43\x61ncelTasksResponse\x12\x17\n\x0f\x63\x61ncelled_tasks\x18\x01 \x03(\t\"-\n\x13ReplayTasksResponse\x12\x16\n\x0ereplayed_tasks\x18\x01 \x03(\t\"\xae\x02\n\x19TriggerWorkflowRunRequest\x12\x15\n\rworkflow_name\x18\x01 \x01(\t\x12\r\n\x05input\x18\x02 \x01(\x0c\x12\x1b\n\x13\x61\x64\x64itional_metadata\x18\x03 \x01(\x0c\x12\x15\n\x08priority\x18\x04 \x01(\x05H\x00\x88\x01\x01\x12U\n\x15\x64\x65sired_worker_labels\x18\x05 \x03(\x0b\x32\x36.v1.TriggerWorkflowRunRequest.DesiredWorkerLabelsEntry\x1aS\n\x18\x44\x65siredWorkerLabelsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12&\n\x05value\x18\x02 \x01(\x0b\x32\x17.v1.DesiredWorkerLabels:\x02\x38\x01\x42\x0b\n\t_priority\"1\n\x1aTriggerWorkflowRunResponse\x12\x13\n\x0b\x65xternal_id\x18\x01 \x01(\t\"X\n\x18\x42ranchDurableTaskRequest\x12\x18\n\x10task_external_id\x18\x01 \x01(\t\x12\x0f\n\x07node_id\x18\x02 \x01(\x03\x12\x11\n\tbranch_id\x18\x03 \x01(\x03\"Y\n\x19\x42ranchDurableTaskResponse\x12\x18\n\x10task_external_id\x18\x01 \x01(\t\x12\x0f\n\x07node_id\x18\x02 \x01(\x03\x12\x11\n\tbranch_id\x18\x03 \x01(\x03\"\xac\x04\n\x1c\x43reateWorkflowVersionRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x0f\n\x07version\x18\x03 \x01(\t\x12\x16\n\x0e\x65vent_triggers\x18\x04 \x03(\t\x12\x15\n\rcron_triggers\x18\x05 \x03(\t\x12!\n\x05tasks\x18\x06 \x03(\x0b\x32\x12.v1.CreateTaskOpts\x12$\n\x0b\x63oncurrency\x18\x07 \x01(\x0b\x32\x0f.v1.Concurrency\x12\x17\n\ncron_input\x18\x08 \x01(\tH\x00\x88\x01\x01\x12\x30\n\x0fon_failure_task\x18\t \x01(\x0b\x32\x12.v1.CreateTaskOptsH\x01\x88\x01\x01\x12\'\n\x06sticky\x18\n \x01(\x0e\x32\x12.v1.StickyStrategyH\x02\x88\x01\x01\x12\x1d\n\x10\x64\x65\x66\x61ult_priority\x18\x0b \x01(\x05H\x03\x88\x01\x01\x12(\n\x0f\x63oncurrency_arr\x18\x0c \x03(\x0b\x32\x0f.v1.Concurrency\x12*\n\x0f\x64\x65\x66\x61ult_filters\x18\r \x03(\x0b\x32\x11.v1.DefaultFilter\x12\x1e\n\x11input_json_schema\x18\x0e \x01(\x0cH\x04\x88\x01\x01\x42\r\n\x0b_cron_inputB\x12\n\x10_on_failure_taskB\t\n\x07_stickyB\x13\n\x11_default_priorityB\x14\n\x12_input_json_schema\"T\n\rDefaultFilter\x12\x12\n\nexpression\x18\x01 \x01(\t\x12\r\n\x05scope\x18\x02 \x01(\t\x12\x14\n\x07payload\x18\x03 \x01(\x0cH\x00\x88\x01\x01\x42\n\n\x08_payload\"\x93\x01\n\x0b\x43oncurrency\x12\x12\n\nexpression\x18\x01 \x01(\t\x12\x15\n\x08max_runs\x18\x02 \x01(\x05H\x00\x88\x01\x01\x12\x39\n\x0elimit_strategy\x18\x03 \x01(\x0e\x32\x1c.v1.ConcurrencyLimitStrategyH\x01\x88\x01\x01\x42\x0b\n\t_max_runsB\x11\n\x0f_limit_strategy\"\xb7\x05\n\x0e\x43reateTaskOpts\x12\x13\n\x0breadable_id\x18\x01 \x01(\t\x12\x0e\n\x06\x61\x63tion\x18\x02 \x01(\t\x12\x0f\n\x07timeout\x18\x03 \x01(\t\x12\x0e\n\x06inputs\x18\x04 \x01(\t\x12\x0f\n\x07parents\x18\x05 \x03(\t\x12\x0f\n\x07retries\x18\x06 \x01(\x05\x12,\n\x0brate_limits\x18\x07 \x03(\x0b\x32\x17.v1.CreateTaskRateLimit\x12;\n\rworker_labels\x18\x08 \x03(\x0b\x32$.v1.CreateTaskOpts.WorkerLabelsEntry\x12\x1b\n\x0e\x62\x61\x63koff_factor\x18\t \x01(\x02H\x00\x88\x01\x01\x12 \n\x13\x62\x61\x63koff_max_seconds\x18\n \x01(\x05H\x01\x88\x01\x01\x12$\n\x0b\x63oncurrency\x18\x0b \x03(\x0b\x32\x0f.v1.Concurrency\x12+\n\nconditions\x18\x0c \x01(\x0b\x32\x12.v1.TaskConditionsH\x02\x88\x01\x01\x12\x1d\n\x10schedule_timeout\x18\r \x01(\tH\x03\x88\x01\x01\x12\x12\n\nis_durable\x18\x0e \x01(\x08\x12;\n\rslot_requests\x18\x0f \x03(\x0b\x32$.v1.CreateTaskOpts.SlotRequestsEntry\x1aL\n\x11WorkerLabelsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12&\n\x05value\x18\x02 \x01(\x0b\x32\x17.v1.DesiredWorkerLabels:\x02\x38\x01\x1a\x33\n\x11SlotRequestsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x05:\x02\x38\x01\x42\x11\n\x0f_backoff_factorB\x16\n\x14_backoff_max_secondsB\r\n\x0b_conditionsB\x13\n\x11_schedule_timeout\"\xfd\x01\n\x13\x43reateTaskRateLimit\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x12\n\x05units\x18\x02 \x01(\x05H\x00\x88\x01\x01\x12\x15\n\x08key_expr\x18\x03 \x01(\tH\x01\x88\x01\x01\x12\x17\n\nunits_expr\x18\x04 \x01(\tH\x02\x88\x01\x01\x12\x1e\n\x11limit_values_expr\x18\x05 \x01(\tH\x03\x88\x01\x01\x12,\n\x08\x64uration\x18\x06 \x01(\x0e\x32\x15.v1.RateLimitDurationH\x04\x88\x01\x01\x42\x08\n\x06_unitsB\x0b\n\t_key_exprB\r\n\x0b_units_exprB\x14\n\x12_limit_values_exprB\x0b\n\t_duration\"@\n\x1d\x43reateWorkflowVersionResponse\x12\n\n\x02id\x18\x01 \x01(\t\x12\x13\n\x0bworkflow_id\x18\x02 \x01(\t\"+\n\x14GetRunDetailsRequest\x12\x13\n\x0b\x65xternal_id\x18\x01 \x01(\t\"\xaa\x01\n\rTaskRunDetail\x12\x13\n\x0b\x65xternal_id\x18\x01 \x01(\t\x12\x1d\n\x06status\x18\x02 \x01(\x0e\x32\r.v1.RunStatus\x12\x12\n\x05\x65rror\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x13\n\x06output\x18\x04 \x01(\x0cH\x01\x88\x01\x01\x12\x13\n\x0breadable_id\x18\x05 \x01(\t\x12\x12\n\nis_evicted\x18\x06 \x01(\x08\x42\x08\n\x06_errorB\t\n\x07_output\"\x84\x02\n\x15GetRunDetailsResponse\x12\r\n\x05input\x18\x01 \x01(\x0c\x12\x1d\n\x06status\x18\x02 \x01(\x0e\x32\r.v1.RunStatus\x12:\n\ttask_runs\x18\x03 \x03(\x0b\x32\'.v1.GetRunDetailsResponse.TaskRunsEntry\x12\x0c\n\x04\x64one\x18\x04 \x01(\x08\x12\x1b\n\x13\x61\x64\x64itional_metadata\x18\x05 \x01(\x0c\x12\x12\n\nis_evicted\x18\x06 \x01(\x08\x1a\x42\n\rTaskRunsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12 \n\x05value\x18\x02 \x01(\x0b\x32\x11.v1.TaskRunDetail:\x02\x38\x01*$\n\x0eStickyStrategy\x12\x08\n\x04SOFT\x10\x00\x12\x08\n\x04HARD\x10\x01*]\n\x11RateLimitDuration\x12\n\n\x06SECOND\x10\x00\x12\n\n\x06MINUTE\x10\x01\x12\x08\n\x04HOUR\x10\x02\x12\x07\n\x03\x44\x41Y\x10\x03\x12\x08\n\x04WEEK\x10\x04\x12\t\n\x05MONTH\x10\x05\x12\x08\n\x04YEAR\x10\x06*[\n\tRunStatus\x12\n\n\x06QUEUED\x10\x00\x12\x0b\n\x07RUNNING\x10\x01\x12\r\n\tCOMPLETED\x10\x02\x12\n\n\x06\x46\x41ILED\x10\x03\x12\r\n\tCANCELLED\x10\x04\x12\x0b\n\x07\x45VICTED\x10\x05*\x7f\n\x18\x43oncurrencyLimitStrategy\x12\x16\n\x12\x43\x41NCEL_IN_PROGRESS\x10\x00\x12\x0f\n\x0b\x44ROP_NEWEST\x10\x01\x12\x10\n\x0cQUEUE_NEWEST\x10\x02\x12\x15\n\x11GROUP_ROUND_ROBIN\x10\x03\x12\x11\n\rCANCEL_NEWEST\x10\x04\x32\xcf\x03\n\x0c\x41\x64minService\x12R\n\x0bPutWorkflow\x12 .v1.CreateWorkflowVersionRequest\x1a!.v1.CreateWorkflowVersionResponse\x12>\n\x0b\x43\x61ncelTasks\x12\x16.v1.CancelTasksRequest\x1a\x17.v1.CancelTasksResponse\x12>\n\x0bReplayTasks\x12\x16.v1.ReplayTasksRequest\x1a\x17.v1.ReplayTasksResponse\x12S\n\x12TriggerWorkflowRun\x12\x1d.v1.TriggerWorkflowRunRequest\x1a\x1e.v1.TriggerWorkflowRunResponse\x12\x44\n\rGetRunDetails\x12\x18.v1.GetRunDetailsRequest\x1a\x19.v1.GetRunDetailsResponse\x12P\n\x11\x42ranchDurableTask\x12\x1c.v1.BranchDurableTaskRequest\x1a\x1d.v1.BranchDurableTaskResponseBBZ@github.com/hatchet-dev/hatchet/internal/services/shared/proto/v1b\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x12v1/workflows.proto\x12\x02v1\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x19v1/shared/condition.proto\x1a\x17v1/shared/trigger.proto\"[\n\x12\x43\x61ncelTasksRequest\x12\x14\n\x0c\x65xternal_ids\x18\x01 \x03(\t\x12$\n\x06\x66ilter\x18\x02 \x01(\x0b\x32\x0f.v1.TasksFilterH\x00\x88\x01\x01\x42\t\n\x07_filter\"[\n\x12ReplayTasksRequest\x12\x14\n\x0c\x65xternal_ids\x18\x01 \x03(\t\x12$\n\x06\x66ilter\x18\x02 \x01(\x0b\x32\x0f.v1.TasksFilterH\x00\x88\x01\x01\x42\t\n\x07_filter\"\xb7\x01\n\x0bTasksFilter\x12\x10\n\x08statuses\x18\x01 \x03(\t\x12)\n\x05since\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12.\n\x05until\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampH\x00\x88\x01\x01\x12\x14\n\x0cworkflow_ids\x18\x04 \x03(\t\x12\x1b\n\x13\x61\x64\x64itional_metadata\x18\x05 \x03(\tB\x08\n\x06_until\".\n\x13\x43\x61ncelTasksResponse\x12\x17\n\x0f\x63\x61ncelled_tasks\x18\x01 \x03(\t\"-\n\x13ReplayTasksResponse\x12\x16\n\x0ereplayed_tasks\x18\x01 \x03(\t\"\xae\x02\n\x19TriggerWorkflowRunRequest\x12\x15\n\rworkflow_name\x18\x01 \x01(\t\x12\r\n\x05input\x18\x02 \x01(\x0c\x12\x1b\n\x13\x61\x64\x64itional_metadata\x18\x03 \x01(\x0c\x12\x15\n\x08priority\x18\x04 \x01(\x05H\x00\x88\x01\x01\x12U\n\x15\x64\x65sired_worker_labels\x18\x05 \x03(\x0b\x32\x36.v1.TriggerWorkflowRunRequest.DesiredWorkerLabelsEntry\x1aS\n\x18\x44\x65siredWorkerLabelsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12&\n\x05value\x18\x02 \x01(\x0b\x32\x17.v1.DesiredWorkerLabels:\x02\x38\x01\x42\x0b\n\t_priority\"1\n\x1aTriggerWorkflowRunResponse\x12\x13\n\x0b\x65xternal_id\x18\x01 \x01(\t\"X\n\x18\x42ranchDurableTaskRequest\x12\x18\n\x10task_external_id\x18\x01 \x01(\t\x12\x0f\n\x07node_id\x18\x02 \x01(\x03\x12\x11\n\tbranch_id\x18\x03 \x01(\x03\"Y\n\x19\x42ranchDurableTaskResponse\x12\x18\n\x10task_external_id\x18\x01 \x01(\t\x12\x0f\n\x07node_id\x18\x02 \x01(\x03\x12\x11\n\tbranch_id\x18\x03 \x01(\x03\"\xed\x04\n\x1c\x43reateWorkflowVersionRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x0f\n\x07version\x18\x03 \x01(\t\x12\x16\n\x0e\x65vent_triggers\x18\x04 \x03(\t\x12\x15\n\rcron_triggers\x18\x05 \x03(\t\x12!\n\x05tasks\x18\x06 \x03(\x0b\x32\x12.v1.CreateTaskOpts\x12$\n\x0b\x63oncurrency\x18\x07 \x01(\x0b\x32\x0f.v1.Concurrency\x12\x17\n\ncron_input\x18\x08 \x01(\tH\x00\x88\x01\x01\x12\x30\n\x0fon_failure_task\x18\t \x01(\x0b\x32\x12.v1.CreateTaskOptsH\x01\x88\x01\x01\x12\'\n\x06sticky\x18\n \x01(\x0e\x32\x12.v1.StickyStrategyH\x02\x88\x01\x01\x12\x1d\n\x10\x64\x65\x66\x61ult_priority\x18\x0b \x01(\x05H\x03\x88\x01\x01\x12(\n\x0f\x63oncurrency_arr\x18\x0c \x03(\x0b\x32\x0f.v1.Concurrency\x12*\n\x0f\x64\x65\x66\x61ult_filters\x18\r \x03(\x0b\x32\x11.v1.DefaultFilter\x12\x1e\n\x11input_json_schema\x18\x0e \x01(\x0cH\x04\x88\x01\x01\x12/\n\x0bidempotency\x18\x0f \x01(\x0b\x32\x15.v1.IdempotencyConfigH\x05\x88\x01\x01\x42\r\n\x0b_cron_inputB\x12\n\x10_on_failure_taskB\t\n\x07_stickyB\x13\n\x11_default_priorityB\x14\n\x12_input_json_schemaB\x0e\n\x0c_idempotency\"7\n\x11IdempotencyConfig\x12\x12\n\nexpression\x18\x01 \x01(\t\x12\x0e\n\x06ttl_ms\x18\x02 \x01(\x03\"T\n\rDefaultFilter\x12\x12\n\nexpression\x18\x01 \x01(\t\x12\r\n\x05scope\x18\x02 \x01(\t\x12\x14\n\x07payload\x18\x03 \x01(\x0cH\x00\x88\x01\x01\x42\n\n\x08_payload\"\x93\x01\n\x0b\x43oncurrency\x12\x12\n\nexpression\x18\x01 \x01(\t\x12\x15\n\x08max_runs\x18\x02 \x01(\x05H\x00\x88\x01\x01\x12\x39\n\x0elimit_strategy\x18\x03 \x01(\x0e\x32\x1c.v1.ConcurrencyLimitStrategyH\x01\x88\x01\x01\x42\x0b\n\t_max_runsB\x11\n\x0f_limit_strategy\"\xb7\x05\n\x0e\x43reateTaskOpts\x12\x13\n\x0breadable_id\x18\x01 \x01(\t\x12\x0e\n\x06\x61\x63tion\x18\x02 \x01(\t\x12\x0f\n\x07timeout\x18\x03 \x01(\t\x12\x0e\n\x06inputs\x18\x04 \x01(\t\x12\x0f\n\x07parents\x18\x05 \x03(\t\x12\x0f\n\x07retries\x18\x06 \x01(\x05\x12,\n\x0brate_limits\x18\x07 \x03(\x0b\x32\x17.v1.CreateTaskRateLimit\x12;\n\rworker_labels\x18\x08 \x03(\x0b\x32$.v1.CreateTaskOpts.WorkerLabelsEntry\x12\x1b\n\x0e\x62\x61\x63koff_factor\x18\t \x01(\x02H\x00\x88\x01\x01\x12 \n\x13\x62\x61\x63koff_max_seconds\x18\n \x01(\x05H\x01\x88\x01\x01\x12$\n\x0b\x63oncurrency\x18\x0b \x03(\x0b\x32\x0f.v1.Concurrency\x12+\n\nconditions\x18\x0c \x01(\x0b\x32\x12.v1.TaskConditionsH\x02\x88\x01\x01\x12\x1d\n\x10schedule_timeout\x18\r \x01(\tH\x03\x88\x01\x01\x12\x12\n\nis_durable\x18\x0e \x01(\x08\x12;\n\rslot_requests\x18\x0f \x03(\x0b\x32$.v1.CreateTaskOpts.SlotRequestsEntry\x1aL\n\x11WorkerLabelsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12&\n\x05value\x18\x02 \x01(\x0b\x32\x17.v1.DesiredWorkerLabels:\x02\x38\x01\x1a\x33\n\x11SlotRequestsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x05:\x02\x38\x01\x42\x11\n\x0f_backoff_factorB\x16\n\x14_backoff_max_secondsB\r\n\x0b_conditionsB\x13\n\x11_schedule_timeout\"\xfd\x01\n\x13\x43reateTaskRateLimit\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x12\n\x05units\x18\x02 \x01(\x05H\x00\x88\x01\x01\x12\x15\n\x08key_expr\x18\x03 \x01(\tH\x01\x88\x01\x01\x12\x17\n\nunits_expr\x18\x04 \x01(\tH\x02\x88\x01\x01\x12\x1e\n\x11limit_values_expr\x18\x05 \x01(\tH\x03\x88\x01\x01\x12,\n\x08\x64uration\x18\x06 \x01(\x0e\x32\x15.v1.RateLimitDurationH\x04\x88\x01\x01\x42\x08\n\x06_unitsB\x0b\n\t_key_exprB\r\n\x0b_units_exprB\x14\n\x12_limit_values_exprB\x0b\n\t_duration\"@\n\x1d\x43reateWorkflowVersionResponse\x12\n\n\x02id\x18\x01 \x01(\t\x12\x13\n\x0bworkflow_id\x18\x02 \x01(\t\"+\n\x14GetRunDetailsRequest\x12\x13\n\x0b\x65xternal_id\x18\x01 \x01(\t\"\xaa\x01\n\rTaskRunDetail\x12\x13\n\x0b\x65xternal_id\x18\x01 \x01(\t\x12\x1d\n\x06status\x18\x02 \x01(\x0e\x32\r.v1.RunStatus\x12\x12\n\x05\x65rror\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x13\n\x06output\x18\x04 \x01(\x0cH\x01\x88\x01\x01\x12\x13\n\x0breadable_id\x18\x05 \x01(\t\x12\x12\n\nis_evicted\x18\x06 \x01(\x08\x42\x08\n\x06_errorB\t\n\x07_output\"\x84\x02\n\x15GetRunDetailsResponse\x12\r\n\x05input\x18\x01 \x01(\x0c\x12\x1d\n\x06status\x18\x02 \x01(\x0e\x32\r.v1.RunStatus\x12:\n\ttask_runs\x18\x03 \x03(\x0b\x32\'.v1.GetRunDetailsResponse.TaskRunsEntry\x12\x0c\n\x04\x64one\x18\x04 \x01(\x08\x12\x1b\n\x13\x61\x64\x64itional_metadata\x18\x05 \x01(\x0c\x12\x12\n\nis_evicted\x18\x06 \x01(\x08\x1a\x42\n\rTaskRunsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12 \n\x05value\x18\x02 \x01(\x0b\x32\x11.v1.TaskRunDetail:\x02\x38\x01*$\n\x0eStickyStrategy\x12\x08\n\x04SOFT\x10\x00\x12\x08\n\x04HARD\x10\x01*]\n\x11RateLimitDuration\x12\n\n\x06SECOND\x10\x00\x12\n\n\x06MINUTE\x10\x01\x12\x08\n\x04HOUR\x10\x02\x12\x07\n\x03\x44\x41Y\x10\x03\x12\x08\n\x04WEEK\x10\x04\x12\t\n\x05MONTH\x10\x05\x12\x08\n\x04YEAR\x10\x06*[\n\tRunStatus\x12\n\n\x06QUEUED\x10\x00\x12\x0b\n\x07RUNNING\x10\x01\x12\r\n\tCOMPLETED\x10\x02\x12\n\n\x06\x46\x41ILED\x10\x03\x12\r\n\tCANCELLED\x10\x04\x12\x0b\n\x07\x45VICTED\x10\x05*\x7f\n\x18\x43oncurrencyLimitStrategy\x12\x16\n\x12\x43\x41NCEL_IN_PROGRESS\x10\x00\x12\x0f\n\x0b\x44ROP_NEWEST\x10\x01\x12\x10\n\x0cQUEUE_NEWEST\x10\x02\x12\x15\n\x11GROUP_ROUND_ROBIN\x10\x03\x12\x11\n\rCANCEL_NEWEST\x10\x04\x32\xcf\x03\n\x0c\x41\x64minService\x12R\n\x0bPutWorkflow\x12 .v1.CreateWorkflowVersionRequest\x1a!.v1.CreateWorkflowVersionResponse\x12>\n\x0b\x43\x61ncelTasks\x12\x16.v1.CancelTasksRequest\x1a\x17.v1.CancelTasksResponse\x12>\n\x0bReplayTasks\x12\x16.v1.ReplayTasksRequest\x1a\x17.v1.ReplayTasksResponse\x12S\n\x12TriggerWorkflowRun\x12\x1d.v1.TriggerWorkflowRunRequest\x1a\x1e.v1.TriggerWorkflowRunResponse\x12\x44\n\rGetRunDetails\x12\x18.v1.GetRunDetailsRequest\x1a\x19.v1.GetRunDetailsResponse\x12P\n\x11\x42ranchDurableTask\x12\x1c.v1.BranchDurableTaskRequest\x1a\x1d.v1.BranchDurableTaskResponseBBZ@github.com/hatchet-dev/hatchet/internal/services/shared/proto/v1b\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -43,14 +43,14 @@ _globals['_CREATETASKOPTS_SLOTREQUESTSENTRY']._serialized_options = b'8\001' _globals['_GETRUNDETAILSRESPONSE_TASKRUNSENTRY']._loaded_options = None _globals['_GETRUNDETAILSRESPONSE_TASKRUNSENTRY']._serialized_options = b'8\001' - _globals['_STICKYSTRATEGY']._serialized_start=3411 - _globals['_STICKYSTRATEGY']._serialized_end=3447 - _globals['_RATELIMITDURATION']._serialized_start=3449 - _globals['_RATELIMITDURATION']._serialized_end=3542 - _globals['_RUNSTATUS']._serialized_start=3544 - _globals['_RUNSTATUS']._serialized_end=3635 - _globals['_CONCURRENCYLIMITSTRATEGY']._serialized_start=3637 - _globals['_CONCURRENCYLIMITSTRATEGY']._serialized_end=3764 + _globals['_STICKYSTRATEGY']._serialized_start=3533 + _globals['_STICKYSTRATEGY']._serialized_end=3569 + _globals['_RATELIMITDURATION']._serialized_start=3571 + _globals['_RATELIMITDURATION']._serialized_end=3664 + _globals['_RUNSTATUS']._serialized_start=3666 + _globals['_RUNSTATUS']._serialized_end=3757 + _globals['_CONCURRENCYLIMITSTRATEGY']._serialized_start=3759 + _globals['_CONCURRENCYLIMITSTRATEGY']._serialized_end=3886 _globals['_CANCELTASKSREQUEST']._serialized_start=111 _globals['_CANCELTASKSREQUEST']._serialized_end=202 _globals['_REPLAYTASKSREQUEST']._serialized_start=204 @@ -72,29 +72,31 @@ _globals['_BRANCHDURABLETASKRESPONSE']._serialized_start=1024 _globals['_BRANCHDURABLETASKRESPONSE']._serialized_end=1113 _globals['_CREATEWORKFLOWVERSIONREQUEST']._serialized_start=1116 - _globals['_CREATEWORKFLOWVERSIONREQUEST']._serialized_end=1672 - _globals['_DEFAULTFILTER']._serialized_start=1674 - _globals['_DEFAULTFILTER']._serialized_end=1758 - _globals['_CONCURRENCY']._serialized_start=1761 - _globals['_CONCURRENCY']._serialized_end=1908 - _globals['_CREATETASKOPTS']._serialized_start=1911 - _globals['_CREATETASKOPTS']._serialized_end=2606 - _globals['_CREATETASKOPTS_WORKERLABELSENTRY']._serialized_start=2398 - _globals['_CREATETASKOPTS_WORKERLABELSENTRY']._serialized_end=2474 - _globals['_CREATETASKOPTS_SLOTREQUESTSENTRY']._serialized_start=2476 - _globals['_CREATETASKOPTS_SLOTREQUESTSENTRY']._serialized_end=2527 - _globals['_CREATETASKRATELIMIT']._serialized_start=2609 - _globals['_CREATETASKRATELIMIT']._serialized_end=2862 - _globals['_CREATEWORKFLOWVERSIONRESPONSE']._serialized_start=2864 - _globals['_CREATEWORKFLOWVERSIONRESPONSE']._serialized_end=2928 - _globals['_GETRUNDETAILSREQUEST']._serialized_start=2930 - _globals['_GETRUNDETAILSREQUEST']._serialized_end=2973 - _globals['_TASKRUNDETAIL']._serialized_start=2976 - _globals['_TASKRUNDETAIL']._serialized_end=3146 - _globals['_GETRUNDETAILSRESPONSE']._serialized_start=3149 - _globals['_GETRUNDETAILSRESPONSE']._serialized_end=3409 - _globals['_GETRUNDETAILSRESPONSE_TASKRUNSENTRY']._serialized_start=3343 - _globals['_GETRUNDETAILSRESPONSE_TASKRUNSENTRY']._serialized_end=3409 - _globals['_ADMINSERVICE']._serialized_start=3767 - _globals['_ADMINSERVICE']._serialized_end=4230 + _globals['_CREATEWORKFLOWVERSIONREQUEST']._serialized_end=1737 + _globals['_IDEMPOTENCYCONFIG']._serialized_start=1739 + _globals['_IDEMPOTENCYCONFIG']._serialized_end=1794 + _globals['_DEFAULTFILTER']._serialized_start=1796 + _globals['_DEFAULTFILTER']._serialized_end=1880 + _globals['_CONCURRENCY']._serialized_start=1883 + _globals['_CONCURRENCY']._serialized_end=2030 + _globals['_CREATETASKOPTS']._serialized_start=2033 + _globals['_CREATETASKOPTS']._serialized_end=2728 + _globals['_CREATETASKOPTS_WORKERLABELSENTRY']._serialized_start=2520 + _globals['_CREATETASKOPTS_WORKERLABELSENTRY']._serialized_end=2596 + _globals['_CREATETASKOPTS_SLOTREQUESTSENTRY']._serialized_start=2598 + _globals['_CREATETASKOPTS_SLOTREQUESTSENTRY']._serialized_end=2649 + _globals['_CREATETASKRATELIMIT']._serialized_start=2731 + _globals['_CREATETASKRATELIMIT']._serialized_end=2984 + _globals['_CREATEWORKFLOWVERSIONRESPONSE']._serialized_start=2986 + _globals['_CREATEWORKFLOWVERSIONRESPONSE']._serialized_end=3050 + _globals['_GETRUNDETAILSREQUEST']._serialized_start=3052 + _globals['_GETRUNDETAILSREQUEST']._serialized_end=3095 + _globals['_TASKRUNDETAIL']._serialized_start=3098 + _globals['_TASKRUNDETAIL']._serialized_end=3268 + _globals['_GETRUNDETAILSRESPONSE']._serialized_start=3271 + _globals['_GETRUNDETAILSRESPONSE']._serialized_end=3531 + _globals['_GETRUNDETAILSRESPONSE_TASKRUNSENTRY']._serialized_start=3465 + _globals['_GETRUNDETAILSRESPONSE_TASKRUNSENTRY']._serialized_end=3531 + _globals['_ADMINSERVICE']._serialized_start=3889 + _globals['_ADMINSERVICE']._serialized_end=4352 # @@protoc_insertion_point(module_scope) diff --git a/sdks/python/hatchet_sdk/contracts/v1/workflows_pb2.pyi b/sdks/python/hatchet_sdk/contracts/v1/workflows_pb2.pyi index 3015f82678..2a870a90e8 100644 --- a/sdks/python/hatchet_sdk/contracts/v1/workflows_pb2.pyi +++ b/sdks/python/hatchet_sdk/contracts/v1/workflows_pb2.pyi @@ -154,7 +154,7 @@ class BranchDurableTaskResponse(_message.Message): def __init__(self, task_external_id: _Optional[str] = ..., node_id: _Optional[int] = ..., branch_id: _Optional[int] = ...) -> None: ... class CreateWorkflowVersionRequest(_message.Message): - __slots__ = ("name", "description", "version", "event_triggers", "cron_triggers", "tasks", "concurrency", "cron_input", "on_failure_task", "sticky", "default_priority", "concurrency_arr", "default_filters", "input_json_schema") + __slots__ = ("name", "description", "version", "event_triggers", "cron_triggers", "tasks", "concurrency", "cron_input", "on_failure_task", "sticky", "default_priority", "concurrency_arr", "default_filters", "input_json_schema", "idempotency") NAME_FIELD_NUMBER: _ClassVar[int] DESCRIPTION_FIELD_NUMBER: _ClassVar[int] VERSION_FIELD_NUMBER: _ClassVar[int] @@ -169,6 +169,7 @@ class CreateWorkflowVersionRequest(_message.Message): CONCURRENCY_ARR_FIELD_NUMBER: _ClassVar[int] DEFAULT_FILTERS_FIELD_NUMBER: _ClassVar[int] INPUT_JSON_SCHEMA_FIELD_NUMBER: _ClassVar[int] + IDEMPOTENCY_FIELD_NUMBER: _ClassVar[int] name: str description: str version: str @@ -183,7 +184,16 @@ class CreateWorkflowVersionRequest(_message.Message): concurrency_arr: _containers.RepeatedCompositeFieldContainer[Concurrency] default_filters: _containers.RepeatedCompositeFieldContainer[DefaultFilter] input_json_schema: bytes - def __init__(self, name: _Optional[str] = ..., description: _Optional[str] = ..., version: _Optional[str] = ..., event_triggers: _Optional[_Iterable[str]] = ..., cron_triggers: _Optional[_Iterable[str]] = ..., tasks: _Optional[_Iterable[_Union[CreateTaskOpts, _Mapping]]] = ..., concurrency: _Optional[_Union[Concurrency, _Mapping]] = ..., cron_input: _Optional[str] = ..., on_failure_task: _Optional[_Union[CreateTaskOpts, _Mapping]] = ..., sticky: _Optional[_Union[StickyStrategy, str]] = ..., default_priority: _Optional[int] = ..., concurrency_arr: _Optional[_Iterable[_Union[Concurrency, _Mapping]]] = ..., default_filters: _Optional[_Iterable[_Union[DefaultFilter, _Mapping]]] = ..., input_json_schema: _Optional[bytes] = ...) -> None: ... + idempotency: IdempotencyConfig + def __init__(self, name: _Optional[str] = ..., description: _Optional[str] = ..., version: _Optional[str] = ..., event_triggers: _Optional[_Iterable[str]] = ..., cron_triggers: _Optional[_Iterable[str]] = ..., tasks: _Optional[_Iterable[_Union[CreateTaskOpts, _Mapping]]] = ..., concurrency: _Optional[_Union[Concurrency, _Mapping]] = ..., cron_input: _Optional[str] = ..., on_failure_task: _Optional[_Union[CreateTaskOpts, _Mapping]] = ..., sticky: _Optional[_Union[StickyStrategy, str]] = ..., default_priority: _Optional[int] = ..., concurrency_arr: _Optional[_Iterable[_Union[Concurrency, _Mapping]]] = ..., default_filters: _Optional[_Iterable[_Union[DefaultFilter, _Mapping]]] = ..., input_json_schema: _Optional[bytes] = ..., idempotency: _Optional[_Union[IdempotencyConfig, _Mapping]] = ...) -> None: ... + +class IdempotencyConfig(_message.Message): + __slots__ = ("expression", "ttl_ms") + EXPRESSION_FIELD_NUMBER: _ClassVar[int] + TTL_MS_FIELD_NUMBER: _ClassVar[int] + expression: str + ttl_ms: int + def __init__(self, expression: _Optional[str] = ..., ttl_ms: _Optional[int] = ...) -> None: ... class DefaultFilter(_message.Message): __slots__ = ("expression", "scope", "payload") diff --git a/sdks/python/hatchet_sdk/hatchet.py b/sdks/python/hatchet_sdk/hatchet.py index fd67366af0..d6b0bdc5d8 100644 --- a/sdks/python/hatchet_sdk/hatchet.py +++ b/sdks/python/hatchet_sdk/hatchet.py @@ -278,6 +278,7 @@ def workflow( task_defaults: TaskDefaults = TaskDefaults(), default_filters: list[DefaultFilter] | None = None, default_additional_metadata: JSONSerializableMapping | None = None, + idempotency_key_expression: str | None = None, ) -> Workflow[EmptyModel]: ... @overload @@ -298,6 +299,7 @@ def workflow( task_defaults: TaskDefaults = TaskDefaults(), default_filters: list[DefaultFilter] | None = None, default_additional_metadata: JSONSerializableMapping | None = None, + idempotency_key_expression: str | None = None, ) -> Workflow[TWorkflowInput]: ... def workflow( @@ -317,6 +319,7 @@ def workflow( task_defaults: TaskDefaults = TaskDefaults(), default_filters: list[DefaultFilter] | None = None, default_additional_metadata: JSONSerializableMapping | None = None, + idempotency_key_expression: str | None = None, ) -> Workflow[EmptyModel] | Workflow[TWorkflowInput]: """ Define a Hatchet workflow, which can then declare `task`s and be `run`, `schedule`d, and so on. @@ -364,6 +367,7 @@ def workflow( default_priority=default_priority, default_filters=default_filters or [], default_additional_metadata=default_additional_metadata or {}, + idempotency_key_expression=idempotency_key_expression, ), self, ) @@ -394,6 +398,7 @@ def task( backoff_max_seconds: int | None = None, default_filters: list[DefaultFilter] | None = None, default_additional_metadata: JSONSerializableMapping | None = None, + idempotency_key_expression: str | None = None, ) -> Callable[ [Callable[Concatenate[EmptyModel, Context, P], R | CoroutineLike[R]]], Standalone[EmptyModel, R], @@ -425,6 +430,7 @@ def task( backoff_max_seconds: int | None = None, default_filters: list[DefaultFilter] | None = None, default_additional_metadata: JSONSerializableMapping | None = None, + idempotency_key_expression: str | None = None, ) -> Callable[ [Callable[Concatenate[TWorkflowInput, Context, P], R | CoroutineLike[R]]], Standalone[TWorkflowInput, R], @@ -455,6 +461,7 @@ def task( backoff_max_seconds: int | None = None, default_filters: list[DefaultFilter] | None = None, default_additional_metadata: JSONSerializableMapping | None = None, + idempotency_key_expression: str | None = None, ) -> ( Callable[ [Callable[Concatenate[EmptyModel, Context, P], R | CoroutineLike[R]]], @@ -528,6 +535,7 @@ def inner( input_validator=TypeAdapter(normalize_validator(input_validator)), default_filters=default_filters or [], default_additional_metadata=default_additional_metadata or {}, + idempotency_key_expression=idempotency_key_expression, ), self, ) @@ -590,6 +598,7 @@ def durable_task( default_filters: list[DefaultFilter] | None = None, default_additional_metadata: JSONSerializableMapping | None = None, eviction_policy: EvictionPolicy | None = DEFAULT_DURABLE_TASK_EVICTION_POLICY, + idempotency_key_expression: str | None = None, ) -> Callable[ [Callable[Concatenate[EmptyModel, DurableContext, P], R | CoroutineLike[R]]], Standalone[EmptyModel, R], @@ -622,6 +631,7 @@ def durable_task( default_filters: list[DefaultFilter] | None = None, default_additional_metadata: JSONSerializableMapping | None = None, eviction_policy: EvictionPolicy | None = DEFAULT_DURABLE_TASK_EVICTION_POLICY, + idempotency_key_expression: str | None = None, ) -> Callable[ [ Callable[ @@ -657,6 +667,7 @@ def durable_task( default_filters: list[DefaultFilter] | None = None, default_additional_metadata: JSONSerializableMapping | None = None, eviction_policy: EvictionPolicy | None = DEFAULT_DURABLE_TASK_EVICTION_POLICY, + idempotency_key_expression: str | None = None, ) -> ( Callable[ [ @@ -737,6 +748,7 @@ def inner( default_priority=default_priority, default_filters=default_filters or [], default_additional_metadata=default_additional_metadata or {}, + idempotency_key_expression=idempotency_key_expression, ), self, ) diff --git a/sdks/python/hatchet_sdk/runnables/types.py b/sdks/python/hatchet_sdk/runnables/types.py index b6b393d93f..e4b5da8067 100644 --- a/sdks/python/hatchet_sdk/runnables/types.py +++ b/sdks/python/hatchet_sdk/runnables/types.py @@ -100,6 +100,7 @@ class WorkflowConfig(BaseModel): concurrency: int | ConcurrencyExpression | list[ConcurrencyExpression] | None = None input_validator: TypeAdapter[TaskPayloadForInternalUse] default_priority: int | Priority | None = None + idempotency_key_expression: str | None = None task_defaults: TaskDefaults = TaskDefaults() default_filters: list[DefaultFilter] = Field(default_factory=list) diff --git a/sdks/python/hatchet_sdk/runnables/workflow.py b/sdks/python/hatchet_sdk/runnables/workflow.py index 56966f5faf..24de70ea8c 100644 --- a/sdks/python/hatchet_sdk/runnables/workflow.py +++ b/sdks/python/hatchet_sdk/runnables/workflow.py @@ -246,6 +246,8 @@ def to_proto(self) -> CreateWorkflowVersionRequest: except Exception: json_schema = None + # print("idempotency key expression:", self._config.idempotency_key_expression) + return CreateWorkflowVersionRequest( name=name, description=self._config.description, @@ -264,6 +266,7 @@ def to_proto(self) -> CreateWorkflowVersionRequest: default_priority=self._config.default_priority, default_filters=[f.to_proto() for f in self._config.default_filters], input_json_schema=json_schema, + idempotency_key_expr=self._config.idempotency_key_expression, ) def _get_workflow_input(self, ctx: Context) -> TWorkflowInput: diff --git a/sdks/python/poetry.lock b/sdks/python/poetry.lock index f8e30657b6..30fffa47e2 100644 --- a/sdks/python/poetry.lock +++ b/sdks/python/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.0.0 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -7,6 +7,7 @@ description = "Happy Eyeballs for asyncio" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, @@ -19,6 +20,7 @@ description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "aiohttp-3.13.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:02222e7e233295f40e011c1b00e3b0bd451f22cf853a0304c3595633ee47da4b"}, {file = "aiohttp-3.13.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bace460460ed20614fa6bc8cb09966c0b8517b8c58ad8046828c6078d25333b5"}, @@ -153,7 +155,7 @@ propcache = ">=0.2.0" yarl = ">=1.17.0,<2.0" [package.extras] -speedups = ["Brotli (>=1.2) ; platform_python_implementation == \"CPython\"", "aiodns (>=3.3.0)", "backports.zstd ; platform_python_implementation == \"CPython\" and python_version < \"3.14\"", "brotlicffi (>=1.2) ; platform_python_implementation != \"CPython\""] +speedups = ["Brotli (>=1.2)", "aiodns (>=3.3.0)", "backports.zstd", "brotlicffi (>=1.2)"] [[package]] name = "aiosignal" @@ -162,6 +164,7 @@ description = "aiosignal: a list of registered asynchronous callbacks" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e"}, {file = "aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7"}, @@ -178,7 +181,7 @@ description = "Document parameters, class attributes, return types, and variable optional = true python-versions = ">=3.8" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320"}, {file = "annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4"}, @@ -191,6 +194,7 @@ description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -203,7 +207,7 @@ description = "High-level concurrency and networking framework on top of asyncio optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"openai\" or extra == \"claude\" or extra == \"docs\"" +markers = "(extra == \"docs\" or extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708"}, {file = "anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc"}, @@ -224,7 +228,7 @@ description = "Timeout context manager for asyncio programs" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version == \"3.10\"" +markers = "python_version < \"3.11\"" files = [ {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, @@ -237,6 +241,7 @@ description = "Classes Without Boilerplate" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309"}, {file = "attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32"}, @@ -249,7 +254,7 @@ description = "Backport of asyncio.Runner, a context manager that controls event optional = true python-versions = "<3.11,>=3.8" groups = ["main"] -markers = "extra == \"test\" and python_version == \"3.10\"" +markers = "extra == \"test\" and python_version < \"3.11\"" files = [ {file = "backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5"}, {file = "backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162"}, @@ -262,7 +267,7 @@ description = "Screen-scraping library" optional = true python-versions = ">=3.7.0" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb"}, {file = "beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86"}, @@ -286,7 +291,7 @@ description = "The uncompromising code formatter." optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"lint\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"lint\"" files = [ {file = "black-26.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:86a8b5035fce64f5dcd1b794cf8ec4d31fe458cf6ce3986a30deb434df82a1d2"}, {file = "black-26.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5602bdb96d52d2d0672f24f6ffe5218795736dd34807fd0fd55ccd6bf206168b"}, @@ -331,7 +336,7 @@ typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} colorama = ["colorama (>=0.4.3)"] d = ["aiohttp (>=3.10)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2) ; sys_platform != \"win32\"", "winloop (>=0.5.0) ; sys_platform == \"win32\""] +uvloop = ["uvloop (>=0.15.2)", "winloop (>=0.5.0)"] [[package]] name = "bs4" @@ -340,7 +345,7 @@ description = "Dummy package for Beautiful Soup (beautifulsoup4)" optional = true python-versions = "*" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "bs4-0.0.2-py2.py3-none-any.whl", hash = "sha256:abf8742c0805ef7f662dce4b51cca104cffe52b835238afc169142ab9b3fbccc"}, {file = "bs4-0.0.2.tar.gz", hash = "sha256:a48685c58f50fe127722417bae83fe6badf500d54b55f7e39ffe43b798653925"}, @@ -356,7 +361,7 @@ description = "Python package for providing Mozilla's CA Bundle." optional = true python-versions = ">=3.7" groups = ["main"] -markers = "extra == \"openai\" or extra == \"claude\" or extra == \"docs\" or extra == \"otel\" or extra == \"test\"" +markers = "(extra == \"otel\" or extra == \"test\" or extra == \"openai\" or extra == \"docs\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa"}, {file = "certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7"}, @@ -369,7 +374,7 @@ description = "Foreign Function Interface for Python calling C code." optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(extra == \"openai\" or extra == \"claude\") and platform_python_implementation != \"PyPy\"" +markers = "(extra == \"openai\" or extra == \"claude\") and platform_python_implementation != \"PyPy\" and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44"}, {file = "cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49"}, @@ -467,7 +472,7 @@ description = "The Real First Universal Charset Detector. Open, modern and activ optional = true python-versions = ">=3.7" groups = ["main"] -markers = "extra == \"openai\" or extra == \"otel\" or extra == \"test\"" +markers = "(extra == \"otel\" or extra == \"test\" or extra == \"openai\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "charset_normalizer-3.4.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2e1d8ca8611099001949d1cdfaefc510cf0f212484fe7c565f735b68c78c3c95"}, {file = "charset_normalizer-3.4.6-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e25369dc110d58ddf29b949377a93e0716d72a24f62bad72b2b39f155949c1fd"}, @@ -607,7 +612,7 @@ description = "Python SDK for Claude Code" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"claude\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"claude\"" files = [ {file = "claude_agent_sdk-0.1.55-py3-none-macosx_11_0_arm64.whl", hash = "sha256:70fa9f20f9b3a632d25d2666b533505a754d36429a4ad91a465eee87ac5798f7"}, {file = "claude_agent_sdk-0.1.55-py3-none-macosx_11_0_x86_64.whl", hash = "sha256:01c4ed18aa3dd38a5c1a37fc5a775549abc9d6658bad65a69e36dafbbecd3e29"}, @@ -632,7 +637,7 @@ description = "Composable command line interface toolkit" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "sys_platform != \"emscripten\" and (extra == \"openai\" or extra == \"claude\") or extra == \"docs\" or extra == \"lint\"" +markers = "(extra == \"lint\" or extra == \"docs\" or extra == \"openai\" or extra == \"claude\") and (extra == \"lint\" or extra == \"docs\" or sys_platform != \"emscripten\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "click-8.3.3-py3-none-any.whl", hash = "sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613"}, {file = "click-8.3.3.tar.gz", hash = "sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2"}, @@ -648,7 +653,7 @@ description = "Cross-platform colored terminal text." optional = true python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" groups = ["main"] -markers = "(extra == \"openai\" or extra == \"claude\" or extra == \"docs\" or extra == \"lint\") and sys_platform != \"emscripten\" and platform_system == \"Windows\" or (extra == \"docs\" or extra == \"lint\" or extra == \"openai\") and platform_system == \"Windows\" or extra == \"test\" and sys_platform == \"win32\"" +markers = "(extra == \"lint\" or extra == \"test\" or extra == \"docs\" or extra == \"openai\" or extra == \"claude\") and (platform_system == \"Windows\" or extra == \"test\") and (extra == \"lint\" or sys_platform != \"emscripten\" or extra == \"docs\" or extra == \"openai\") and (python_version <= \"3.13\" or python_version >= \"3.14\") and (extra == \"lint\" or extra == \"docs\" or extra == \"openai\" or extra == \"claude\" or sys_platform == \"win32\") and (platform_system == \"Windows\" or sys_platform == \"win32\")" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -661,7 +666,7 @@ description = "cryptography is a package which provides cryptographic recipes an optional = true python-versions = "!=3.9.0,!=3.9.1,>=3.9" groups = ["main"] -markers = "extra == \"openai\" or extra == \"claude\"" +markers = "(extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "cryptography-48.0.0-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:0c558d2cdffd8f4bbb30fc7134c74d2ca9a476f830bb053074498fbc86f41ed6"}, {file = "cryptography-48.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f5333311663ea94f75dd408665686aaf426563556bb5283554a3539177e03b8c"}, @@ -716,7 +721,7 @@ files = [ [package.dependencies] cffi = {version = ">=2.0.0", markers = "platform_python_implementation != \"PyPy\""} -typing-extensions = {version = ">=4.13.2", markers = "python_full_version < \"3.11.0\""} +typing-extensions = {version = ">=4.13.2", markers = "python_full_version < \"3.11\""} [package.extras] ssh = ["bcrypt (>=3.1.5)"] @@ -728,7 +733,7 @@ description = "Distro - an OS platform information API" optional = true python-versions = ">=3.6" groups = ["main"] -markers = "extra == \"docs\" or extra == \"openai\"" +markers = "(extra == \"docs\" or extra == \"openai\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, @@ -741,14 +746,14 @@ description = "Parse Python docstrings in reST, Google and Numpydoc format" optional = true python-versions = ">=3.8" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "docstring_parser_fork-0.0.14-py3-none-any.whl", hash = "sha256:4c544f234ef2cc2749a3df32b70c437d77888b1099143a1ad5454452c574b9af"}, {file = "docstring_parser_fork-0.0.14.tar.gz", hash = "sha256:a2743a63d8d36c09650594f7b4ab5b2758fee8629dcf794d1b221b23179baa5c"}, ] [package.extras] -dev = ["docstring-parser[docs]", "docstring-parser[test]", "pre-commit (>=2.16.0) ; python_version >= \"3.9\""] +dev = ["docstring-parser[docs]", "docstring-parser[test]", "pre-commit (>=2.16.0)"] docs = ["pydoctor (>=25.4.0)"] test = ["pytest"] @@ -759,7 +764,7 @@ description = "Backport of PEP 654 (exception groups)" optional = true python-versions = ">=3.7" groups = ["main"] -markers = "(extra == \"openai\" or extra == \"claude\" or extra == \"docs\" or extra == \"test\") and python_version == \"3.10\"" +markers = "(extra == \"test\" or extra == \"docs\" or extra == \"openai\" or extra == \"claude\") and python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598"}, {file = "exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219"}, @@ -778,7 +783,7 @@ description = "execnet: rapid multi-Python deployment" optional = true python-versions = ">=3.8" groups = ["main"] -markers = "extra == \"test\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"test\"" files = [ {file = "execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec"}, {file = "execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd"}, @@ -794,7 +799,7 @@ description = "FastAPI framework, high performance, easy to learn, fast to code, optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "fastapi-0.136.1-py3-none-any.whl", hash = "sha256:a6e9d7eeada96c93a4d69cb03836b44fa34e2854accb7244a1ece36cd4781c3f"}, {file = "fastapi-0.136.1.tar.gz", hash = "sha256:7af665ad7acfa0a3baf8983d393b6b471b9da10ede59c60045f49fbc89a0fa7f"}, @@ -819,6 +824,7 @@ description = "A list-like structure which implements collections.abc.MutableSeq optional = false python-versions = ">=3.9" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "frozenlist-1.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b37f6d31b3dcea7deb5e9696e529a6aa4a898adc33db82da12e4c60a7c4d2011"}, {file = "frozenlist-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef2b7b394f208233e471abc541cc6991f907ffd47dc72584acee3147899d6565"}, @@ -959,7 +965,7 @@ description = "Copy your docs directly to the gh-pages branch." optional = true python-versions = "*" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, @@ -978,7 +984,7 @@ description = "Common protobufs used in Google APIs" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"otel\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"otel\"" files = [ {file = "googleapis_common_protos-1.73.1-py3-none-any.whl", hash = "sha256:e51f09eb0a43a8602f5a915870972e6b4a394088415c79d79605a46d8e826ee8"}, {file = "googleapis_common_protos-1.73.1.tar.gz", hash = "sha256:13114f0e9d2391756a0194c3a8131974ed7bffb06086569ba193364af59163b6"}, @@ -997,7 +1003,7 @@ description = "Signatures for entire Python programs. Extract the structure, the optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"docs\" or extra == \"openai\"" +markers = "(extra == \"docs\" or extra == \"openai\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "griffelib-2.0.2-py3-none-any.whl", hash = "sha256:925c857658fb1ba40c0772c37acbc2ab650bd794d9c1b9726922e36ea4117ea1"}, {file = "griffelib-2.0.2.tar.gz", hash = "sha256:3cf20b3bc470e83763ffbf236e0076b1211bac1bc67de13daf494640f2de707e"}, @@ -1013,6 +1019,7 @@ description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "grpcio-1.80.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:886457a7768e408cdce226ad1ca67d2958917d306523a0e21e1a2fdaa75c9c9c"}, {file = "grpcio-1.80.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:7b641fc3f1dc647bfd80bd713addc68f6d145956f64677e56d9ebafc0bd72388"}, @@ -1090,6 +1097,7 @@ description = "Protobuf code generator for gRPC" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "grpcio_tools-1.80.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:727477b9afa4b53f5ec70cafb41c3965d893835e0d4ea9b542fe3d0d005602bf"}, {file = "grpcio_tools-1.80.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:85fe8d15f146c62cb76f38d963e256392d287442b9232717d30ae9e3bbda9bc3"}, @@ -1166,7 +1174,7 @@ description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = true python-versions = ">=3.8" groups = ["main"] -markers = "extra == \"openai\" or extra == \"claude\" or extra == \"docs\"" +markers = "(extra == \"docs\" or extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, @@ -1179,7 +1187,7 @@ description = "A minimal low-level HTTP client." optional = true python-versions = ">=3.8" groups = ["main"] -markers = "extra == \"openai\" or extra == \"claude\" or extra == \"docs\"" +markers = "(extra == \"docs\" or extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, @@ -1202,7 +1210,7 @@ description = "The next generation HTTP client." optional = true python-versions = ">=3.8" groups = ["main"] -markers = "extra == \"openai\" or extra == \"claude\" or extra == \"docs\"" +markers = "(extra == \"docs\" or extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, @@ -1215,7 +1223,7 @@ httpcore = "==1.*" idna = "*" [package.extras] -brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] +brotli = ["brotli", "brotlicffi"] cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] @@ -1228,7 +1236,7 @@ description = "Consume Server-Sent Event (SSE) messages with HTTPX." optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"openai\" or extra == \"claude\"" +markers = "(extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc"}, {file = "httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d"}, @@ -1241,6 +1249,7 @@ description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.8" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"}, {file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"}, @@ -1256,7 +1265,7 @@ description = "Read metadata from Python packages" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"otel\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"otel\"" files = [ {file = "importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151"}, {file = "importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb"}, @@ -1266,13 +1275,13 @@ files = [ zipp = ">=3.20" [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=3.4)"] perf = ["ipython"] test = ["flufl.flake8", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] -type = ["mypy (<1.19) ; platform_python_implementation == \"PyPy\"", "pytest-mypy (>=1.0.1)"] +type = ["mypy (<1.19)", "pytest-mypy (>=1.0.1)"] [[package]] name = "iniconfig" @@ -1281,7 +1290,7 @@ description = "brain-dead simple config-ini parsing" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"test\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"test\"" files = [ {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, @@ -1294,7 +1303,7 @@ description = "A very fast and expressive template engine." optional = true python-versions = ">=3.7" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, @@ -1313,7 +1322,7 @@ description = "Fast iterable JSON parser." optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"docs\" or extra == \"openai\"" +markers = "(extra == \"docs\" or extra == \"openai\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "jiter-0.13.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2ffc63785fd6c7977defe49b9824ae6ce2b2e2b77ce539bdaf006c26da06342e"}, {file = "jiter-0.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4a638816427006c1e3f0013eb66d391d7a3acda99a7b0cf091eff4497ccea33a"}, @@ -1426,7 +1435,7 @@ description = "An implementation of JSON Schema validation for Python" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"openai\" or extra == \"claude\"" +markers = "(extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce"}, {file = "jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326"}, @@ -1449,7 +1458,7 @@ description = "The JSON Schema meta-schemas and vocabularies, exposed as a Regis optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"openai\" or extra == \"claude\"" +markers = "(extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe"}, {file = "jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d"}, @@ -1465,7 +1474,7 @@ description = "Mypyc runtime library" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"lint\" and platform_python_implementation != \"PyPy\"" +markers = "extra == \"lint\" and platform_python_implementation != \"PyPy\" and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "librt-0.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:81fd938344fecb9373ba1b155968c8a329491d2ce38e7ddb76f30ffb938f12dc"}, {file = "librt-0.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5db05697c82b3a2ec53f6e72b2ed373132b0c2e05135f0696784e97d7f5d48e7"}, @@ -1566,7 +1575,7 @@ description = "Python implementation of John Gruber's Markdown." optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36"}, {file = "markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950"}, @@ -1583,7 +1592,7 @@ description = "Convert HTML to markdown." optional = true python-versions = "*" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "markdownify-1.2.2-py3-none-any.whl", hash = "sha256:3f02d3cc52714084d6e589f70397b6fc9f2f3a8531481bf35e8cc39f975e186a"}, {file = "markdownify-1.2.2.tar.gz", hash = "sha256:b274f1b5943180b031b699b199cbaeb1e2ac938b75851849a31fd0c3d6603d09"}, @@ -1600,7 +1609,7 @@ description = "Safely add untrusted strings to HTML/XML markup." optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"}, {file = "markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"}, @@ -1700,7 +1709,7 @@ description = "Model Context Protocol SDK" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"openai\" or extra == \"claude\"" +markers = "(extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "mcp-1.27.0-py3-none-any.whl", hash = "sha256:5ce1fa81614958e267b21fb2aa34e0aea8e2c6ede60d52aba45fd47246b4d741"}, {file = "mcp-1.27.0.tar.gz", hash = "sha256:d3dc35a7eec0d458c1da4976a48f982097ddaab87e278c5511d5a4a56e852b83"}, @@ -1734,7 +1743,7 @@ description = "A deep merge function for 🐍." optional = true python-versions = ">=3.6" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, @@ -1747,7 +1756,7 @@ description = "Project documentation with Markdown." optional = true python-versions = ">=3.8" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e"}, {file = "mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2"}, @@ -1770,7 +1779,7 @@ watchdog = ">=2.0" [package.extras] i18n = ["babel (>=2.9.0)"] -min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4) ; platform_system == \"Windows\"", "ghp-import (==1.0)", "importlib-metadata (==4.4) ; python_version < \"3.10\"", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"] +min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.4)", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"] [[package]] name = "mkdocs-autorefs" @@ -1779,7 +1788,7 @@ description = "Automatically link across pages in MkDocs." optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "mkdocs_autorefs-1.4.4-py3-none-any.whl", hash = "sha256:834ef5408d827071ad1bc69e0f39704fa34c7fc05bc8e1c72b227dfdc5c76089"}, {file = "mkdocs_autorefs-1.4.4.tar.gz", hash = "sha256:d54a284f27a7346b9c38f1f852177940c222da508e66edc816a0fa55fc6da197"}, @@ -1797,7 +1806,7 @@ description = "An extra command for MkDocs that infers required PyPI packages fr optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "mkdocs_get_deps-0.2.2-py3-none-any.whl", hash = "sha256:e7878cbeac04860b8b5e0ca31d3abad3df9411a75a32cde82f8e44b6c16ff650"}, {file = "mkdocs_get_deps-0.2.2.tar.gz", hash = "sha256:8ee8d5f316cdbbb2834bc1df6e69c08fe769a83e040060de26d3c19fad3599a1"}, @@ -1815,7 +1824,7 @@ description = "Automatic documentation from sources, for MkDocs." optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "mkdocstrings-1.0.4-py3-none-any.whl", hash = "sha256:63464b4b29053514f32a1dbbf604e52876d5e638111b0c295ab7ed3cac73ca9b"}, {file = "mkdocstrings-1.0.4.tar.gz", hash = "sha256:3969a6515b77db65fd097b53c1b7aa4ae840bd71a2ee62a6a3e89503446d7172"}, @@ -1842,7 +1851,7 @@ description = "A Python handler for mkdocstrings." optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "mkdocstrings_python-2.0.3-py3-none-any.whl", hash = "sha256:0b83513478bdfd803ff05aa43e9b1fca9dd22bcd9471f09ca6257f009bc5ee12"}, {file = "mkdocstrings_python-2.0.3.tar.gz", hash = "sha256:c518632751cc869439b31c9d3177678ad2bfa5c21b79b863956ad68fc92c13b8"}, @@ -1861,6 +1870,7 @@ description = "multidict implementation" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "multidict-6.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c93c3db7ea657dd4637d57e74ab73de31bccefe144d3d4ce370052035bc85fb5"}, {file = "multidict-6.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:974e72a2474600827abaeda71af0c53d9ebbc3c2eb7da37b37d7829ae31232d8"}, @@ -2020,7 +2030,7 @@ description = "Optional static typing for Python" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"lint\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"lint\"" files = [ {file = "mypy-1.20.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d99f515f95fd03a90875fdb2cca12ff074aa04490db4d190905851bdf8a549a8"}, {file = "mypy-1.20.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bd0212976dc57a5bfeede7c219e7cd66568a32c05c9129686dd487c059c1b88a"}, @@ -2090,7 +2100,7 @@ description = "Type system extensions for programs checked with the mypy type ch optional = true python-versions = ">=3.8" groups = ["main"] -markers = "extra == \"lint\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"lint\"" files = [ {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, @@ -2103,7 +2113,7 @@ description = "The official Python library for the openai API" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"docs\" or extra == \"openai\"" +markers = "(extra == \"docs\" or extra == \"openai\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "openai-2.36.0-py3-none-any.whl", hash = "sha256:143f6194b548dbc2c921af1f1b03b9f14c85fed8a75b5b516f5bcc11a2a50c63"}, {file = "openai-2.36.0.tar.gz", hash = "sha256:139dea0edd2f1b30c33d46ae1a6929e03906254140318e4608e98fe8c566f2e7"}, @@ -2132,7 +2142,7 @@ description = "OpenAI Agents SDK" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"openai\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"openai\"" files = [ {file = "openai_agents-0.13.5-py3-none-any.whl", hash = "sha256:672c76830d25b7eb3d85580539ba7caca975288df4395fe80c84cb4edfe4665f"}, {file = "openai_agents-0.13.5.tar.gz", hash = "sha256:ebe5bfb3d7d702d133ff9fb335718d61b741300de25b409ab4a6c8212ee945c0"}, @@ -2148,7 +2158,7 @@ types-requests = ">=2.0,<3" typing-extensions = ">=4.12.2,<5" [package.extras] -any-llm = ["any-llm-sdk (>=1.11.0,<2) ; python_version >= \"3.11\""] +any-llm = ["any-llm-sdk (>=1.11.0,<2)"] dapr = ["dapr (>=1.16.0)", "grpcio (>=1.60.0)"] encrypt = ["cryptography (>=45.0,<46)"] litellm = ["litellm (>=1.81.0,<=1.82.6)"] @@ -2156,7 +2166,7 @@ realtime = ["websockets (>=15.0,<16)"] redis = ["redis (>=7)"] sqlalchemy = ["asyncpg (>=0.29.0)", "sqlalchemy (>=2.0)"] viz = ["graphviz (>=0.17)"] -voice = ["numpy (>=2.2.0,<3) ; python_version >= \"3.10\"", "websockets (>=15.0,<16)"] +voice = ["numpy (>=2.2.0,<3)", "websockets (>=15.0,<16)"] [[package]] name = "opentelemetry-api" @@ -2165,7 +2175,7 @@ description = "OpenTelemetry Python API" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"otel\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"otel\"" files = [ {file = "opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9"}, {file = "opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f"}, @@ -2182,7 +2192,7 @@ description = "OpenTelemetry Python Distro" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"otel\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"otel\"" files = [ {file = "opentelemetry_distro-0.61b0-py3-none-any.whl", hash = "sha256:f21d1ac0627549795d75e332006dd068877f00e461b1b2e8fe4568d6eb7b9590"}, {file = "opentelemetry_distro-0.61b0.tar.gz", hash = "sha256:975b845f50181ad53753becf4fd4b123b54fa04df5a9d78812264436d6518981"}, @@ -2203,7 +2213,7 @@ description = "OpenTelemetry Collector Exporters" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"otel\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"otel\"" files = [ {file = "opentelemetry_exporter_otlp-1.40.0-py3-none-any.whl", hash = "sha256:48c87e539ec9afb30dc443775a1334cc5487de2f72a770a4c00b1610bf6c697d"}, {file = "opentelemetry_exporter_otlp-1.40.0.tar.gz", hash = "sha256:7caa0870b95e2fcb59d64e16e2b639ecffb07771b6cd0000b5d12e5e4fef765a"}, @@ -2220,7 +2230,7 @@ description = "OpenTelemetry Protobuf encoding" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"otel\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"otel\"" files = [ {file = "opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149"}, {file = "opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa"}, @@ -2236,7 +2246,7 @@ description = "OpenTelemetry Collector Protobuf over gRPC Exporter" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"otel\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"otel\"" files = [ {file = "opentelemetry_exporter_otlp_proto_grpc-1.40.0-py3-none-any.whl", hash = "sha256:2aa0ca53483fe0cf6405087a7491472b70335bc5c7944378a0a8e72e86995c52"}, {file = "opentelemetry_exporter_otlp_proto_grpc-1.40.0.tar.gz", hash = "sha256:bd4015183e40b635b3dab8da528b27161ba83bf4ef545776b196f0fb4ec47740"}, @@ -2265,7 +2275,7 @@ description = "OpenTelemetry Collector Protobuf over HTTP Exporter" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"otel\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"otel\"" files = [ {file = "opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl", hash = "sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069"}, {file = "opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz", hash = "sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c"}, @@ -2290,7 +2300,7 @@ description = "Instrumentation Tools & Auto Instrumentation for OpenTelemetry Py optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"otel\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"otel\"" files = [ {file = "opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63"}, {file = "opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7"}, @@ -2309,7 +2319,7 @@ description = "OpenTelemetry Python Proto" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"otel\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"otel\"" files = [ {file = "opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f"}, {file = "opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd"}, @@ -2325,7 +2335,7 @@ description = "OpenTelemetry Python SDK" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"otel\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"otel\"" files = [ {file = "opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1"}, {file = "opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2"}, @@ -2343,7 +2353,7 @@ description = "OpenTelemetry Semantic Conventions" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"otel\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"otel\"" files = [ {file = "opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2"}, {file = "opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a"}, @@ -2360,7 +2370,7 @@ description = "Core utilities for Python packages" optional = true python-versions = ">=3.8" groups = ["main"] -markers = "extra == \"docs\" or extra == \"lint\" or extra == \"otel\" or extra == \"test\"" +markers = "(extra == \"otel\" or extra == \"lint\" or extra == \"test\" or extra == \"docs\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"}, {file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"}, @@ -2373,7 +2383,7 @@ description = "Utility library for gitignore style pattern matching of file path optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"docs\" or extra == \"lint\"" +markers = "(extra == \"lint\" or extra == \"docs\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723"}, {file = "pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645"}, @@ -2392,7 +2402,7 @@ description = "A small Python package for determining appropriate platform-speci optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"docs\" or extra == \"lint\"" +markers = "(extra == \"lint\" or extra == \"docs\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "platformdirs-4.9.4-py3-none-any.whl", hash = "sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868"}, {file = "platformdirs-4.9.4.tar.gz", hash = "sha256:1ec356301b7dc906d83f371c8f487070e99d3ccf9e501686456394622a01a934"}, @@ -2405,7 +2415,7 @@ description = "plugin and hook calling mechanisms for python" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"test\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"test\"" files = [ {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, @@ -2422,6 +2432,7 @@ description = "Python client for the Prometheus monitoring system." optional = false python-versions = ">=3.9" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "prometheus_client-0.25.0-py3-none-any.whl", hash = "sha256:d5aec89e349a6ec230805d0df882f3807f74fd6c1a2fa86864e3c2279059fed1"}, {file = "prometheus_client-0.25.0.tar.gz", hash = "sha256:5e373b75c31afb3c86f1a52fa1ad470c9aace18082d39ec0d2f918d11cc9ba28"}, @@ -2439,6 +2450,7 @@ description = "Accelerated property cache" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "propcache-0.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c2d1fa3201efaf55d730400d945b5b3ab6e672e100ba0f9a409d950ab25d7db"}, {file = "propcache-0.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1eb2994229cc8ce7fe9b3db88f5465f5fd8651672840b2e426b88cdb1a30aac8"}, @@ -2571,6 +2583,7 @@ description = "" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "protobuf-6.33.6-cp310-abi3-win32.whl", hash = "sha256:7d29d9b65f8afef196f8334e80d6bc1d5d4adedb449971fefd3723824e6e77d3"}, {file = "protobuf-6.33.6-cp310-abi3-win_amd64.whl", hash = "sha256:0cd27b587afca21b7cfa59a74dcbd48a50f0a6400cfb59391340ad729d91d326"}, @@ -2591,7 +2604,7 @@ description = "Cross-platform lib for process and system monitoring." optional = true python-versions = ">=3.6" groups = ["main"] -markers = "extra == \"lint\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"lint\"" files = [ {file = "psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b"}, {file = "psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea"}, @@ -2617,8 +2630,8 @@ files = [ ] [package.extras] -dev = ["abi3audit", "black", "check-manifest", "colorama ; os_name == \"nt\"", "coverage", "packaging", "psleak", "pylint", "pyperf", "pypinfo", "pyreadline3 ; os_name == \"nt\"", "pytest", "pytest-cov", "pytest-instafail", "pytest-xdist", "pywin32 ; os_name == \"nt\" and implementation_name != \"pypy\"", "requests", "rstcheck", "ruff", "setuptools", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "validate-pyproject[all]", "virtualenv", "vulture", "wheel", "wheel ; os_name == \"nt\" and implementation_name != \"pypy\"", "wmi ; os_name == \"nt\" and implementation_name != \"pypy\""] -test = ["psleak", "pytest", "pytest-instafail", "pytest-xdist", "pywin32 ; os_name == \"nt\" and implementation_name != \"pypy\"", "setuptools", "wheel ; os_name == \"nt\" and implementation_name != \"pypy\"", "wmi ; os_name == \"nt\" and implementation_name != \"pypy\""] +dev = ["abi3audit", "black", "check-manifest", "colorama", "coverage", "packaging", "psleak", "pylint", "pyperf", "pypinfo", "pyreadline3", "pytest", "pytest-cov", "pytest-instafail", "pytest-xdist", "pywin32", "requests", "rstcheck", "ruff", "setuptools", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "validate-pyproject[all]", "virtualenv", "vulture", "wheel", "wheel", "wmi"] +test = ["psleak", "pytest", "pytest-instafail", "pytest-xdist", "pywin32", "setuptools", "wheel", "wmi"] [[package]] name = "psycopg" @@ -2627,7 +2640,7 @@ description = "PostgreSQL database adapter for Python" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"test\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"test\"" files = [ {file = "psycopg-3.3.4-py3-none-any.whl", hash = "sha256:b6bbc25ccf05c8fad3b061d9db2ef0909a555171b84b07f29458a447253d679a"}, {file = "psycopg-3.3.4.tar.gz", hash = "sha256:e21207764952cff81b6b8bdacad9a3939f2793367fdac2987b3aac36a651b5bc"}, @@ -2639,12 +2652,12 @@ typing-extensions = {version = ">=4.6", markers = "python_version < \"3.13\""} tzdata = {version = "*", markers = "sys_platform == \"win32\""} [package.extras] -binary = ["psycopg-binary (==3.3.4) ; implementation_name != \"pypy\""] -c = ["psycopg-c (==3.3.4) ; implementation_name != \"pypy\""] +binary = ["psycopg-binary (==3.3.4)"] +c = ["psycopg-c (==3.3.4)"] dev = ["ast-comments (>=1.1.2)", "black (>=26.1.0)", "codespell (>=2.2)", "cython-lint (>=0.16)", "dnspython (>=2.1)", "flake8 (>=4.0)", "isort-psycopg", "isort[colors] (>=6.0)", "mypy (>=1.19.0)", "pre-commit (>=4.0.1)", "types-setuptools (>=57.4)", "types-shapely (>=2.0)", "wheel (>=0.37)"] docs = ["Sphinx (>=9.1)", "furo (==2025.12.19)", "sphinx-autobuild (>=2025.8.25)", "sphinx-autodoc-typehints (>=3.10.2)"] pool = ["psycopg-pool"] -test = ["anyio (>=4.0)", "mypy (>=1.19.0) ; implementation_name != \"pypy\"", "pproxy (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0)", "pytest-randomly (>=3.5)"] +test = ["anyio (>=4.0)", "mypy (>=1.19.0)", "pproxy (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0)", "pytest-randomly (>=3.5)"] [[package]] name = "psycopg-pool" @@ -2653,7 +2666,7 @@ description = "Connection Pool for Psycopg" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"test\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"test\"" files = [ {file = "psycopg_pool-3.3.0-py3-none-any.whl", hash = "sha256:2e44329155c410b5e8666372db44276a8b1ebd8c90f1c3026ebba40d4bc81063"}, {file = "psycopg_pool-3.3.0.tar.gz", hash = "sha256:fa115eb2860bd88fce1717d75611f41490dec6135efb619611142b24da3f6db5"}, @@ -2672,7 +2685,7 @@ description = "C parser in Python" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(extra == \"openai\" or extra == \"claude\") and platform_python_implementation != \"PyPy\" and implementation_name != \"PyPy\"" +markers = "(extra == \"openai\" or extra == \"claude\") and platform_python_implementation != \"PyPy\" and implementation_name != \"PyPy\" and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992"}, {file = "pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29"}, @@ -2685,6 +2698,7 @@ description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba"}, {file = "pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6"}, @@ -2698,7 +2712,7 @@ typing-inspection = ">=0.4.2" [package.extras] email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] +timezone = ["tzdata"] [[package]] name = "pydantic-core" @@ -2707,6 +2721,7 @@ description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "pydantic_core-2.46.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a396dcc17e5a0b164dbe026896245a4fa9ff402edca1dff0be3d53a517f74de4"}, {file = "pydantic_core-2.46.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da4b951fe36dc7c3a1ccb4e3cd1747c3542b8c9ceede8fc86cae054e764485f5"}, @@ -2840,6 +2855,7 @@ description = "Settings management using Pydantic" optional = false python-versions = ">=3.10" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "pydantic_settings-2.14.1-py3-none-any.whl", hash = "sha256:6e3c7edfd8277687cdc598f56e5cff0e9bfff0910a3749deaa8d4401c3a2b9de"}, {file = "pydantic_settings-2.14.1.tar.gz", hash = "sha256:e874d3bec7e787b0c9958277956ed9b4dd5de6a80e162188fdaff7c5e26fd5fa"}, @@ -2864,7 +2880,7 @@ description = "A Python docstring linter that checks arguments, returns, yields, optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "pydoclint-0.8.3-py3-none-any.whl", hash = "sha256:5fc9b82d0d515afce0908cb70e8ff695a68b19042785c248c4f227ad66b4a164"}, {file = "pydoclint-0.8.3.tar.gz", hash = "sha256:0c69c0ed92c6f6b5aec2a14371bbd9e0a77980da8e2fa0f708092aa06b3b20b3"}, @@ -2885,7 +2901,7 @@ description = "Pygments is a syntax highlighting package written in Python." optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"test\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"test\"" files = [ {file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"}, {file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"}, @@ -2901,7 +2917,7 @@ description = "JSON Web Token implementation in Python" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"openai\" or extra == \"claude\"" +markers = "(extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "pyjwt-2.12.1-py3-none-any.whl", hash = "sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c"}, {file = "pyjwt-2.12.1.tar.gz", hash = "sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b"}, @@ -2924,7 +2940,7 @@ description = "Extension pack for Python Markdown." optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "pymdown_extensions-10.21.2-py3-none-any.whl", hash = "sha256:5c0fd2a2bea14eb39af8ff284f1066d898ab2187d81b889b75d46d4348c01638"}, {file = "pymdown_extensions-10.21.2.tar.gz", hash = "sha256:c3f55a5b8a1d0edf6699e35dcbea71d978d34ff3fa79f3d807b8a5b3fa90fbdc"}, @@ -2944,7 +2960,7 @@ description = "pytest: simple powerful testing with Python" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"test\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"test\"" files = [ {file = "pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9"}, {file = "pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c"}, @@ -2969,7 +2985,7 @@ description = "Pytest support for asyncio" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"test\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"test\"" files = [ {file = "pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5"}, {file = "pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5"}, @@ -2991,7 +3007,7 @@ description = "pytest plugin that allows you to add environment variables." optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"test\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"test\"" files = [ {file = "pytest_env-1.6.0-py3-none-any.whl", hash = "sha256:1e7f8a62215e5885835daaed694de8657c908505b964ec8097a7ce77b403d9a3"}, {file = "pytest_env-1.6.0.tar.gz", hash = "sha256:ac02d6fba16af54d61e311dd70a3c61024a4e966881ea844affc3c8f0bf207d3"}, @@ -3012,7 +3028,7 @@ description = "Adds the ability to retry flaky tests in CI environments" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"test\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"test\"" files = [ {file = "pytest_retry-1.7.0-py3-none-any.whl", hash = "sha256:a2dac85b79a4e2375943f1429479c65beb6c69553e7dae6b8332be47a60954f4"}, {file = "pytest_retry-1.7.0.tar.gz", hash = "sha256:f8d52339f01e949df47c11ba9ee8d5b362f5824dff580d3870ec9ae0057df80f"}, @@ -3031,7 +3047,7 @@ description = "pytest xdist plugin for distributed testing, most importantly acr optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"test\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"test\"" files = [ {file = "pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88"}, {file = "pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1"}, @@ -3053,6 +3069,7 @@ description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -3068,6 +3085,7 @@ description = "Read key-value pairs from a .env file and set them as environment optional = false python-versions = ">=3.10" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a"}, {file = "python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3"}, @@ -3083,7 +3101,7 @@ description = "A streaming multipart parser for Python" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"openai\" or extra == \"claude\"" +markers = "(extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "python_multipart-0.0.27-py3-none-any.whl", hash = "sha256:6fccfad17a27334bd0193681b369f476eda3409f17381a2d65aa7df3f7275645"}, {file = "python_multipart-0.0.27.tar.gz", hash = "sha256:9870a6a8c5a20a5bf4f07c017bd1489006ff8836cff097b6933355ee2b49b602"}, @@ -3096,7 +3114,7 @@ description = "A Fast, spec compliant Python 3.14+ tokenizer that runs on older optional = true python-versions = ">=3.8" groups = ["main"] -markers = "extra == \"lint\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"lint\"" files = [ {file = "pytokens-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a44ed93ea23415c54f3face3b65ef2b844d96aeb3455b8a69b3df6beab6acc5"}, {file = "pytokens-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:add8bf86b71a5d9fb5b89f023a80b791e04fba57960aa790cc6125f7f1d39dfe"}, @@ -3152,7 +3170,7 @@ description = "Python for Window Extensions" optional = true python-versions = "*" groups = ["main"] -markers = "(extra == \"openai\" or extra == \"claude\") and sys_platform == \"win32\"" +markers = "(extra == \"openai\" or extra == \"claude\") and sys_platform == \"win32\" and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3"}, {file = "pywin32-311-cp310-cp310-win_amd64.whl", hash = "sha256:797c2772017851984b97180b0bebe4b620bb86328e8a884bb626156295a63b3b"}, @@ -3183,7 +3201,7 @@ description = "YAML parser and emitter for Python" optional = true python-versions = ">=3.8" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"}, {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"}, @@ -3267,7 +3285,7 @@ description = "A custom YAML tag for referencing environment variables in YAML f optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04"}, {file = "pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff"}, @@ -3283,7 +3301,7 @@ description = "JSON Referencing + Python" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"openai\" or extra == \"claude\"" +markers = "(extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231"}, {file = "referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8"}, @@ -3301,7 +3319,7 @@ description = "Python HTTP for Humans." optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"openai\" or extra == \"otel\" or extra == \"test\"" +markers = "(extra == \"otel\" or extra == \"test\" or extra == \"openai\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a"}, {file = "requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517"}, @@ -3324,7 +3342,7 @@ description = "Python bindings to Rust's persistent data structures (rpds)" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"openai\" or extra == \"claude\"" +markers = "(extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "rpds_py-0.30.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:679ae98e00c0e8d68a7fda324e16b90fd5260945b45d3b824c892cec9eea3288"}, {file = "rpds_py-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cc2206b76b4f576934f0ed374b10d7ca5f457858b157ca52064bdfc26b9fc00"}, @@ -3450,7 +3468,7 @@ description = "An extremely fast Python linter and code formatter, written in Ru optional = true python-versions = ">=3.7" groups = ["main"] -markers = "extra == \"lint\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"lint\"" files = [ {file = "ruff-0.15.10-py3-none-linux_armv6l.whl", hash = "sha256:0744e31482f8f7d0d10a11fcbf897af272fefdfcb10f5af907b18c2813ff4d5f"}, {file = "ruff-0.15.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b1e7c16ea0ff5a53b7c2df52d947e685973049be1cdfe2b59a9c43601897b22e"}, @@ -3479,19 +3497,20 @@ description = "Most extensible Python build backend with support for C/C++ exten optional = false python-versions = ">=3.9" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb"}, {file = "setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.13.0) ; sys_platform != \"cygwin\""] -core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.13.0)"] +core = ["importlib_metadata (>=6)", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.18.*)", "pytest-mypy"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.18.*)", "pytest-mypy"] [[package]] name = "six" @@ -3500,6 +3519,7 @@ description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -3512,7 +3532,7 @@ description = "Sniff out which async library your code is running under" optional = true python-versions = ">=3.7" groups = ["main"] -markers = "extra == \"docs\" or extra == \"openai\"" +markers = "(extra == \"docs\" or extra == \"openai\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -3525,7 +3545,7 @@ description = "A modern CSS selector implementation for Beautiful Soup." optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95"}, {file = "soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349"}, @@ -3538,7 +3558,7 @@ description = "SSE plugin for Starlette" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"openai\" or extra == \"claude\"" +markers = "(extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "sse_starlette-3.3.4-py3-none-any.whl", hash = "sha256:84bb06e58939a8b38d8341f1bc9792f06c2b53f48c608dd207582b664fc8f3c1"}, {file = "sse_starlette-3.3.4.tar.gz", hash = "sha256:aaf92fc067af8a5427192895ac028e947b484ac01edbc3caf00e7e7137c7bef1"}, @@ -3561,7 +3581,7 @@ description = "The little ASGI library that shines." optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"openai\" or extra == \"claude\" or extra == \"docs\"" +markers = "(extra == \"openai\" or extra == \"claude\" or extra == \"docs\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "starlette-1.0.0-py3-none-any.whl", hash = "sha256:d3ec55e0bb321692d275455ddfd3df75fff145d009685eb40dc91fc66b03d38b"}, {file = "starlette-1.0.0.tar.gz", hash = "sha256:6a4beaf1f81bb472fd19ea9b918b50dc3a77a6f2e190a12954b25e6ed5eea149"}, @@ -3581,6 +3601,7 @@ description = "Retry code until it succeeds" optional = false python-versions = ">=3.10" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55"}, {file = "tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a"}, @@ -3597,7 +3618,7 @@ description = "A lil' TOML parser" optional = true python-versions = ">=3.8" groups = ["main"] -markers = "(extra == \"docs\" or extra == \"lint\" or extra == \"test\") and python_version == \"3.10\"" +markers = "(extra == \"lint\" or extra == \"test\" or extra == \"docs\") and python_version < \"3.11\"" files = [ {file = "tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30"}, {file = "tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a"}, @@ -3655,7 +3676,7 @@ description = "Fast, Extensible Progress Meter" optional = true python-versions = ">=3.7" groups = ["main"] -markers = "extra == \"docs\" or extra == \"openai\"" +markers = "(extra == \"docs\" or extra == \"openai\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf"}, {file = "tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb"}, @@ -3678,7 +3699,7 @@ description = "Typing stubs for beautifulsoup4" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "types_beautifulsoup4-4.12.0.20250516-py3-none-any.whl", hash = "sha256:5923399d4a1ba9cc8f0096fe334cc732e130269541d66261bb42ab039c0376ee"}, {file = "types_beautifulsoup4-4.12.0.20250516.tar.gz", hash = "sha256:aa19dd73b33b70d6296adf92da8ab8a0c945c507e6fb7d5db553415cc77b417e"}, @@ -3694,7 +3715,7 @@ description = "Typing stubs for grpcio" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"lint\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"lint\"" files = [ {file = "types_grpcio-1.0.0.20251009-py3-none-any.whl", hash = "sha256:112ac4312a5b0a273a4c414f7f2c7668f342990d9c6ab0f647391c36331f95ed"}, {file = "types_grpcio-1.0.0.20251009.tar.gz", hash = "sha256:a8f615ea7a47b31f10da028ab5258d4f1611fbd70719ca450fc0ab3fb9c62b63"}, @@ -3707,7 +3728,7 @@ description = "Typing stubs for html5lib" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "types_html5lib-1.1.11.20251117-py3-none-any.whl", hash = "sha256:2a3fc935de788a4d2659f4535002a421e05bea5e172b649d33232e99d4272d08"}, {file = "types_html5lib-1.1.11.20251117.tar.gz", hash = "sha256:1a6a3ac5394aa12bf547fae5d5eff91dceec46b6d07c4367d9b39a37f42f201a"}, @@ -3723,7 +3744,7 @@ description = "Typing stubs for protobuf" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"lint\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"lint\"" files = [ {file = "types_protobuf-6.32.1.20260221-py3-none-any.whl", hash = "sha256:da7cdd947975964a93c30bfbcc2c6841ee646b318d3816b033adc2c4eb6448e4"}, {file = "types_protobuf-6.32.1.20260221.tar.gz", hash = "sha256:6d5fb060a616bfb076cbb61b4b3c3969f5fc8bec5810f9a2f7e648ee5cbcbf6e"}, @@ -3736,7 +3757,7 @@ description = "Typing stubs for psutil" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"lint\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"lint\"" files = [ {file = "types_psutil-7.2.2.20260508-py3-none-any.whl", hash = "sha256:b142452e0953f2d07dbdbb98d81f3a629f5906cc2d94bb7e34da0fba55fbab4a"}, {file = "types_psutil-7.2.2.20260508.tar.gz", hash = "sha256:8cfd8339f5e898570f80486423e65d87558d89d0181bf723d20ac5e778fe218e"}, @@ -3749,7 +3770,7 @@ description = "Typing stubs for requests" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"lint\" or extra == \"openai\"" +markers = "(extra == \"lint\" or extra == \"openai\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "types_requests-2.33.0.20260503-py3-none-any.whl", hash = "sha256:02aaa7e3577a13471715bb1bddb693cc985ea514f754b503bf033e6a09a3e528"}, {file = "types_requests-2.33.0.20260503.tar.gz", hash = "sha256:9721b2d9dbee7131f2fb39f20f0ebb1999c18cef4b512c9a7932f3722de7c5f4"}, @@ -3765,7 +3786,7 @@ description = "Typing stubs for webencodings" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "types_webencodings-0.5.0.20251108-py3-none-any.whl", hash = "sha256:e21f81ff750795faffddaffd70a3d8bfff77d006f22c27e393eb7812586249d8"}, {file = "types_webencodings-0.5.0.20251108.tar.gz", hash = "sha256:2378e2ceccced3d41bb5e21387586e7b5305e11519fc6b0659c629f23b2e5de4"}, @@ -3778,6 +3799,7 @@ description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, @@ -3790,6 +3812,7 @@ description = "Runtime typing introspection tools" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, @@ -3805,7 +3828,7 @@ description = "Provider of IANA time zone data" optional = true python-versions = ">=2" groups = ["main"] -markers = "extra == \"test\" and sys_platform == \"win32\"" +markers = "extra == \"test\" and sys_platform == \"win32\" and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1"}, {file = "tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7"}, @@ -3818,16 +3841,17 @@ description = "HTTP library with thread-safe connection pooling, file post, and optional = false python-versions = ">=3.10" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897"}, {file = "urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c"}, ] [package.extras] -brotli = ["brotli (>=1.2.0) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=1.2.0.0) ; platform_python_implementation != \"CPython\""] +brotli = ["brotli (>=1.2.0)", "brotlicffi (>=1.2.0.0)"] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""] +zstd = ["backports-zstd (>=1.0.0)"] [[package]] name = "uvicorn" @@ -3836,7 +3860,7 @@ description = "The lightning-fast ASGI server." optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(extra == \"openai\" or extra == \"claude\") and sys_platform != \"emscripten\" or extra == \"docs\"" +markers = "(extra == \"openai\" or extra == \"claude\" or extra == \"docs\") and (python_version <= \"3.13\" or python_version >= \"3.14\") and (sys_platform != \"emscripten\" or extra == \"docs\")" files = [ {file = "uvicorn-0.46.0-py3-none-any.whl", hash = "sha256:bbebbcbed972d162afca128605223022bedd345b7bc7855ce66deb31487a9048"}, {file = "uvicorn-0.46.0.tar.gz", hash = "sha256:fb9da0926999cc6cb22dc7cd71a94a632f078e6ae47ff683c5c420750fb7413d"}, @@ -3848,7 +3872,7 @@ h11 = ">=0.8" typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} [package.extras] -standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.15.1) ; sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"", "watchfiles (>=0.20)", "websockets (>=10.4)"] +standard = ["colorama (>=0.4)", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.15.1)", "watchfiles (>=0.20)", "websockets (>=10.4)"] [[package]] name = "watchdog" @@ -3857,7 +3881,7 @@ description = "Filesystem events monitoring" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26"}, {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112"}, @@ -3901,7 +3925,7 @@ description = "Module for decorators, wrappers and monkey patching." optional = true python-versions = ">=3.8" groups = ["main"] -markers = "extra == \"otel\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"otel\"" files = [ {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88bbae4d40d5a46142e70d58bf664a89b6b4befaea7b2ecc14e03cedb8e06c04"}, {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b13af258d6a9ad602d57d889f83b9d5543acd471eee12eb51f5b01f8eb1bc2"}, @@ -3993,6 +4017,7 @@ description = "Yet another URL library" optional = false python-versions = ">=3.10" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "yarl-1.23.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cff6d44cb13d39db2663a22b22305d10855efa0fa8015ddeacc40bc59b9d8107"}, {file = "yarl-1.23.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4c53f8347cd4200f0d70a48ad059cabaf24f5adc6ba08622a23423bc7efa10d"}, @@ -4136,14 +4161,14 @@ description = "Backport of pathlib-compatible object wrapper for zip files" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"otel\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"otel\"" files = [ {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] From d490686ac774a3bb4cfb18b72729cfe070f333cd Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Fri, 29 May 2026 14:53:15 -0400 Subject: [PATCH 07/69] feat: wire up idempotency config on the sdk --- sdks/python/examples/simple/worker.py | 4 +-- sdks/python/hatchet_sdk/__init__.py | 2 ++ sdks/python/hatchet_sdk/hatchet.py | 31 ++++++++++++------- sdks/python/hatchet_sdk/runnables/types.py | 3 +- sdks/python/hatchet_sdk/runnables/workflow.py | 6 +++- sdks/python/hatchet_sdk/types/idempotency.py | 19 ++++++++++++ 6 files changed, 49 insertions(+), 16 deletions(-) create mode 100644 sdks/python/hatchet_sdk/types/idempotency.py diff --git a/sdks/python/examples/simple/worker.py b/sdks/python/examples/simple/worker.py index ee67ee8afd..ed90b2c1b8 100644 --- a/sdks/python/examples/simple/worker.py +++ b/sdks/python/examples/simple/worker.py @@ -1,10 +1,10 @@ # > Simple -from hatchet_sdk import Context, DurableContext, EmptyModel, Hatchet +from hatchet_sdk import Context, DurableContext, EmptyModel, Hatchet, IdempotencyConfig hatchet = Hatchet() -@hatchet.task(idempotency_key_expression="input.some_id") +@hatchet.task(idempotency=IdempotencyConfig(key_expression="input.some_id", ttl=None)) def simple(input: EmptyModel, ctx: Context) -> dict[str, str]: return {"result": "Hello, world!"} diff --git a/sdks/python/hatchet_sdk/__init__.py b/sdks/python/hatchet_sdk/__init__.py index d19c4950b1..97693178c1 100644 --- a/sdks/python/hatchet_sdk/__init__.py +++ b/sdks/python/hatchet_sdk/__init__.py @@ -154,6 +154,7 @@ ConcurrencyExpression, ConcurrencyLimitStrategy, ) +from hatchet_sdk.types.idempotency import IdempotencyConfig from hatchet_sdk.types.labels import ( DesiredWorkerLabel, WorkerLabel, @@ -223,6 +224,7 @@ "GithubBranch", "GithubRepo", "Hatchet", + "IdempotencyConfig", "Job", "JobRun", "JobRunStatus", diff --git a/sdks/python/hatchet_sdk/hatchet.py b/sdks/python/hatchet_sdk/hatchet.py index d6b0bdc5d8..94b135a483 100644 --- a/sdks/python/hatchet_sdk/hatchet.py +++ b/sdks/python/hatchet_sdk/hatchet.py @@ -41,6 +41,7 @@ ) from hatchet_sdk.runnables.workflow import BaseWorkflow, Standalone, Workflow from hatchet_sdk.types.concurrency import ConcurrencyExpression +from hatchet_sdk.types.idempotency import IdempotencyConfig from hatchet_sdk.types.labels import DesiredWorkerLabel from hatchet_sdk.types.priority import Priority, _warn_if_int_priority from hatchet_sdk.types.rate_limit import RateLimit @@ -278,7 +279,7 @@ def workflow( task_defaults: TaskDefaults = TaskDefaults(), default_filters: list[DefaultFilter] | None = None, default_additional_metadata: JSONSerializableMapping | None = None, - idempotency_key_expression: str | None = None, + idempotency: IdempotencyConfig | None = None, ) -> Workflow[EmptyModel]: ... @overload @@ -299,7 +300,7 @@ def workflow( task_defaults: TaskDefaults = TaskDefaults(), default_filters: list[DefaultFilter] | None = None, default_additional_metadata: JSONSerializableMapping | None = None, - idempotency_key_expression: str | None = None, + idempotency: IdempotencyConfig | None = None, ) -> Workflow[TWorkflowInput]: ... def workflow( @@ -319,7 +320,7 @@ def workflow( task_defaults: TaskDefaults = TaskDefaults(), default_filters: list[DefaultFilter] | None = None, default_additional_metadata: JSONSerializableMapping | None = None, - idempotency_key_expression: str | None = None, + idempotency: IdempotencyConfig | None = None, ) -> Workflow[EmptyModel] | Workflow[TWorkflowInput]: """ Define a Hatchet workflow, which can then declare `task`s and be `run`, `schedule`d, and so on. @@ -348,6 +349,8 @@ def workflow( :param default_additional_metadata: A dictionary of additional metadata to attach to each run of this workflow by default. + :param idempotency: An optional idempotency configuration for the workflow, controlling how Hatchet should determine if two runs of this workflow are "the same" for the purposes of deduplication and idempotent execution. + :returns: The created `Workflow` object, which can be used to declare tasks, run the workflow, and so on. """ @@ -367,7 +370,7 @@ def workflow( default_priority=default_priority, default_filters=default_filters or [], default_additional_metadata=default_additional_metadata or {}, - idempotency_key_expression=idempotency_key_expression, + idempotency=idempotency, ), self, ) @@ -398,7 +401,7 @@ def task( backoff_max_seconds: int | None = None, default_filters: list[DefaultFilter] | None = None, default_additional_metadata: JSONSerializableMapping | None = None, - idempotency_key_expression: str | None = None, + idempotency: IdempotencyConfig | None = None, ) -> Callable[ [Callable[Concatenate[EmptyModel, Context, P], R | CoroutineLike[R]]], Standalone[EmptyModel, R], @@ -430,7 +433,7 @@ def task( backoff_max_seconds: int | None = None, default_filters: list[DefaultFilter] | None = None, default_additional_metadata: JSONSerializableMapping | None = None, - idempotency_key_expression: str | None = None, + idempotency: IdempotencyConfig | None = None, ) -> Callable[ [Callable[Concatenate[TWorkflowInput, Context, P], R | CoroutineLike[R]]], Standalone[TWorkflowInput, R], @@ -461,7 +464,7 @@ def task( backoff_max_seconds: int | None = None, default_filters: list[DefaultFilter] | None = None, default_additional_metadata: JSONSerializableMapping | None = None, - idempotency_key_expression: str | None = None, + idempotency: IdempotencyConfig | None = None, ) -> ( Callable[ [Callable[Concatenate[EmptyModel, Context, P], R | CoroutineLike[R]]], @@ -511,6 +514,8 @@ def task( :param default_additional_metadata: A dictionary of additional metadata to attach to each run of this task by default. + :param idempotency: An optional idempotency configuration for the task, controlling how Hatchet should determine if two runs of this task are "the same" for the purposes of deduplication and idempotent execution. + :returns: A decorator which creates a `Standalone` task object. """ @@ -535,7 +540,7 @@ def inner( input_validator=TypeAdapter(normalize_validator(input_validator)), default_filters=default_filters or [], default_additional_metadata=default_additional_metadata or {}, - idempotency_key_expression=idempotency_key_expression, + idempotency=idempotency, ), self, ) @@ -598,7 +603,7 @@ def durable_task( default_filters: list[DefaultFilter] | None = None, default_additional_metadata: JSONSerializableMapping | None = None, eviction_policy: EvictionPolicy | None = DEFAULT_DURABLE_TASK_EVICTION_POLICY, - idempotency_key_expression: str | None = None, + idempotency: IdempotencyConfig | None = None, ) -> Callable[ [Callable[Concatenate[EmptyModel, DurableContext, P], R | CoroutineLike[R]]], Standalone[EmptyModel, R], @@ -631,7 +636,7 @@ def durable_task( default_filters: list[DefaultFilter] | None = None, default_additional_metadata: JSONSerializableMapping | None = None, eviction_policy: EvictionPolicy | None = DEFAULT_DURABLE_TASK_EVICTION_POLICY, - idempotency_key_expression: str | None = None, + idempotency: IdempotencyConfig | None = None, ) -> Callable[ [ Callable[ @@ -667,7 +672,7 @@ def durable_task( default_filters: list[DefaultFilter] | None = None, default_additional_metadata: JSONSerializableMapping | None = None, eviction_policy: EvictionPolicy | None = DEFAULT_DURABLE_TASK_EVICTION_POLICY, - idempotency_key_expression: str | None = None, + idempotency: IdempotencyConfig | None = None, ) -> ( Callable[ [ @@ -727,6 +732,8 @@ def durable_task( :param eviction_policy: An optional eviction policy controlling when idle durable tasks are evicted from workers. + :param idempotency: An optional idempotency configuration for the task, controlling how Hatchet should determine if two runs of this task are "the same" for the purposes of deduplication and idempotent execution. + :returns: A decorator which creates a `Standalone` task object. """ @@ -748,7 +755,7 @@ def inner( default_priority=default_priority, default_filters=default_filters or [], default_additional_metadata=default_additional_metadata or {}, - idempotency_key_expression=idempotency_key_expression, + idempotency=idempotency, ), self, ) diff --git a/sdks/python/hatchet_sdk/runnables/types.py b/sdks/python/hatchet_sdk/runnables/types.py index e4b5da8067..e0ea55bc55 100644 --- a/sdks/python/hatchet_sdk/runnables/types.py +++ b/sdks/python/hatchet_sdk/runnables/types.py @@ -18,6 +18,7 @@ from hatchet_sdk.types.concurrency import ( ConcurrencyExpression, ) +from hatchet_sdk.types.idempotency import IdempotencyConfig from hatchet_sdk.types.priority import Priority from hatchet_sdk.types.sticky import StickyStrategy from hatchet_sdk.utils.timedelta_to_expression import Duration @@ -100,7 +101,7 @@ class WorkflowConfig(BaseModel): concurrency: int | ConcurrencyExpression | list[ConcurrencyExpression] | None = None input_validator: TypeAdapter[TaskPayloadForInternalUse] default_priority: int | Priority | None = None - idempotency_key_expression: str | None = None + idempotency: IdempotencyConfig | None = None task_defaults: TaskDefaults = TaskDefaults() default_filters: list[DefaultFilter] = Field(default_factory=list) diff --git a/sdks/python/hatchet_sdk/runnables/workflow.py b/sdks/python/hatchet_sdk/runnables/workflow.py index 24de70ea8c..bd9e557a69 100644 --- a/sdks/python/hatchet_sdk/runnables/workflow.py +++ b/sdks/python/hatchet_sdk/runnables/workflow.py @@ -266,7 +266,11 @@ def to_proto(self) -> CreateWorkflowVersionRequest: default_priority=self._config.default_priority, default_filters=[f.to_proto() for f in self._config.default_filters], input_json_schema=json_schema, - idempotency_key_expr=self._config.idempotency_key_expression, + idempotency=( + self._config.idempotency.to_proto() + if self._config.idempotency + else None + ), ) def _get_workflow_input(self, ctx: Context) -> TWorkflowInput: diff --git a/sdks/python/hatchet_sdk/types/idempotency.py b/sdks/python/hatchet_sdk/types/idempotency.py new file mode 100644 index 0000000000..4fddb5f3f9 --- /dev/null +++ b/sdks/python/hatchet_sdk/types/idempotency.py @@ -0,0 +1,19 @@ +from datetime import timedelta + +from pydantic import BaseModel + +from hatchet_sdk.contracts.v1.workflows_pb2 import ( + IdempotencyConfig as IdempotencyConfigProto, +) + + +class IdempotencyConfig(BaseModel): + key_expression: str + ttl: timedelta | None + + def to_proto(self) -> IdempotencyConfigProto: + ttl_ms = int(self.ttl.total_seconds() * 1000) if self.ttl is not None else None + return IdempotencyConfigProto( + expression=self.key_expression, + ttl_ms=ttl_ms, + ) From 73beb46445f7d816068d55303f9994854ccd146c Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Fri, 29 May 2026 14:54:32 -0400 Subject: [PATCH 08/69] fix: engine wiring --- internal/services/admin/v1/server.go | 35 +++++++++++++++++----------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/internal/services/admin/v1/server.go b/internal/services/admin/v1/server.go index ebd4d822c0..f52b3c1761 100644 --- a/internal/services/admin/v1/server.go +++ b/internal/services/admin/v1/server.go @@ -897,20 +897,29 @@ func getCreateWorkflowOpts(req *contracts.CreateWorkflowVersionRequest) (*v1.Cre }) } + var idempotency *v1.IdempotencyConfig + + if req.Idempotency != nil { + idempotency = &v1.IdempotencyConfig{ + Expression: req.Idempotency.Expression, + TTLMs: req.Idempotency.TtlMs, + } + } + return &v1.CreateWorkflowVersionOpts{ - Name: req.Name, - Concurrency: concurrency, - Description: &req.Description, - EventTriggers: req.EventTriggers, - CronTriggers: req.CronTriggers, - CronInput: cronInput, - Tasks: tasks, - OnFailure: onFailureTask, - Sticky: sticky, - DefaultPriority: req.DefaultPriority, - DefaultFilters: defaultFilters, - InputJsonSchema: req.InputJsonSchema, - IdempotencyKeyExpr: req.IdempotencyKeyExpr, + Name: req.Name, + Concurrency: concurrency, + Description: &req.Description, + EventTriggers: req.EventTriggers, + CronTriggers: req.CronTriggers, + CronInput: cronInput, + Tasks: tasks, + OnFailure: onFailureTask, + Sticky: sticky, + DefaultPriority: req.DefaultPriority, + DefaultFilters: defaultFilters, + InputJsonSchema: req.InputJsonSchema, + Idempotency: idempotency, }, nil } From 30548575b3870ad772f2a3f051429da8658c395b Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Fri, 29 May 2026 14:56:40 -0400 Subject: [PATCH 09/69] fix: py type --- sdks/python/examples/simple/worker.py | 7 ++++++- sdks/python/hatchet_sdk/types/idempotency.py | 5 ++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/sdks/python/examples/simple/worker.py b/sdks/python/examples/simple/worker.py index ed90b2c1b8..27d106219f 100644 --- a/sdks/python/examples/simple/worker.py +++ b/sdks/python/examples/simple/worker.py @@ -1,10 +1,15 @@ # > Simple from hatchet_sdk import Context, DurableContext, EmptyModel, Hatchet, IdempotencyConfig +from datetime import timedelta hatchet = Hatchet() -@hatchet.task(idempotency=IdempotencyConfig(key_expression="input.some_id", ttl=None)) +@hatchet.task( + idempotency=IdempotencyConfig( + key_expression="input.some_id", ttl=timedelta(minutes=1) + ) +) def simple(input: EmptyModel, ctx: Context) -> dict[str, str]: return {"result": "Hello, world!"} diff --git a/sdks/python/hatchet_sdk/types/idempotency.py b/sdks/python/hatchet_sdk/types/idempotency.py index 4fddb5f3f9..2212120d8f 100644 --- a/sdks/python/hatchet_sdk/types/idempotency.py +++ b/sdks/python/hatchet_sdk/types/idempotency.py @@ -9,11 +9,10 @@ class IdempotencyConfig(BaseModel): key_expression: str - ttl: timedelta | None + ttl: timedelta def to_proto(self) -> IdempotencyConfigProto: - ttl_ms = int(self.ttl.total_seconds() * 1000) if self.ttl is not None else None return IdempotencyConfigProto( expression=self.key_expression, - ttl_ms=ttl_ms, + ttl_ms=int(self.ttl.total_seconds() * 1000), ) From 8214dfdd39a2603ed1b71c7511e48acda9c4f698 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Fri, 29 May 2026 14:56:56 -0400 Subject: [PATCH 10/69] chore: revert worker changes --- sdks/python/examples/simple/worker.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/sdks/python/examples/simple/worker.py b/sdks/python/examples/simple/worker.py index 27d106219f..f7970eb74b 100644 --- a/sdks/python/examples/simple/worker.py +++ b/sdks/python/examples/simple/worker.py @@ -1,15 +1,10 @@ # > Simple -from hatchet_sdk import Context, DurableContext, EmptyModel, Hatchet, IdempotencyConfig -from datetime import timedelta +from hatchet_sdk import Context, DurableContext, EmptyModel, Hatchet hatchet = Hatchet() -@hatchet.task( - idempotency=IdempotencyConfig( - key_expression="input.some_id", ttl=timedelta(minutes=1) - ) -) +@hatchet.task() def simple(input: EmptyModel, ctx: Context) -> dict[str, str]: return {"result": "Hello, world!"} @@ -23,7 +18,7 @@ async def simple_durable(input: EmptyModel, ctx: DurableContext) -> dict[str, st def main() -> None: worker = hatchet.worker( "test-worker", - workflows=[simple], + workflows=[simple, simple_durable], ) worker.start() From 5b432bbaf884d96cdf46a34396ec4f2a7ca11e0e Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Fri, 29 May 2026 14:59:32 -0400 Subject: [PATCH 11/69] feat: new example --- sdks/python/examples/idempotency/trigger.py | 4 +++ sdks/python/examples/idempotency/worker.py | 29 +++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 sdks/python/examples/idempotency/trigger.py create mode 100644 sdks/python/examples/idempotency/worker.py diff --git a/sdks/python/examples/idempotency/trigger.py b/sdks/python/examples/idempotency/trigger.py new file mode 100644 index 0000000000..40e4f8510d --- /dev/null +++ b/sdks/python/examples/idempotency/trigger.py @@ -0,0 +1,4 @@ +from examples.idempotency.worker import idempotent_task, IdempotencyInput + +idempotent_task.run(input=IdempotencyInput(id=123), wait_for_result=False) +idempotent_task.run(input=IdempotencyInput(id=123), wait_for_result=False) diff --git a/sdks/python/examples/idempotency/worker.py b/sdks/python/examples/idempotency/worker.py new file mode 100644 index 0000000000..0dfefa01d2 --- /dev/null +++ b/sdks/python/examples/idempotency/worker.py @@ -0,0 +1,29 @@ +from hatchet_sdk import Context, Hatchet, IdempotencyConfig +from datetime import timedelta +from pydantic import BaseModel + +hatchet = Hatchet() + + +class IdempotencyInput(BaseModel): + id: int + + +@hatchet.task( + idempotency=IdempotencyConfig(key_expression="input.id", ttl=timedelta(minutes=1)), + input_validator=IdempotencyInput, +) +async def idempotent_task(input: IdempotencyInput, ctx: Context) -> dict[str, str]: + return {"result": f"Hello, world from task {input.id}"} + + +def main() -> None: + worker = hatchet.worker( + "test-worker", + workflows=[idempotent_task], + ) + worker.start() + + +if __name__ == "__main__": + main() From 5234b9afd48029493e3aeb185572c536d138a1b7 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Fri, 29 May 2026 15:01:00 -0400 Subject: [PATCH 12/69] feat: fix migration, add cols --- ...0517_v1_0_106.sql => 20260529180517_v1_0_115.sql} | 4 ++-- pkg/repository/sqlcv1/models.go | 1 + pkg/repository/sqlcv1/workflows.sql.go | 12 ++++++++---- sql/schema/v0.sql | 1 + 4 files changed, 12 insertions(+), 6 deletions(-) rename cmd/hatchet-migrate/migrate/migrations/{20260507180517_v1_0_106.sql => 20260529180517_v1_0_115.sql} (77%) diff --git a/cmd/hatchet-migrate/migrate/migrations/20260507180517_v1_0_106.sql b/cmd/hatchet-migrate/migrate/migrations/20260529180517_v1_0_115.sql similarity index 77% rename from cmd/hatchet-migrate/migrate/migrations/20260507180517_v1_0_106.sql rename to cmd/hatchet-migrate/migrate/migrations/20260529180517_v1_0_115.sql index b9fa9354ac..8d490abe5c 100644 --- a/cmd/hatchet-migrate/migrate/migrations/20260507180517_v1_0_106.sql +++ b/cmd/hatchet-migrate/migrate/migrations/20260529180517_v1_0_115.sql @@ -2,7 +2,7 @@ -- +goose StatementBegin ALTER TABLE "WorkflowVersion" ADD COLUMN "idempotencyKeyExpression" TEXT, - ADD COLUMN "idempotencyKeyTtl" BIGINT + ADD COLUMN "idempotencyKeyTtlMs" BIGINT ; -- +goose StatementEnd @@ -10,6 +10,6 @@ ALTER TABLE "WorkflowVersion" -- +goose StatementBegin ALTER TABLE "WorkflowVersion" DROP COLUMN "idempotencyKeyExpression", - DROP COLUMN "idempotencyKeyTtl" + DROP COLUMN "idempotencyKeyTtlMs" ; -- +goose StatementEnd diff --git a/pkg/repository/sqlcv1/models.go b/pkg/repository/sqlcv1/models.go index 70c0a7dd41..22060e9286 100644 --- a/pkg/repository/sqlcv1/models.go +++ b/pkg/repository/sqlcv1/models.go @@ -4001,4 +4001,5 @@ type WorkflowVersion struct { CreateWorkflowVersionOpts []byte `json:"createWorkflowVersionOpts"` InputJsonSchema []byte `json:"inputJsonSchema"` IdempotencyKeyExpression pgtype.Text `json:"idempotencyKeyExpression"` + IdempotencyKeyTtlMs pgtype.Int8 `json:"idempotencyKeyTtlMs"` } diff --git a/pkg/repository/sqlcv1/workflows.sql.go b/pkg/repository/sqlcv1/workflows.sql.go index eab95d2ab5..b1d0850916 100644 --- a/pkg/repository/sqlcv1/workflows.sql.go +++ b/pkg/repository/sqlcv1/workflows.sql.go @@ -933,7 +933,7 @@ INSERT INTO "WorkflowVersion" ( $12::jsonb, $13::text, $14::bigint -) RETURNING id, "createdAt", "updatedAt", "deletedAt", version, "order", "workflowId", checksum, "scheduleTimeout", "onFailureJobId", sticky, kind, "defaultPriority", "createWorkflowVersionOpts", "inputJsonSchema", "idempotencyKeyExpression" +) RETURNING id, "createdAt", "updatedAt", "deletedAt", version, "order", "workflowId", checksum, "scheduleTimeout", "onFailureJobId", sticky, kind, "defaultPriority", "createWorkflowVersionOpts", "inputJsonSchema", "idempotencyKeyExpression", "idempotencyKeyTtlMs" ` type CreateWorkflowVersionParams struct { @@ -988,6 +988,7 @@ func (q *Queries) CreateWorkflowVersion(ctx context.Context, db DBTX, arg Create &i.CreateWorkflowVersionOpts, &i.InputJsonSchema, &i.IdempotencyKeyExpression, + &i.IdempotencyKeyTtlMs, ) return &i, err } @@ -1228,7 +1229,7 @@ func (q *Queries) GetWorkflowShape(ctx context.Context, db DBTX, workflowversion const getWorkflowVersionById = `-- name: GetWorkflowVersionById :one SELECT - wv.id, wv."createdAt", wv."updatedAt", wv."deletedAt", wv.version, wv."order", wv."workflowId", wv.checksum, wv."scheduleTimeout", wv."onFailureJobId", wv.sticky, wv.kind, wv."defaultPriority", wv."createWorkflowVersionOpts", wv."inputJsonSchema", wv."idempotencyKeyExpression", + wv.id, wv."createdAt", wv."updatedAt", wv."deletedAt", wv.version, wv."order", wv."workflowId", wv.checksum, wv."scheduleTimeout", wv."onFailureJobId", wv.sticky, wv.kind, wv."defaultPriority", wv."createWorkflowVersionOpts", wv."inputJsonSchema", wv."idempotencyKeyExpression", wv."idempotencyKeyTtlMs", w.id, w."createdAt", w."updatedAt", w."deletedAt", w."tenantId", w.name, w.description, w."isPaused" FROM "WorkflowVersion" as wv @@ -1264,6 +1265,7 @@ func (q *Queries) GetWorkflowVersionById(ctx context.Context, db DBTX, id uuid.U &i.WorkflowVersion.CreateWorkflowVersionOpts, &i.WorkflowVersion.InputJsonSchema, &i.WorkflowVersion.IdempotencyKeyExpression, + &i.WorkflowVersion.IdempotencyKeyTtlMs, &i.Workflow.ID, &i.Workflow.CreatedAt, &i.Workflow.UpdatedAt, @@ -1352,7 +1354,7 @@ func (q *Queries) GetWorkflowVersionEventTriggerRefs(ctx context.Context, db DBT const getWorkflowVersionForEngine = `-- name: GetWorkflowVersionForEngine :many SELECT - workflowversions.id, workflowversions."createdAt", workflowversions."updatedAt", workflowversions."deletedAt", workflowversions.version, workflowversions."order", workflowversions."workflowId", workflowversions.checksum, workflowversions."scheduleTimeout", workflowversions."onFailureJobId", workflowversions.sticky, workflowversions.kind, workflowversions."defaultPriority", workflowversions."createWorkflowVersionOpts", workflowversions."inputJsonSchema", workflowversions."idempotencyKeyExpression", + workflowversions.id, workflowversions."createdAt", workflowversions."updatedAt", workflowversions."deletedAt", workflowversions.version, workflowversions."order", workflowversions."workflowId", workflowversions.checksum, workflowversions."scheduleTimeout", workflowversions."onFailureJobId", workflowversions.sticky, workflowversions.kind, workflowversions."defaultPriority", workflowversions."createWorkflowVersionOpts", workflowversions."inputJsonSchema", workflowversions."idempotencyKeyExpression", workflowversions."idempotencyKeyTtlMs", w."name" as "workflowName", wc."limitStrategy" as "concurrencyLimitStrategy", wc."maxRuns" as "concurrencyMaxRuns", @@ -1411,6 +1413,7 @@ func (q *Queries) GetWorkflowVersionForEngine(ctx context.Context, db DBTX, arg &i.WorkflowVersion.CreateWorkflowVersionOpts, &i.WorkflowVersion.InputJsonSchema, &i.WorkflowVersion.IdempotencyKeyExpression, + &i.WorkflowVersion.IdempotencyKeyTtlMs, &i.WorkflowName, &i.ConcurrencyLimitStrategy, &i.ConcurrencyMaxRuns, @@ -1477,7 +1480,7 @@ const linkOnFailureJob = `-- name: LinkOnFailureJob :one UPDATE "WorkflowVersion" SET "onFailureJobId" = $1::uuid WHERE "id" = $2::uuid -RETURNING id, "createdAt", "updatedAt", "deletedAt", version, "order", "workflowId", checksum, "scheduleTimeout", "onFailureJobId", sticky, kind, "defaultPriority", "createWorkflowVersionOpts", "inputJsonSchema", "idempotencyKeyExpression" +RETURNING id, "createdAt", "updatedAt", "deletedAt", version, "order", "workflowId", checksum, "scheduleTimeout", "onFailureJobId", sticky, kind, "defaultPriority", "createWorkflowVersionOpts", "inputJsonSchema", "idempotencyKeyExpression", "idempotencyKeyTtlMs" ` type LinkOnFailureJobParams struct { @@ -1505,6 +1508,7 @@ func (q *Queries) LinkOnFailureJob(ctx context.Context, db DBTX, arg LinkOnFailu &i.CreateWorkflowVersionOpts, &i.InputJsonSchema, &i.IdempotencyKeyExpression, + &i.IdempotencyKeyTtlMs, ) return &i, err } diff --git a/sql/schema/v0.sql b/sql/schema/v0.sql index e51125dad4..2ffd1267b4 100644 --- a/sql/schema/v0.sql +++ b/sql/schema/v0.sql @@ -1087,6 +1087,7 @@ CREATE TABLE "createWorkflowVersionOpts" JSONB, "inputJsonSchema" JSONB, "idempotencyKeyExpression" TEXT, + "idempotencyKeyTtlMs" BIGINT, CONSTRAINT "WorkflowVersion_pkey" PRIMARY KEY ("id") ); From dff7c68b8eebd0ca15c8abdcacf58abd0dceab88 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Fri, 29 May 2026 15:01:36 -0400 Subject: [PATCH 13/69] feat: wiring --- pkg/repository/sqlcv1/workflows.sql | 4 ++-- pkg/repository/sqlcv1/workflows.sql.go | 6 +++--- pkg/repository/workflow.go | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/repository/sqlcv1/workflows.sql b/pkg/repository/sqlcv1/workflows.sql index 65c4b28905..eebbcc64ba 100644 --- a/pkg/repository/sqlcv1/workflows.sql +++ b/pkg/repository/sqlcv1/workflows.sql @@ -123,7 +123,7 @@ INSERT INTO "WorkflowVersion" ( "createWorkflowVersionOpts", "inputJsonSchema", "idempotencyKeyExpression", - "idempotencyKeyTtl" + "idempotencyKeyTtlMs" ) VALUES ( @id::uuid, coalesce(sqlc.narg('createdAt')::timestamp, CURRENT_TIMESTAMP), @@ -140,7 +140,7 @@ INSERT INTO "WorkflowVersion" ( sqlc.narg('createWorkflowVersionOpts')::jsonb, sqlc.narg('inputJsonSchema')::jsonb, sqlc.narg('idempotencyKeyExpression')::text, - sqlc.narg('idempotencyKeyTtl')::bigint + sqlc.narg('idempotencyKeyTtlMs')::bigint ) RETURNING *; -- name: CreateJob :one diff --git a/pkg/repository/sqlcv1/workflows.sql.go b/pkg/repository/sqlcv1/workflows.sql.go index b1d0850916..e21a0fd9b1 100644 --- a/pkg/repository/sqlcv1/workflows.sql.go +++ b/pkg/repository/sqlcv1/workflows.sql.go @@ -915,7 +915,7 @@ INSERT INTO "WorkflowVersion" ( "createWorkflowVersionOpts", "inputJsonSchema", "idempotencyKeyExpression", - "idempotencyKeyTtl" + "idempotencyKeyTtlMs" ) VALUES ( $1::uuid, coalesce($2::timestamp, CURRENT_TIMESTAMP), @@ -950,7 +950,7 @@ type CreateWorkflowVersionParams struct { CreateWorkflowVersionOpts []byte `json:"createWorkflowVersionOpts"` InputJsonSchema []byte `json:"inputJsonSchema"` IdempotencyKeyExpression pgtype.Text `json:"idempotencyKeyExpression"` - IdempotencyKeyTtl pgtype.Int8 `json:"idempotencyKeyTtl"` + IdempotencyKeyTtlMs pgtype.Int8 `json:"idempotencyKeyTtlMs"` } func (q *Queries) CreateWorkflowVersion(ctx context.Context, db DBTX, arg CreateWorkflowVersionParams) (*WorkflowVersion, error) { @@ -968,7 +968,7 @@ func (q *Queries) CreateWorkflowVersion(ctx context.Context, db DBTX, arg Create arg.CreateWorkflowVersionOpts, arg.InputJsonSchema, arg.IdempotencyKeyExpression, - arg.IdempotencyKeyTtl, + arg.IdempotencyKeyTtlMs, ) var i WorkflowVersion err := row.Scan( diff --git a/pkg/repository/workflow.go b/pkg/repository/workflow.go index 264130975e..6448fe357b 100644 --- a/pkg/repository/workflow.go +++ b/pkg/repository/workflow.go @@ -440,7 +440,7 @@ func (r *workflowRepository) createWorkflowVersionTxs(ctx context.Context, tx sq String: idempotency.Expression, Valid: true, } - createParams.IdempotencyKeyTtl = sqlchelpers.ToBigInt(&idempotency.TTLMs) + createParams.IdempotencyKeyTtlMs = sqlchelpers.ToBigInt(&idempotency.TTLMs) } if opts.DefaultPriority != nil { From 0e6a5b0b02778cd3d577761c9e9406b679e8fe29 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Fri, 29 May 2026 15:21:52 -0400 Subject: [PATCH 14/69] feat: wire up idempotency to the triggerTuple --- pkg/repository/ids.go | 3 -- pkg/repository/sqlcv1/triggers.sql | 12 +++++-- pkg/repository/sqlcv1/triggers.sql.go | 43 +++++++++++++++++------- pkg/repository/trigger.go | 48 ++++++++++----------------- 4 files changed, 57 insertions(+), 49 deletions(-) diff --git a/pkg/repository/ids.go b/pkg/repository/ids.go index 454eb64272..cda9de11a0 100644 --- a/pkg/repository/ids.go +++ b/pkg/repository/ids.go @@ -27,9 +27,6 @@ type WorkflowNameTriggerOpts struct { ExternalId uuid.UUID - // (optional) The idempotency key to use for debouncing this task - IdempotencyKey *IdempotencyKey - // Whether to skip the creation of the child workflow ShouldSkip bool } diff --git a/pkg/repository/sqlcv1/triggers.sql b/pkg/repository/sqlcv1/triggers.sql index f1e9b90508..aabbe79639 100644 --- a/pkg/repository/sqlcv1/triggers.sql +++ b/pkg/repository/sqlcv1/triggers.sql @@ -4,7 +4,9 @@ WITH latest_versions AS ( SELECT DISTINCT ON("workflowId") "workflowId", workflowVersions."id" AS "workflowVersionId", - workflow."name" AS "workflowName" + workflow."name" AS "workflowName", + workflowVersions."idempotencyKeyExpression", + workflowVersions."idempotencyKeyTtlMs" FROM "WorkflowVersion" as workflowVersions JOIN @@ -24,7 +26,9 @@ SELECT latest_versions."workflowId", latest_versions."workflowName", eventRef."eventKey" as "workflowTriggeringEventKeyPattern", - k.event_key::TEXT as "incomingEventKey" + k.event_key::TEXT as "incomingEventKey", + latest_versions."idempotencyKeyExpression", + latest_versions."idempotencyKeyTtlMs" FROM latest_versions JOIN @@ -38,7 +42,9 @@ JOIN event_keys k ON k.event_key LIKE REPLACE(eventRef."eventKey", '*', '%') SELECT DISTINCT ON("workflowId") "workflowId", workflowVersions."id" AS "workflowVersionId", - workflow."name" AS "workflowName" + workflow."name" AS "workflowName", + workflowVersions."idempotencyKeyExpression", + workflowVersions."idempotencyKeyTtlMs" FROM "WorkflowVersion" as workflowVersions JOIN diff --git a/pkg/repository/sqlcv1/triggers.sql.go b/pkg/repository/sqlcv1/triggers.sql.go index 1b6d856f65..f15c6829d8 100644 --- a/pkg/repository/sqlcv1/triggers.sql.go +++ b/pkg/repository/sqlcv1/triggers.sql.go @@ -9,13 +9,16 @@ import ( "context" "github.com/google/uuid" + "github.com/jackc/pgx/v5/pgtype" ) const listWorkflowsByNames = `-- name: ListWorkflowsByNames :many SELECT DISTINCT ON("workflowId") "workflowId", workflowVersions."id" AS "workflowVersionId", - workflow."name" AS "workflowName" + workflow."name" AS "workflowName", + workflowVersions."idempotencyKeyExpression", + workflowVersions."idempotencyKeyTtlMs" FROM "WorkflowVersion" as workflowVersions JOIN @@ -33,9 +36,11 @@ type ListWorkflowsByNamesParams struct { } type ListWorkflowsByNamesRow struct { - WorkflowId uuid.UUID `json:"workflowId"` - WorkflowVersionId uuid.UUID `json:"workflowVersionId"` - WorkflowName string `json:"workflowName"` + WorkflowId uuid.UUID `json:"workflowId"` + WorkflowVersionId uuid.UUID `json:"workflowVersionId"` + WorkflowName string `json:"workflowName"` + IdempotencyKeyExpression pgtype.Text `json:"idempotencyKeyExpression"` + IdempotencyKeyTtlMs pgtype.Int8 `json:"idempotencyKeyTtlMs"` } func (q *Queries) ListWorkflowsByNames(ctx context.Context, db DBTX, arg ListWorkflowsByNamesParams) ([]*ListWorkflowsByNamesRow, error) { @@ -47,7 +52,13 @@ func (q *Queries) ListWorkflowsByNames(ctx context.Context, db DBTX, arg ListWor var items []*ListWorkflowsByNamesRow for rows.Next() { var i ListWorkflowsByNamesRow - if err := rows.Scan(&i.WorkflowId, &i.WorkflowVersionId, &i.WorkflowName); err != nil { + if err := rows.Scan( + &i.WorkflowId, + &i.WorkflowVersionId, + &i.WorkflowName, + &i.IdempotencyKeyExpression, + &i.IdempotencyKeyTtlMs, + ); err != nil { return nil, err } items = append(items, &i) @@ -63,7 +74,9 @@ WITH latest_versions AS ( SELECT DISTINCT ON("workflowId") "workflowId", workflowVersions."id" AS "workflowVersionId", - workflow."name" AS "workflowName" + workflow."name" AS "workflowName", + workflowVersions."idempotencyKeyExpression", + workflowVersions."idempotencyKeyTtlMs" FROM "WorkflowVersion" as workflowVersions JOIN @@ -82,7 +95,9 @@ SELECT latest_versions."workflowId", latest_versions."workflowName", eventRef."eventKey" as "workflowTriggeringEventKeyPattern", - k.event_key::TEXT as "incomingEventKey" + k.event_key::TEXT as "incomingEventKey", + latest_versions."idempotencyKeyExpression", + latest_versions."idempotencyKeyTtlMs" FROM latest_versions JOIN @@ -98,11 +113,13 @@ type ListWorkflowsForEventsParams struct { } type ListWorkflowsForEventsRow struct { - WorkflowVersionId uuid.UUID `json:"workflowVersionId"` - WorkflowId uuid.UUID `json:"workflowId"` - WorkflowName string `json:"workflowName"` - WorkflowTriggeringEventKeyPattern string `json:"workflowTriggeringEventKeyPattern"` - IncomingEventKey string `json:"incomingEventKey"` + WorkflowVersionId uuid.UUID `json:"workflowVersionId"` + WorkflowId uuid.UUID `json:"workflowId"` + WorkflowName string `json:"workflowName"` + WorkflowTriggeringEventKeyPattern string `json:"workflowTriggeringEventKeyPattern"` + IncomingEventKey string `json:"incomingEventKey"` + IdempotencyKeyExpression pgtype.Text `json:"idempotencyKeyExpression"` + IdempotencyKeyTtlMs pgtype.Int8 `json:"idempotencyKeyTtlMs"` } // Get all of the latest workflow versions @@ -122,6 +139,8 @@ func (q *Queries) ListWorkflowsForEvents(ctx context.Context, db DBTX, arg ListW &i.WorkflowName, &i.WorkflowTriggeringEventKeyPattern, &i.IncomingEventKey, + &i.IdempotencyKeyExpression, + &i.IdempotencyKeyTtlMs, ); err != nil { return nil, err } diff --git a/pkg/repository/trigger.go b/pkg/repository/trigger.go index 5fba9f0d80..0372b18a94 100644 --- a/pkg/repository/trigger.go +++ b/pkg/repository/trigger.go @@ -586,6 +586,7 @@ type triggerTuple struct { desiredWorkerLabels []*sqlcv1.GetDesiredLabelsRow triggeringEventExternalId *uuid.UUID triggeringEventKey *string + idempotency *IdempotencyConfig } type createCoreUserEventOpts struct { @@ -2238,6 +2239,15 @@ func (r *sharedRepository) prepareTriggerFromEvents(ctx context.Context, tx sqlc additionalMetadata := triggerConverter.ToMetadata(opt.AdditionalMetadata) externalId := uuid.New() + var idempotency *IdempotencyConfig + + if workflow.IdempotencyKeyExpression.Valid && workflow.IdempotencyKeyTtlMs.Valid { + idempotency = &IdempotencyConfig{ + Expression: workflow.IdempotencyKeyExpression.String, + TTLMs: workflow.IdempotencyKeyTtlMs.Int64, + } + } + triggerOpts = append(triggerOpts, triggerTuple{ workflowVersionId: workflow.WorkflowVersionId, workflowId: workflow.WorkflowId, @@ -2249,6 +2259,7 @@ func (r *sharedRepository) prepareTriggerFromEvents(ctx context.Context, tx sqlc filterPayload: decision.FilterPayload, triggeringEventExternalId: &opt.ExternalId, triggeringEventKey: &opt.Key, + idempotency: idempotency, }) externalIdToEventIdAndFilterId[externalId] = EventExternalIdFilterId{ @@ -2283,13 +2294,8 @@ func (r *sharedRepository) prepareTriggerFromWorkflowNames(ctx context.Context, workflowNames := make([]string, 0, len(opts)) uniqueNames := make(map[string]struct{}) namesToOpts := make(map[string][]*WorkflowNameTriggerOpts) - idempotencyKeyToExternalIds := make(map[IdempotencyKey]uuid.UUID) for _, opt := range opts { - if opt.IdempotencyKey != nil { - idempotencyKeyToExternalIds[*opt.IdempotencyKey] = opt.ExternalId - } - namesToOpts[opt.WorkflowName] = append(namesToOpts[opt.WorkflowName], opt) if _, ok := uniqueNames[opt.WorkflowName]; ok { @@ -2300,21 +2306,6 @@ func (r *sharedRepository) prepareTriggerFromWorkflowNames(ctx context.Context, workflowNames = append(workflowNames, opt.WorkflowName) } - keyClaimantPairs := make([]KeyClaimantPair, 0, len(idempotencyKeyToExternalIds)) - - for idempotencyKey, runExternalId := range idempotencyKeyToExternalIds { - keyClaimantPairs = append(keyClaimantPairs, KeyClaimantPair{ - IdempotencyKey: idempotencyKey, - ClaimedByExternalId: runExternalId, - }) - } - - keyClaimantPairToWasClaimed, err := claimIdempotencyKeys(ctx, r.queries, tx, tenantId, keyClaimantPairs) - - if err != nil { - return nil, fmt.Errorf("failed to claim idempotency keys: %w", err) - } - workflowVersionsByNames, err := r.listWorkflowsByNames(ctx, tx, tenantId, workflowNames) if err != nil { @@ -2332,17 +2323,11 @@ func (r *sharedRepository) prepareTriggerFromWorkflowNames(ctx context.Context, } for _, opt := range opts { - if opt.IdempotencyKey != nil { - keyClaimantPair := KeyClaimantPair{ - IdempotencyKey: *opt.IdempotencyKey, - ClaimedByExternalId: opt.ExternalId, - } - - wasSuccessfullyClaimed := keyClaimantPairToWasClaimed[keyClaimantPair] - - // if we did not successfully claim the idempotency key, we should not trigger the workflow - if !wasSuccessfullyClaimed { - continue + var idempotency *IdempotencyConfig + if workflowVersion.IdempotencyKeyExpression.Valid && workflowVersion.IdempotencyKeyTtlMs.Valid { + idempotency = &IdempotencyConfig{ + Expression: workflowVersion.IdempotencyKeyExpression.String, + TTLMs: workflowVersion.IdempotencyKeyTtlMs.Int64, } } @@ -2361,6 +2346,7 @@ func (r *sharedRepository) prepareTriggerFromWorkflowNames(ctx context.Context, childKey: opt.ChildKey, priority: opt.Priority, desiredWorkerLabels: opt.DesiredWorkerLabels, + idempotency: idempotency, }) } } From 30fb2faf767d00ee78b7053a7f94bac7e524cb99 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Fri, 29 May 2026 16:05:46 -0400 Subject: [PATCH 15/69] feat: initial engine wiring --- .../services/ticker/schedule_workflow_v1.go | 25 +---- pkg/repository/idempotency.go | 15 +-- pkg/repository/sqlcv1/idempotency-keys.sql | 73 +++++-------- pkg/repository/sqlcv1/idempotency-keys.sql.go | 103 +++++++----------- pkg/repository/trigger.go | 101 +++++++++++++++-- 5 files changed, 167 insertions(+), 150 deletions(-) diff --git a/internal/services/ticker/schedule_workflow_v1.go b/internal/services/ticker/schedule_workflow_v1.go index ad0d1c19dd..818434d9f1 100644 --- a/internal/services/ticker/schedule_workflow_v1.go +++ b/internal/services/ticker/schedule_workflow_v1.go @@ -2,37 +2,17 @@ package ticker import ( "context" - "errors" "fmt" - "time" "github.com/google/uuid" - "github.com/jackc/pgerrcode" - "github.com/jackc/pgx/v5/pgconn" "github.com/hatchet-dev/hatchet/internal/msgqueue" tasktypes "github.com/hatchet-dev/hatchet/internal/services/shared/tasktypes/v1" v1 "github.com/hatchet-dev/hatchet/pkg/repository" - "github.com/hatchet-dev/hatchet/pkg/repository/sqlchelpers" "github.com/hatchet-dev/hatchet/pkg/repository/sqlcv1" ) func (t *TickerImpl) runScheduledWorkflowV1(ctx context.Context, tenantId uuid.UUID, workflowVersion *sqlcv1.GetWorkflowVersionForEngineRow, scheduledWorkflowId uuid.UUID, scheduled *sqlcv1.PollScheduledWorkflowsRow) error { - expiresAt := scheduled.TriggerAt.Time.Add(time.Second * 30) - err := t.repov1.Idempotency().CreateIdempotencyKey(ctx, tenantId, scheduledWorkflowId.String(), sqlchelpers.TimestamptzFromTime(expiresAt)) - - var pgErr *pgconn.PgError - // if we get a unique violation, it means we tried to create a duplicate idempotency key, which means this - // run has already been processed, so we should just return - if err != nil && errors.As(err, &pgErr) && pgErr.Code == pgerrcode.UniqueViolation { - t.l.Info().Ctx(ctx).Msgf("idempotency key for scheduled workflow %s already exists, skipping", scheduledWorkflowId) - return nil - } else if err != nil { - return fmt.Errorf("could not create idempotency key: %w", err) - } - - key := v1.IdempotencyKey(scheduledWorkflowId.String()) - // send workflow run to task controller opt := &v1.WorkflowNameTriggerOpts{ TriggerTaskData: &v1.TriggerTaskData{ @@ -41,9 +21,8 @@ func (t *TickerImpl) runScheduledWorkflowV1(ctx context.Context, tenantId uuid.U AdditionalMetadata: scheduled.AdditionalMetadata, Priority: &scheduled.Priority, }, - IdempotencyKey: &key, - ExternalId: uuid.New(), - ShouldSkip: false, + ExternalId: uuid.New(), + ShouldSkip: false, } msg, err := tasktypes.TriggerTaskMessage( diff --git a/pkg/repository/idempotency.go b/pkg/repository/idempotency.go index 5024e35ceb..8608a7bae2 100644 --- a/pkg/repository/idempotency.go +++ b/pkg/repository/idempotency.go @@ -12,8 +12,13 @@ import ( type WasSuccessfullyClaimed bool type IdempotencyKey string +type ClaimIdempotencyKeysOpt struct { + Key string + ClaimedByExternalId uuid.UUID + ExpiresAt pgtype.Timestamptz +} + type IdempotencyRepository interface { - CreateIdempotencyKey(context context.Context, tenantId uuid.UUID, key string, expiresAt pgtype.Timestamptz) error EvictExpiredIdempotencyKeys(context context.Context, tenantId uuid.UUID) error } @@ -27,14 +32,6 @@ func newIdempotencyRepository(shared *sharedRepository) IdempotencyRepository { } } -func (r *idempotencyRepository) CreateIdempotencyKey(context context.Context, tenantId uuid.UUID, key string, expiresAt pgtype.Timestamptz) error { - return r.queries.CreateIdempotencyKey(context, r.pool, sqlcv1.CreateIdempotencyKeyParams{ - Tenantid: tenantId, - Key: key, - Expiresat: expiresAt, - }) -} - func (r *idempotencyRepository) EvictExpiredIdempotencyKeys(context context.Context, tenantId uuid.UUID) error { return r.queries.CleanUpExpiredIdempotencyKeys(context, r.pool, tenantId) } diff --git a/pkg/repository/sqlcv1/idempotency-keys.sql b/pkg/repository/sqlcv1/idempotency-keys.sql index d86b710abd..be4e088d65 100644 --- a/pkg/repository/sqlcv1/idempotency-keys.sql +++ b/pkg/repository/sqlcv1/idempotency-keys.sql @@ -1,15 +1,3 @@ --- name: CreateIdempotencyKey :exec -INSERT INTO v1_idempotency_key ( - tenant_id, - key, - expires_at -) -VALUES ( - @tenantId::UUID, - @key::TEXT, - @expiresAt::TIMESTAMPTZ -); - -- name: CleanUpExpiredIdempotencyKeys :exec DELETE FROM v1_idempotency_key WHERE @@ -19,55 +7,50 @@ WHERE -- name: ClaimIdempotencyKeys :many WITH inputs AS ( - SELECT DISTINCT + SELECT UNNEST(@keys::TEXT[]) AS key, + UNNEST(@expiresAts::TIMESTAMPTZ[]) AS expires_at, UNNEST(@claimedByExternalIds::UUID[]) AS claimed_by_external_id -), incoming_claims AS ( - SELECT - *, - ROW_NUMBER() OVER(PARTITION BY key ORDER BY claimed_by_external_id) AS claim_index - FROM inputs -), candidate_keys AS ( - -- Grab all of the keys that are attempting to be claimed - SELECT - tenant_id, - expires_at, - key, - ROW_NUMBER() OVER(PARTITION BY tenant_id, key ORDER BY expires_at) AS key_index +), locked_existing_keys AS ( + SELECT * FROM v1_idempotency_key WHERE tenant_id = @tenantId::UUID AND key IN ( SELECT key - FROM incoming_claims + FROM inputs ) - AND claimed_by_external_id IS NULL - AND expires_at > NOW() -), to_update AS ( - SELECT - ck.tenant_id, - ck.expires_at, - ck.key, - ic.claimed_by_external_id - FROM candidate_keys ck - JOIN incoming_claims ic ON (ck.key, ck.key_index) = (ic.key, ic.claim_index) - WHERE ck.tenant_id = @tenantId::UUID FOR UPDATE SKIP LOCKED +), already_claimed_keys AS ( + SELECT * + FROM locked_existing_keys + WHERE expires_at > NOW() +), claimable_keys AS ( + SELECT * + FROM locked_existing_keys + WHERE expires_at <= NOW() ), claims AS ( - UPDATE v1_idempotency_key k + INSERT INTO v1_idempotency_key (tenant_id, key, expires_at, claimed_by_external_id) + SELECT key, expires_at, @tenantId::UUID, claimed_by_external_id + FROM inputs + ON CONFLICT (tenant_id, key) DO UPDATE SET - claimed_by_external_id = u.claimed_by_external_id, - updated_at = NOW() - FROM to_update u - WHERE (u.tenant_id, u.expires_at, u.key) = (k.tenant_id, k.expires_at, k.key) - RETURNING k.* + expires_at = CASE + WHEN (v1_idempotency_key.tenant_id, v1_idempotency_key.key) IN (SELECT tenant_id, key FROM claimable_keys) THEN EXCLUDED.expires_at + ELSE v1_idempotency_key.expires_at + END, + claimed_by_external_id = CASE + WHEN (v1_idempotency_key.tenant_id, v1_idempotency_key.key) IN (SELECT tenant_id, key FROM claimable_keys) THEN EXCLUDED.claimed_by_external_id + ELSE v1_idempotency_key.claimed_by_external_id + END + RETURNING * ) SELECT i.key::TEXT AS key, c.expires_at::TIMESTAMPTZ AS expires_at, - c.claimed_by_external_id IS NOT NULL::BOOLEAN AS was_successfully_claimed, + (c.claimed_by_external_id = i.claimed_by_external_id)::BOOLEAN AS was_successfully_claimed, c.claimed_by_external_id FROM inputs i -LEFT JOIN claims c ON (i.key = c.key AND i.claimed_by_external_id = c.claimed_by_external_id) +LEFT JOIN claims c USING (key) ; diff --git a/pkg/repository/sqlcv1/idempotency-keys.sql.go b/pkg/repository/sqlcv1/idempotency-keys.sql.go index 1ef63348e1..48b10c721d 100644 --- a/pkg/repository/sqlcv1/idempotency-keys.sql.go +++ b/pkg/repository/sqlcv1/idempotency-keys.sql.go @@ -14,63 +14,59 @@ import ( const claimIdempotencyKeys = `-- name: ClaimIdempotencyKeys :many WITH inputs AS ( - SELECT DISTINCT - UNNEST($1::TEXT[]) AS key, - UNNEST($2::UUID[]) AS claimed_by_external_id -), incoming_claims AS ( - SELECT - key, claimed_by_external_id, - ROW_NUMBER() OVER(PARTITION BY key ORDER BY claimed_by_external_id) AS claim_index - FROM inputs -), candidate_keys AS ( - -- Grab all of the keys that are attempting to be claimed SELECT - tenant_id, - expires_at, - key, - ROW_NUMBER() OVER(PARTITION BY tenant_id, key ORDER BY expires_at) AS key_index + UNNEST($1::TEXT[]) AS key, + UNNEST($2::TIMESTAMPTZ[]) AS expires_at, + UNNEST($3::UUID[]) AS claimed_by_external_id +), locked_existing_keys AS ( + SELECT tenant_id, key, expires_at, claimed_by_external_id, inserted_at, updated_at FROM v1_idempotency_key WHERE - tenant_id = $3::UUID + tenant_id = $4::UUID AND key IN ( SELECT key - FROM incoming_claims + FROM inputs ) - AND claimed_by_external_id IS NULL - AND expires_at > NOW() -), to_update AS ( - SELECT - ck.tenant_id, - ck.expires_at, - ck.key, - ic.claimed_by_external_id - FROM candidate_keys ck - JOIN incoming_claims ic ON (ck.key, ck.key_index) = (ic.key, ic.claim_index) - WHERE ck.tenant_id = $3::UUID FOR UPDATE SKIP LOCKED +), already_claimed_keys AS ( + SELECT tenant_id, key, expires_at, claimed_by_external_id, inserted_at, updated_at + FROM locked_existing_keys + WHERE expires_at > NOW() +), claimable_keys AS ( + SELECT tenant_id, key, expires_at, claimed_by_external_id, inserted_at, updated_at + FROM locked_existing_keys + WHERE expires_at <= NOW() ), claims AS ( - UPDATE v1_idempotency_key k + INSERT INTO v1_idempotency_key (tenant_id, key, expires_at, claimed_by_external_id) + SELECT key, expires_at, $4::UUID, claimed_by_external_id + FROM inputs + ON CONFLICT (tenant_id, key) DO UPDATE SET - claimed_by_external_id = u.claimed_by_external_id, - updated_at = NOW() - FROM to_update u - WHERE (u.tenant_id, u.expires_at, u.key) = (k.tenant_id, k.expires_at, k.key) - RETURNING k.tenant_id, k.key, k.expires_at, k.claimed_by_external_id, k.inserted_at, k.updated_at + expires_at = CASE + WHEN (v1_idempotency_key.tenant_id, v1_idempotency_key.key) IN (SELECT tenant_id, key FROM claimable_keys) THEN EXCLUDED.expires_at + ELSE v1_idempotency_key.expires_at + END, + claimed_by_external_id = CASE + WHEN (v1_idempotency_key.tenant_id, v1_idempotency_key.key) IN (SELECT tenant_id, key FROM claimable_keys) THEN EXCLUDED.claimed_by_external_id + ELSE v1_idempotency_key.claimed_by_external_id + END + RETURNING tenant_id, key, expires_at, claimed_by_external_id, inserted_at, updated_at ) SELECT i.key::TEXT AS key, c.expires_at::TIMESTAMPTZ AS expires_at, - c.claimed_by_external_id IS NOT NULL::BOOLEAN AS was_successfully_claimed, + (c.claimed_by_external_id = i.claimed_by_external_id)::BOOLEAN AS was_successfully_claimed, c.claimed_by_external_id FROM inputs i -LEFT JOIN claims c ON (i.key = c.key AND i.claimed_by_external_id = c.claimed_by_external_id) +LEFT JOIN claims c USING (key) ` type ClaimIdempotencyKeysParams struct { - Keys []string `json:"keys"` - Claimedbyexternalids []uuid.UUID `json:"claimedbyexternalids"` - Tenantid uuid.UUID `json:"tenantid"` + Keys []string `json:"keys"` + Expiresats []pgtype.Timestamptz `json:"expiresats"` + Claimedbyexternalids []uuid.UUID `json:"claimedbyexternalids"` + Tenantid uuid.UUID `json:"tenantid"` } type ClaimIdempotencyKeysRow struct { @@ -81,7 +77,12 @@ type ClaimIdempotencyKeysRow struct { } func (q *Queries) ClaimIdempotencyKeys(ctx context.Context, db DBTX, arg ClaimIdempotencyKeysParams) ([]*ClaimIdempotencyKeysRow, error) { - rows, err := db.Query(ctx, claimIdempotencyKeys, arg.Keys, arg.Claimedbyexternalids, arg.Tenantid) + rows, err := db.Query(ctx, claimIdempotencyKeys, + arg.Keys, + arg.Expiresats, + arg.Claimedbyexternalids, + arg.Tenantid, + ) if err != nil { return nil, err } @@ -116,27 +117,3 @@ func (q *Queries) CleanUpExpiredIdempotencyKeys(ctx context.Context, db DBTX, te _, err := db.Exec(ctx, cleanUpExpiredIdempotencyKeys, tenantid) return err } - -const createIdempotencyKey = `-- name: CreateIdempotencyKey :exec -INSERT INTO v1_idempotency_key ( - tenant_id, - key, - expires_at -) -VALUES ( - $1::UUID, - $2::TEXT, - $3::TIMESTAMPTZ -) -` - -type CreateIdempotencyKeyParams struct { - Tenantid uuid.UUID `json:"tenantid"` - Key string `json:"key"` - Expiresat pgtype.Timestamptz `json:"expiresat"` -} - -func (q *Queries) CreateIdempotencyKey(ctx context.Context, db DBTX, arg CreateIdempotencyKeyParams) error { - _, err := db.Exec(ctx, createIdempotencyKey, arg.Tenantid, arg.Key, arg.Expiresat) - return err -} diff --git a/pkg/repository/trigger.go b/pkg/repository/trigger.go index 0372b18a94..977e6b7c70 100644 --- a/pkg/repository/trigger.go +++ b/pkg/repository/trigger.go @@ -595,21 +595,108 @@ type createCoreUserEventOpts struct { params sqlcv1.BulkCreateEventsParams } +func (r *sharedRepository) evalIdempotencyKey(tuple triggerTuple) (string, error) { + inputData := make(map[string]interface{}) + if tuple.input != nil { + if err := json.Unmarshal(tuple.input, &inputData); err != nil { + return "", fmt.Errorf("failed to unmarshal input for idempotency key evaluation: %w", err) + } + } + + additionalMetadata := make(map[string]interface{}) + if tuple.additionalMetadata != nil { + if err := json.Unmarshal(tuple.additionalMetadata, &additionalMetadata); err != nil { + return "", fmt.Errorf("failed to unmarshal additional metadata for idempotency key evaluation: %w", err) + } + } + + key, err := r.celParser.ParseAndEvalWorkflowString( + tuple.idempotency.Expression, + cel.NewInput( + cel.WithInput(inputData), + cel.WithAdditionalMetadata(additionalMetadata), + ), + ) + if err != nil { + return "", fmt.Errorf("failed to evaluate idempotency key expression %q: %w", tuple.idempotency.Expression, err) + } + + return key, nil +} + func (r *sharedRepository) triggerWorkflows( ctx context.Context, existingTx *OptimisticTx, tenantId uuid.UUID, - tuples []triggerTuple, + triggerCandidateTuples []triggerTuple, coreEvents *createCoreUserEventOpts, ) ([]*V1TaskWithPayload, []*DAGWithData, error) { - for i := range tuples { - tuples[i].additionalMetadata = ensureTraceparent(tuples[i].additionalMetadata, tuples[i].externalId) + var preflightTx sqlcv1.DBTX = r.pool + + if existingTx != nil { + preflightTx = existingTx.tx + } + + tuples := make([]triggerTuple, 0, len(triggerCandidateTuples)) + + keys := make([]string, 0, len(triggerCandidateTuples)) + expiresAts := make([]pgtype.Timestamptz, 0, len(triggerCandidateTuples)) + claimedByExternalIds := make([]uuid.UUID, 0, len(triggerCandidateTuples)) + externalIdToTuple := make(map[uuid.UUID]triggerTuple) + + for _, tuple := range triggerCandidateTuples { + if tuple.idempotency == nil { + tuples = append(tuples, tuple) + } else { + externalIdToTuple[tuple.externalId] = tuple + key, err := r.evalIdempotencyKey(tuple) + if err != nil { + return nil, nil, err + } + + keys = append(keys, key) + expiresAts = append(expiresAts, pgtype.Timestamptz{ + Time: time.Now().Add(time.Duration(tuple.idempotency.TTLMs) * time.Millisecond), + Valid: true, + }) + claimedByExternalIds = append(claimedByExternalIds, tuple.externalId) + } + } + + if len(keys) > 0 { + // todo: need to use tx here, find the right one (this might be nil) + claims, err := r.queries.ClaimIdempotencyKeys(ctx, existingTx.tx, sqlcv1.ClaimIdempotencyKeysParams{ + Keys: keys, + Expiresats: expiresAts, + Claimedbyexternalids: claimedByExternalIds, + Tenantid: tenantId, + }) + + if err != nil { + return nil, nil, fmt.Errorf("failed to claim idempotency keys: %w", err) + } + + for _, claim := range claims { + if !claim.WasSuccessfullyClaimed || claim.ClaimedByExternalID == nil { + continue + } + + // todo: this probably should not be a pointer + tuple, ok := externalIdToTuple[*claim.ClaimedByExternalID] + + if !ok { + continue + } + + tuples = append(tuples, tuple) + } } // get unique workflow version ids uniqueWorkflowVersionIds := make(map[uuid.UUID]struct{}) - for _, tuple := range tuples { + for i, tuple := range tuples { + tuples[i].additionalMetadata = ensureTraceparent(tuples[i].additionalMetadata, tuples[i].externalId) uniqueWorkflowVersionIds[tuple.workflowVersionId] = struct{}{} } @@ -620,12 +707,6 @@ func (r *sharedRepository) triggerWorkflows( workflowVersionIds = append(workflowVersionIds, id) } - var preflightTx sqlcv1.DBTX = r.pool - - if existingTx != nil { - preflightTx = existingTx.tx - } - workflowVersionToSteps, err := r.listStepsByWorkflowVersionIds(ctx, preflightTx, tenantId, workflowVersionIds) if err != nil { From 3fec2455b15498e99aa0e8545507c7d4f4b7e8fd Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Fri, 29 May 2026 16:07:26 -0400 Subject: [PATCH 16/69] fix: len --- pkg/repository/trigger.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/repository/trigger.go b/pkg/repository/trigger.go index 977e6b7c70..0688afdac8 100644 --- a/pkg/repository/trigger.go +++ b/pkg/repository/trigger.go @@ -597,14 +597,14 @@ type createCoreUserEventOpts struct { func (r *sharedRepository) evalIdempotencyKey(tuple triggerTuple) (string, error) { inputData := make(map[string]interface{}) - if tuple.input != nil { + if len(tuple.input) > 0 { if err := json.Unmarshal(tuple.input, &inputData); err != nil { return "", fmt.Errorf("failed to unmarshal input for idempotency key evaluation: %w", err) } } additionalMetadata := make(map[string]interface{}) - if tuple.additionalMetadata != nil { + if len(tuple.additionalMetadata) > 0 { if err := json.Unmarshal(tuple.additionalMetadata, &additionalMetadata); err != nil { return "", fmt.Errorf("failed to unmarshal additional metadata for idempotency key evaluation: %w", err) } From ed2d3e65835809828db17032668a88ae8537e15a Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Fri, 29 May 2026 16:08:55 -0400 Subject: [PATCH 17/69] fix: types --- pkg/repository/sqlcv1/idempotency-keys.sql | 2 +- pkg/repository/sqlcv1/idempotency-keys.sql.go | 2 +- sdks/python/examples/idempotency/trigger.py | 4 ++-- sdks/python/examples/idempotency/worker.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/repository/sqlcv1/idempotency-keys.sql b/pkg/repository/sqlcv1/idempotency-keys.sql index be4e088d65..c9a9f6f839 100644 --- a/pkg/repository/sqlcv1/idempotency-keys.sql +++ b/pkg/repository/sqlcv1/idempotency-keys.sql @@ -30,7 +30,7 @@ WITH inputs AS ( FROM locked_existing_keys WHERE expires_at <= NOW() ), claims AS ( - INSERT INTO v1_idempotency_key (tenant_id, key, expires_at, claimed_by_external_id) + INSERT INTO v1_idempotency_key (key, expires_at, tenant_id, claimed_by_external_id) SELECT key, expires_at, @tenantId::UUID, claimed_by_external_id FROM inputs ON CONFLICT (tenant_id, key) DO UPDATE diff --git a/pkg/repository/sqlcv1/idempotency-keys.sql.go b/pkg/repository/sqlcv1/idempotency-keys.sql.go index 48b10c721d..6505bd0da5 100644 --- a/pkg/repository/sqlcv1/idempotency-keys.sql.go +++ b/pkg/repository/sqlcv1/idempotency-keys.sql.go @@ -37,7 +37,7 @@ WITH inputs AS ( FROM locked_existing_keys WHERE expires_at <= NOW() ), claims AS ( - INSERT INTO v1_idempotency_key (tenant_id, key, expires_at, claimed_by_external_id) + INSERT INTO v1_idempotency_key (key, expires_at, tenant_id, claimed_by_external_id) SELECT key, expires_at, $4::UUID, claimed_by_external_id FROM inputs ON CONFLICT (tenant_id, key) DO UPDATE diff --git a/sdks/python/examples/idempotency/trigger.py b/sdks/python/examples/idempotency/trigger.py index 40e4f8510d..9eadfb31b7 100644 --- a/sdks/python/examples/idempotency/trigger.py +++ b/sdks/python/examples/idempotency/trigger.py @@ -1,4 +1,4 @@ from examples.idempotency.worker import idempotent_task, IdempotencyInput -idempotent_task.run(input=IdempotencyInput(id=123), wait_for_result=False) -idempotent_task.run(input=IdempotencyInput(id=123), wait_for_result=False) +idempotent_task.run(input=IdempotencyInput(id="123"), wait_for_result=False) +idempotent_task.run(input=IdempotencyInput(id="123"), wait_for_result=False) diff --git a/sdks/python/examples/idempotency/worker.py b/sdks/python/examples/idempotency/worker.py index 0dfefa01d2..5e815d77ee 100644 --- a/sdks/python/examples/idempotency/worker.py +++ b/sdks/python/examples/idempotency/worker.py @@ -6,7 +6,7 @@ class IdempotencyInput(BaseModel): - id: int + id: str @hatchet.task( From 309526856b2544070fd6829ef37918c82ff1f8c1 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Fri, 29 May 2026 16:15:26 -0400 Subject: [PATCH 18/69] feat: add test --- .../examples/idempotency/test_idempotency.py | 25 +++++++++++++++++++ sdks/python/examples/idempotency/worker.py | 1 + sdks/python/examples/worker.py | 2 ++ 3 files changed, 28 insertions(+) create mode 100644 sdks/python/examples/idempotency/test_idempotency.py diff --git a/sdks/python/examples/idempotency/test_idempotency.py b/sdks/python/examples/idempotency/test_idempotency.py new file mode 100644 index 0000000000..be267ac0c3 --- /dev/null +++ b/sdks/python/examples/idempotency/test_idempotency.py @@ -0,0 +1,25 @@ +import asyncio + +import pytest + +from examples.idempotency.worker import idempotent_task, IdempotencyInput + + +from hatchet_sdk import Hatchet +from hatchet_sdk.clients.rest.models.v1_task_event_type import V1TaskEventType +from hatchet_sdk.clients.rest.models.v1_workflow_run_details import V1WorkflowRunDetails +from hatchet_sdk.exceptions import FailedTaskRunExceptionGroup + + +@pytest.mark.asyncio(loop_scope="session") +async def test_idempotency_keys_prevent_duplicate_runs(hatchet: Hatchet) -> None: + ref1 = await idempotent_task.aio_run( + input=IdempotencyInput(id="123"), wait_for_result=False + ) + ref2 = await idempotent_task.aio_run( + input=IdempotencyInput(id="123"), wait_for_result=False + ) + + print(ref1, ref2) + + assert False diff --git a/sdks/python/examples/idempotency/worker.py b/sdks/python/examples/idempotency/worker.py index 5e815d77ee..54e69e5a6d 100644 --- a/sdks/python/examples/idempotency/worker.py +++ b/sdks/python/examples/idempotency/worker.py @@ -12,6 +12,7 @@ class IdempotencyInput(BaseModel): @hatchet.task( idempotency=IdempotencyConfig(key_expression="input.id", ttl=timedelta(minutes=1)), input_validator=IdempotencyInput, + on_events=["idempotency:example"], ) async def idempotent_task(input: IdempotencyInput, ctx: Context) -> dict[str, str]: return {"result": f"Hello, world from task {input.id}"} diff --git a/sdks/python/examples/worker.py b/sdks/python/examples/worker.py index 14cd4dbeaa..3877df3d46 100644 --- a/sdks/python/examples/worker.py +++ b/sdks/python/examples/worker.py @@ -101,6 +101,7 @@ durable_parent_child_key_bug, child_child_key_bug, ) +from examples.idempotency.worker import idempotent_task from hatchet_sdk import Hatchet hatchet = Hatchet() @@ -195,6 +196,7 @@ def main() -> None: child_child_key_bug, durable_child_key_dedup_replay, durable_spawn_many_dags, + idempotent_task, ], lifespan=lifespan, ) From d079090e91c432f32ce65a46dce9ae959ceb0e34 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Fri, 29 May 2026 16:15:44 -0400 Subject: [PATCH 19/69] chore: rm unused deps --- sdks/python/examples/idempotency/test_idempotency.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/sdks/python/examples/idempotency/test_idempotency.py b/sdks/python/examples/idempotency/test_idempotency.py index be267ac0c3..4ba322333a 100644 --- a/sdks/python/examples/idempotency/test_idempotency.py +++ b/sdks/python/examples/idempotency/test_idempotency.py @@ -1,14 +1,9 @@ -import asyncio - import pytest from examples.idempotency.worker import idempotent_task, IdempotencyInput from hatchet_sdk import Hatchet -from hatchet_sdk.clients.rest.models.v1_task_event_type import V1TaskEventType -from hatchet_sdk.clients.rest.models.v1_workflow_run_details import V1WorkflowRunDetails -from hatchet_sdk.exceptions import FailedTaskRunExceptionGroup @pytest.mark.asyncio(loop_scope="session") From 67f34f80bf44e23a754272577625a3a75f934aea Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Mon, 1 Jun 2026 15:33:14 -0400 Subject: [PATCH 20/69] fix: use the v1 trigger endpoint on python --- sdks/python/hatchet_sdk/clients/admin.py | 112 ++++++++++++------ sdks/python/hatchet_sdk/runnables/workflow.py | 20 ++-- 2 files changed, 84 insertions(+), 48 deletions(-) diff --git a/sdks/python/hatchet_sdk/clients/admin.py b/sdks/python/hatchet_sdk/clients/admin.py index 64aa599712..21d90588d2 100644 --- a/sdks/python/hatchet_sdk/clients/admin.py +++ b/sdks/python/hatchet_sdk/clients/admin.py @@ -151,6 +151,13 @@ def _get_or_create_v0_client(self) -> WorkflowServiceStub: return self.v0_client + def _get_or_create_v1_client(self) -> AdminServiceStub: + if self.client is None: + conn = new_conn(self.config, False) + self.client = AdminServiceStub(conn) + + return self.client + class TriggerWorkflowRequest(BaseModel): model_config = ConfigDict(extra="ignore") @@ -183,7 +190,7 @@ def _prepare_workflow_request( workflow_name: str, input: str | None, options: TriggerWorkflowOptions, - ) -> trigger_protos.TriggerWorkflowRequest: + ) -> workflow_protos.TriggerWorkflowRunRequest: _options = self.TriggerWorkflowRequest.model_validate(options.model_dump()) desired_worker_labels = None @@ -208,15 +215,12 @@ def _prepare_workflow_request( for key, d in labels_dict.items() } - return trigger_protos.TriggerWorkflowRequest( - name=workflow_name, - input=input, - parent_id=_options.parent_id, - parent_task_run_external_id=_options.parent_step_run_id, - child_index=_options.child_index, - child_key=_options.child_key, - additional_metadata=_options.additional_metadata, - desired_worker_id=_options.desired_worker_id, + return workflow_protos.TriggerWorkflowRunRequest( + workflow_name=workflow_name, + input=input.encode("utf-8") if input else None, + additional_metadata=_options.additional_metadata.encode("utf-8") + if _options.additional_metadata + else None, priority=_options.priority, desired_worker_labels=desired_worker_labels, ) @@ -289,15 +293,8 @@ def put_workflow( self, workflow: workflow_protos.CreateWorkflowVersionRequest, ) -> workflow_protos.CreateWorkflowVersionResponse: - if self.client is None: - conn = new_conn(self.config, False) - self.client = AdminServiceStub(conn) - - put_workflow = tenacity_retry(self.client.PutWorkflow, self.config.tenacity) - # print( - # "Putting workflow with idempotency key expression:", - # workflow.idempotency_key_expr, - # ) + client = self._get_or_create_v1_client() + put_workflow = tenacity_retry(client.PutWorkflow, self.config.tenacity) return cast( workflow_protos.CreateWorkflowVersionResponse, put_workflow( @@ -405,7 +402,44 @@ def _create_workflow_run_request( workflow_name = self.config.apply_namespace(workflow_name, namespace) - return self._prepare_workflow_request(workflow_name, input, trigger_options) + _options = self.TriggerWorkflowRequest.model_validate( + trigger_options.model_dump() + ) + + desired_worker_labels = None + if _options.desired_worker_label: + _warn_if_dict_desired_worker_labels( + _options.desired_worker_label, stacklevel=6 + ) + if isinstance(_options.desired_worker_label, list): + labels_dict = { + d.key: d for d in _options.desired_worker_label if d.key is not None + } + else: + labels_dict = _options.desired_worker_label + desired_worker_labels = { + key: trigger_protos.DesiredWorkerLabels( + str_value=d.value if not isinstance(d.value, int) else None, + int_value=d.value if isinstance(d.value, int) else None, + required=d.required, + weight=d.weight, + comparator=d.comparator, # type: ignore[arg-type] + ) + for key, d in labels_dict.items() + } + + return trigger_protos.TriggerWorkflowRequest( + name=workflow_name, + input=input, + parent_id=_options.parent_id, + parent_task_run_external_id=_options.parent_step_run_id, + child_index=_options.child_index, + child_key=_options.child_key, + additional_metadata=_options.additional_metadata, + desired_worker_id=_options.desired_worker_id, + priority=_options.priority, + desired_worker_labels=desired_worker_labels, + ) def run_workflow( self, @@ -413,13 +447,17 @@ def run_workflow( input: str | None, options: TriggerWorkflowOptions = TriggerWorkflowOptions(), ) -> WorkflowRunRef: - request = self._create_workflow_run_request(workflow_name, input, options) - client = self._get_or_create_v0_client() - trigger_workflow = tenacity_retry(client.TriggerWorkflow, self.config.tenacity) + namespace = options.namespace or self.namespace + namespaced_name = self.config.apply_namespace(workflow_name, namespace) + request = self._prepare_workflow_request(namespaced_name, input, options) + client = self._get_or_create_v1_client() + trigger_workflow = tenacity_retry( + client.TriggerWorkflowRun, self.config.tenacity + ) try: resp = cast( - v0_workflow_protos.TriggerWorkflowResponse, + workflow_protos.TriggerWorkflowRunResponse, trigger_workflow( request, metadata=create_authorization_header(self.token), @@ -431,7 +469,7 @@ def run_workflow( raise e return WorkflowRunRef( - workflow_run_id=resp.workflow_run_id, + workflow_run_id=resp.external_id, workflow_run_event_listener=self.workflow_run_event_listener, workflow_run_listener=self.workflow_run_listener, admin_client=self, @@ -443,14 +481,17 @@ async def aio_run_workflow( input: str | None, options: TriggerWorkflowOptions = TriggerWorkflowOptions(), ) -> WorkflowRunRef: - client = self._get_or_create_v0_client() - trigger_workflow = tenacity_retry(client.TriggerWorkflow, self.config.tenacity) - async with spawn_index_lock: - request = self._create_workflow_run_request(workflow_name, input, options) + namespace = options.namespace or self.namespace + namespaced_name = self.config.apply_namespace(workflow_name, namespace) + client = self._get_or_create_v1_client() + trigger_workflow = tenacity_retry( + client.TriggerWorkflowRun, self.config.tenacity + ) + request = self._prepare_workflow_request(namespaced_name, input, options) try: resp = cast( - v0_workflow_protos.TriggerWorkflowResponse, + workflow_protos.TriggerWorkflowRunResponse, await asyncio.to_thread( trigger_workflow, request, @@ -464,7 +505,7 @@ async def aio_run_workflow( raise e return WorkflowRunRef( - workflow_run_id=resp.workflow_run_id, + workflow_run_id=resp.external_id, workflow_run_event_listener=self.workflow_run_event_listener, workflow_run_listener=self.workflow_run_listener, admin_client=self, @@ -574,13 +615,8 @@ def get_workflow_run(self, workflow_run_id: str) -> WorkflowRunRef: ) def get_details(self, external_id: str) -> WorkflowRunDetail: - if self.client is None: - conn = new_conn(self.config, False) - self.client = AdminServiceStub(conn) - - get_run_payloads = tenacity_retry( - self.client.GetRunDetails, self.config.tenacity - ) + client = self._get_or_create_v1_client() + get_run_payloads = tenacity_retry(client.GetRunDetails, self.config.tenacity) response = cast( workflow_protos.GetRunDetailsResponse, diff --git a/sdks/python/hatchet_sdk/runnables/workflow.py b/sdks/python/hatchet_sdk/runnables/workflow.py index bd9e557a69..5118275549 100644 --- a/sdks/python/hatchet_sdk/runnables/workflow.py +++ b/sdks/python/hatchet_sdk/runnables/workflow.py @@ -406,7 +406,7 @@ def create_bulk_run_item( """ return WorkflowRunTriggerConfig( workflow_name=self._config.name, - input=self._serialize_input(input, target="string"), + input=self._serialize_input(input, target="bytes"), options=self._create_trigger_run_options_with_combined_additional_meta( options, child_key=child_key, @@ -419,7 +419,7 @@ def create_bulk_run_item( key=key, ) - def _serialize_input_to_str(self, input: TWorkflowInput | None) -> str | None: + def _serialize_input_to_bytes(self, input: TWorkflowInput | None) -> str | None: return self._config.input_validator.dump_json( input, # type: ignore[arg-type] context=HATCHET_PYDANTIC_SENTINEL, @@ -439,7 +439,7 @@ def _serialize_input_to_dict( @overload def _serialize_input( - self, input: TWorkflowInput | None, target: Literal["string"] = "string" + self, input: TWorkflowInput | None, target: Literal["bytes"] = "bytes" ) -> str | None: ... @overload @@ -450,13 +450,13 @@ def _serialize_input( def _serialize_input( self, input: TWorkflowInput | None, - target: Literal["string"] | Literal["dict"] = "string", + target: Literal["bytes"] | Literal["dict"] = "bytes", ) -> JSONSerializableMapping | str | None: if not input: return None - if target == "string": - return self._serialize_input_to_str(input) + if target == "bytes": + return self._serialize_input_to_bytes(input) if target == "dict": return self._serialize_input_to_dict(input) @@ -643,7 +643,7 @@ def schedule( return self._client._client.admin.schedule_workflow( name=self._config.name, schedules=[run_at], - input=self._serialize_input(input, target="string"), + input=self._serialize_input(input, target="bytes"), options=opts, ) @@ -970,7 +970,7 @@ def run( ref = self._client._client.admin.run_workflow( workflow_name=self._config.name, - input=self._serialize_input(input, target="string"), + input=self._serialize_input(input, target="bytes"), options=self._create_trigger_run_options_with_combined_additional_meta( options, child_key=child_key, @@ -1108,7 +1108,7 @@ async def aio_run( if durable_ctx is not None and durable_ctx._supports_durable_eviction: config = WorkflowRunTriggerConfig( workflow_name=self._config.name, - input=self._serialize_input(input, target="string"), + input=self._serialize_input(input, target="bytes"), options=opts, ) durable_spawn_results = await durable_ctx._spawn_children_no_wait([config]) @@ -1132,7 +1132,7 @@ async def aio_run( ref = await self._client._client.admin.aio_run_workflow( workflow_name=self._config.name, - input=self._serialize_input(input, target="string"), + input=self._serialize_input(input, target="bytes"), options=opts, ) From ecd2b6c6dac4d4e6ca696aa2c5e1bc6eeec4c615 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Mon, 1 Jun 2026 15:34:58 -0400 Subject: [PATCH 21/69] fix: make the python test do a real thing --- .../examples/idempotency/test_idempotency.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/sdks/python/examples/idempotency/test_idempotency.py b/sdks/python/examples/idempotency/test_idempotency.py index 4ba322333a..4d00989f6d 100644 --- a/sdks/python/examples/idempotency/test_idempotency.py +++ b/sdks/python/examples/idempotency/test_idempotency.py @@ -2,8 +2,8 @@ from examples.idempotency.worker import idempotent_task, IdempotencyInput - from hatchet_sdk import Hatchet +from hatchet_sdk.exceptions import DedupeViolationError @pytest.mark.asyncio(loop_scope="session") @@ -11,10 +11,12 @@ async def test_idempotency_keys_prevent_duplicate_runs(hatchet: Hatchet) -> None ref1 = await idempotent_task.aio_run( input=IdempotencyInput(id="123"), wait_for_result=False ) - ref2 = await idempotent_task.aio_run( - input=IdempotencyInput(id="123"), wait_for_result=False - ) - print(ref1, ref2) + assert ref1 is not None + + with pytest.raises(DedupeViolationError) as exc_info: + await idempotent_task.aio_run( + input=IdempotencyInput(id="123"), wait_for_result=False + ) - assert False + assert str(exc_info.value) == ref1.workflow_run_id From 4e7aff9b8008f116c813599ce10e26751934375c Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Mon, 1 Jun 2026 16:15:39 -0400 Subject: [PATCH 22/69] feat: first pass bypassing mq fallback for idempotency --- internal/services/admin/server_v1.go | 4 +- internal/services/admin/v1/server.go | 95 +++++++------ .../services/controllers/task/controller.go | 3 +- .../controllers/task/trigger/trigger.go | 12 +- internal/services/scheduler/v1/optimistic.go | 8 +- .../services/ticker/schedule_workflow_v1.go | 15 +- pkg/repository/durable_events.go | 2 +- pkg/repository/idempotency.go | 22 +++ pkg/repository/ids.go | 3 + pkg/repository/scheduler.go | 2 +- pkg/repository/scheduler_optimistic.go | 12 +- pkg/repository/trigger.go | 130 +++++++++++++----- pkg/scheduling/v1/pool.go | 8 +- pkg/scheduling/v1/tenant_manager.go | 14 +- 14 files changed, 222 insertions(+), 108 deletions(-) diff --git a/internal/services/admin/server_v1.go b/internal/services/admin/server_v1.go index 7a38a46eba..6376e9c725 100644 --- a/internal/services/admin/server_v1.go +++ b/internal/services/admin/server_v1.go @@ -254,7 +254,7 @@ func (i *AdminServiceImpl) ingest(ctx context.Context, tenantId uuid.UUID, opts localWorkerIds = i.localDispatcher.GetLocalWorkerIds() } - localAssigned, schedulingErr := i.localScheduler.RunOptimisticScheduling(ctx, tenantId, opts, localWorkerIds) + localAssigned, _, schedulingErr := i.localScheduler.RunOptimisticScheduling(ctx, tenantId, opts, localWorkerIds) // if we have a scheduling error, we'll fall back to normal ingestion if schedulingErr != nil { @@ -294,7 +294,7 @@ func (i *AdminServiceImpl) ingest(ctx context.Context, tenantId uuid.UUID, opts return nil } } else if i.tw != nil { - triggerErr := i.tw.TriggerFromWorkflowNames(ctx, tenantId, optsToSend) + _, triggerErr := i.tw.TriggerFromWorkflowNames(ctx, tenantId, optsToSend) // if we fail to trigger via gRPC, we fall back to normal ingestion if triggerErr != nil && !errors.Is(triggerErr, trigger.ErrNoTriggerSlots) { diff --git a/internal/services/admin/v1/server.go b/internal/services/admin/v1/server.go index f52b3c1761..88c46a4908 100644 --- a/internal/services/admin/v1/server.go +++ b/internal/services/admin/v1/server.go @@ -15,7 +15,6 @@ import ( "github.com/hatchet-dev/hatchet/internal/listutils" "github.com/hatchet-dev/hatchet/internal/msgqueue" - "github.com/hatchet-dev/hatchet/internal/services/controllers/task/trigger" contracts "github.com/hatchet-dev/hatchet/internal/services/shared/proto/v1" tasktypes "github.com/hatchet-dev/hatchet/internal/services/shared/tasktypes/v1" "github.com/hatchet-dev/hatchet/internal/statusutils" @@ -421,7 +420,7 @@ func (a *AdminServiceImpl) TriggerWorkflowRun(ctx context.Context, req *contract return nil, fmt.Errorf("could not generate external ids: %w", err) } - err = a.ingest( + idempotencyKeyCollisions, err := a.ingest( ctx, tenantId, opt, @@ -431,6 +430,13 @@ func (a *AdminServiceImpl) TriggerWorkflowRun(ctx context.Context, req *contract return nil, err } + for _, collision := range idempotencyKeyCollisions { + // todo: can make this return the original external id instead? + if collision.RequestedExternalId == opt.ExternalId { + return nil, status.Error(codes.AlreadyExists, collision.ExistingExternalId.String()) + } + } + return &contracts.TriggerWorkflowRunResponse{ ExternalId: opt.ExternalId.String(), }, nil @@ -587,7 +593,7 @@ func (a *AdminServiceImpl) generateExternalIds(ctx context.Context, tenantId uui return a.repo.Triggers().PopulateExternalIdsForWorkflow(ctx, tenantId, opts) } -func (a *AdminServiceImpl) ingest(ctx context.Context, tenantId uuid.UUID, opts ...*v1.WorkflowNameTriggerOpts) error { +func (a *AdminServiceImpl) ingest(ctx context.Context, tenantId uuid.UUID, opts ...*v1.WorkflowNameTriggerOpts) ([]v1.IdempotencyCollision, error) { optsToSend := make([]*v1.WorkflowNameTriggerOpts, 0) for _, opt := range opts { @@ -599,7 +605,7 @@ func (a *AdminServiceImpl) ingest(ctx context.Context, tenantId uuid.UUID, opts } if len(optsToSend) == 0 { - return nil + return nil, nil } if a.localScheduler != nil { @@ -609,9 +615,8 @@ func (a *AdminServiceImpl) ingest(ctx context.Context, tenantId uuid.UUID, opts localWorkerIds = a.localDispatcher.GetLocalWorkerIds() } - localAssigned, schedulingErr := a.localScheduler.RunOptimisticScheduling(ctx, tenantId, opts, localWorkerIds) + localAssigned, idempotencyKeyCollisions, schedulingErr := a.localScheduler.RunOptimisticScheduling(ctx, tenantId, opts, localWorkerIds) - // if we have a scheduling error, we'll fall back to normal ingestion if schedulingErr != nil { if !errors.Is(schedulingErr, schedulingv1.ErrTenantNotFound) && !errors.Is(schedulingErr, schedulingv1.ErrNoOptimisticSlots) { a.l.Error().Ctx(ctx).Err(schedulingErr).Msg("could not run optimistic scheduling") @@ -639,57 +644,69 @@ func (a *AdminServiceImpl) ingest(ctx context.Context, tenantId uuid.UUID, opts a.l.Error().Ctx(ctx).Err(dispatcherErr).Msg("could not handle local assignments") } - // we return nil because the failed assignments would have been requeued by the local dispatcher, - // and we have already written the tasks to the database - return nil + return idempotencyKeyCollisions, nil } - // if there's no scheduling error, we return here because the tasks have been scheduled optimistically if schedulingErr == nil { - return nil - } - } else if a.tw != nil { - triggerErr := a.tw.TriggerFromWorkflowNames(ctx, tenantId, optsToSend) - - // if we fail to trigger via gRPC, we fall back to normal ingestion - if triggerErr != nil && !errors.Is(triggerErr, trigger.ErrNoTriggerSlots) { - a.l.Error().Ctx(ctx).Err(triggerErr).Msg("could not trigger workflow runs via gRPC") - } else if triggerErr == nil { - return nil + return idempotencyKeyCollisions, nil } } - verifyErr := a.repo.Triggers().PreflightVerifyWorkflowNameOpts(ctx, tenantId, optsToSend) - - if verifyErr != nil { + if err := a.repo.Triggers().PopulateWorkflowIdempotencyPresence(ctx, tenantId, optsToSend); err != nil { namesNotFound := &v1.ErrNamesNotFound{} - if errors.As(verifyErr, &namesNotFound) { - return status.Error( - codes.InvalidArgument, - verifyErr.Error(), - ) + if errors.As(err, &namesNotFound) { + return nil, status.Error(codes.InvalidArgument, err.Error()) } - return fmt.Errorf("could not verify workflow name opts: %w", verifyErr) + return nil, fmt.Errorf("could not populate workflow info: %w", err) } - msg, err := tasktypes.TriggerTaskMessage( - tenantId, - optsToSend..., - ) + // important: if there's an idempotency key, we can't fall back to the MQ as we'd lose + // any tx-safety we get if we did, so we call `TriggerFromWorkflowNames` directly for those, + // and only fall back to the MQ for opts without idempotency keys + optsWithIdempotencyKeys := make([]*v1.WorkflowNameTriggerOpts, 0, len(optsToSend)) + optsWithoutIdempotencyKeys := make([]*v1.WorkflowNameTriggerOpts, 0, len(optsToSend)) - if err != nil { - return fmt.Errorf("could not create event task: %w", err) + for _, opt := range optsToSend { + if opt.HasIdempotencyKey { + optsWithIdempotencyKeys = append(optsWithIdempotencyKeys, opt) + } else { + optsWithoutIdempotencyKeys = append(optsWithoutIdempotencyKeys, opt) + } } - err = a.mq.SendMessage(ctx, msgqueue.TASK_PROCESSING_QUEUE, msg) + var allIdempotencyKeyCollisions []v1.IdempotencyCollision - if err != nil { - return fmt.Errorf("could not add event to task queue: %w", err) + if len(optsWithIdempotencyKeys) > 0 { + tasks, dags, idempotencyKeyCollisions, err := a.repo.Triggers().TriggerFromWorkflowNames(ctx, tenantId, optsWithIdempotencyKeys) + + if err != nil { + return nil, fmt.Errorf("could not trigger workflows: %w", err) + } + + if a.tw != nil { + if signalErr := a.tw.SignalCreated(ctx, tenantId, tasks, dags); signalErr != nil { + a.l.Error().Ctx(ctx).Err(signalErr).Msg("failed to signal created tasks and DAGs") + } + } + + allIdempotencyKeyCollisions = append(allIdempotencyKeyCollisions, idempotencyKeyCollisions...) + } + + if len(optsWithoutIdempotencyKeys) > 0 { + msg, err := tasktypes.TriggerTaskMessage(tenantId, optsWithoutIdempotencyKeys...) + + if err != nil { + return nil, fmt.Errorf("could not create trigger message: %w", err) + } + + if err := a.mq.SendMessage(ctx, msgqueue.TASK_PROCESSING_QUEUE, msg); err != nil { + return nil, fmt.Errorf("could not send trigger message: %w", err) + } } - return nil + return allIdempotencyKeyCollisions, nil } func (a *AdminServiceImpl) PutWorkflow(ctx context.Context, req *contracts.CreateWorkflowVersionRequest) (*contracts.CreateWorkflowVersionResponse, error) { diff --git a/internal/services/controllers/task/controller.go b/internal/services/controllers/task/controller.go index 4fad5527ff..3937aea4ef 100644 --- a/internal/services/controllers/task/controller.go +++ b/internal/services/controllers/task/controller.go @@ -1071,7 +1071,8 @@ func (tc *TasksControllerImpl) handleProcessInternalEvents(ctx context.Context, // handleProcessEventTrigger is responsible for inserting tasks into the database based on event triggers. func (tc *TasksControllerImpl) handleProcessTaskTrigger(ctx context.Context, tenantId uuid.UUID, payloads [][]byte) error { - return tc.tw.TriggerFromWorkflowNames(ctx, tenantId, msgqueue.JSONConvert[v1.WorkflowNameTriggerOpts](payloads)) + _, err := tc.tw.TriggerFromWorkflowNames(ctx, tenantId, msgqueue.JSONConvert[v1.WorkflowNameTriggerOpts](payloads)) + return err } // processUserEventMatches looks for user event matches diff --git a/internal/services/controllers/task/trigger/trigger.go b/internal/services/controllers/task/trigger/trigger.go index e17cd8538f..8210310528 100644 --- a/internal/services/controllers/task/trigger/trigger.go +++ b/internal/services/controllers/task/trigger/trigger.go @@ -110,7 +110,7 @@ func (tw *TriggerWriter) TriggerFromEvents(ctx context.Context, tenantId uuid.UU return nil } -func (tw *TriggerWriter) TriggerFromWorkflowNames(ctx context.Context, tenantId uuid.UUID, opts []*v1.WorkflowNameTriggerOpts) error { +func (tw *TriggerWriter) TriggerFromWorkflowNames(ctx context.Context, tenantId uuid.UUID, opts []*v1.WorkflowNameTriggerOpts) ([]v1.IdempotencyCollision, error) { // attempt to acquire a slot in the semaphore if tw.semaphore != nil { select { @@ -121,20 +121,20 @@ func (tw *TriggerWriter) TriggerFromWorkflowNames(ctx context.Context, tenantId }() default: // no slots available - return ErrNoTriggerSlots + return nil, ErrNoTriggerSlots } } - tasks, dags, err := tw.repo.Triggers().TriggerFromWorkflowNames(ctx, tenantId, opts) + tasks, dags, idempotencyKeyCollisions, err := tw.repo.Triggers().TriggerFromWorkflowNames(ctx, tenantId, opts) if err != nil { if errors.Is(err, v1.ErrResourceExhausted) { tw.l.Warn().Ctx(ctx).Str("tenantId", tenantId.String()).Msg("resource exhausted while calling TriggerFromWorkflowNames. Not retrying") - return nil + return nil, nil } - return fmt.Errorf("could not trigger workflows from names: %w", err) + return nil, fmt.Errorf("could not trigger workflows from names: %w", err) } // signaling errors do not result in a failure, since we have already written the tasks to the database, but @@ -144,7 +144,7 @@ func (tw *TriggerWriter) TriggerFromWorkflowNames(ctx context.Context, tenantId tw.l.Error().Ctx(ctx).Err(err).Msg("failed to signal created tasks and DAGs in TriggerFromWorkflowNames") } - return nil + return idempotencyKeyCollisions, nil } func (tw *TriggerWriter) SignalCreated(ctx context.Context, tenantId uuid.UUID, tasks []*v1.V1TaskWithPayload, dags []*v1.DAGWithData) error { diff --git a/internal/services/scheduler/v1/optimistic.go b/internal/services/scheduler/v1/optimistic.go index 102e1d0d3f..afc371d73c 100644 --- a/internal/services/scheduler/v1/optimistic.go +++ b/internal/services/scheduler/v1/optimistic.go @@ -11,11 +11,11 @@ import ( schedulingv1 "github.com/hatchet-dev/hatchet/pkg/scheduling/v1" ) -func (s *Scheduler) RunOptimisticScheduling(ctx context.Context, tenantId uuid.UUID, opts []*v1.WorkflowNameTriggerOpts, localWorkerIds map[uuid.UUID]struct{}) (map[uuid.UUID][]*schedulingv1.AssignedItemWithTask, error) { - localTasks, tasks, dags, err := s.pool.RunOptimisticScheduling(ctx, tenantId, opts, localWorkerIds) +func (s *Scheduler) RunOptimisticScheduling(ctx context.Context, tenantId uuid.UUID, opts []*v1.WorkflowNameTriggerOpts, localWorkerIds map[uuid.UUID]struct{}) (map[uuid.UUID][]*schedulingv1.AssignedItemWithTask, []v1.IdempotencyCollision, error) { + localTasks, tasks, dags, idempotencyKeyCollisions, err := s.pool.RunOptimisticScheduling(ctx, tenantId, opts, localWorkerIds) if err != nil { - return nil, err + return nil, nil, err } go func() { @@ -27,7 +27,7 @@ func (s *Scheduler) RunOptimisticScheduling(ctx context.Context, tenantId uuid.U } }() - return localTasks, err + return localTasks, idempotencyKeyCollisions, err } func (s *Scheduler) RunOptimisticSchedulingFromEvents(ctx context.Context, tenantId uuid.UUID, opts []v1.EventTriggerOpts, localWorkerIds map[uuid.UUID]struct{}) (map[uuid.UUID][]*schedulingv1.AssignedItemWithTask, error) { diff --git a/internal/services/ticker/schedule_workflow_v1.go b/internal/services/ticker/schedule_workflow_v1.go index 818434d9f1..19fb487e8c 100644 --- a/internal/services/ticker/schedule_workflow_v1.go +++ b/internal/services/ticker/schedule_workflow_v1.go @@ -3,8 +3,10 @@ package ticker import ( "context" "fmt" + "time" "github.com/google/uuid" + "github.com/jackc/pgx/v5/pgtype" "github.com/hatchet-dev/hatchet/internal/msgqueue" tasktypes "github.com/hatchet-dev/hatchet/internal/services/shared/tasktypes/v1" @@ -13,7 +15,18 @@ import ( ) func (t *TickerImpl) runScheduledWorkflowV1(ctx context.Context, tenantId uuid.UUID, workflowVersion *sqlcv1.GetWorkflowVersionForEngineRow, scheduledWorkflowId uuid.UUID, scheduled *sqlcv1.PollScheduledWorkflowsRow) error { - // send workflow run to task controller + expiresAt := pgtype.Timestamptz{Time: scheduled.TriggerAt.Time.Add(30 * time.Second), Valid: true} + + claimed, err := t.repov1.Idempotency().ClaimKey(ctx, tenantId, scheduledWorkflowId.String(), expiresAt, scheduledWorkflowId) + if err != nil { + return fmt.Errorf("could not claim idempotency key for scheduled workflow: %w", err) + } + + if !claimed { + t.l.Info().Ctx(ctx).Msgf("idempotency key for scheduled workflow %s already claimed, skipping", scheduledWorkflowId) + return nil + } + opt := &v1.WorkflowNameTriggerOpts{ TriggerTaskData: &v1.TriggerTaskData{ WorkflowName: workflowVersion.WorkflowName, diff --git a/pkg/repository/durable_events.go b/pkg/repository/durable_events.go index b374c003e9..e931dd7ab6 100644 --- a/pkg/repository/durable_events.go +++ b/pkg/repository/durable_events.go @@ -1242,7 +1242,7 @@ func (r *durableEventsRepository) IngestDurableTaskEvent(ctx context.Context, op } if len(newTriggerOpts) > 0 { - createdTasks, createdDags, triggerErr := r.triggerFromWorkflowNames(ctx, optTx, tenantId, newTriggerOpts) + createdTasks, createdDags, _, triggerErr := r.triggerFromWorkflowNames(ctx, optTx, tenantId, newTriggerOpts) if triggerErr != nil { return nil, fmt.Errorf("failed to trigger workflows: %w", triggerErr) diff --git a/pkg/repository/idempotency.go b/pkg/repository/idempotency.go index 8608a7bae2..5350fc973e 100644 --- a/pkg/repository/idempotency.go +++ b/pkg/repository/idempotency.go @@ -20,6 +20,7 @@ type ClaimIdempotencyKeysOpt struct { type IdempotencyRepository interface { EvictExpiredIdempotencyKeys(context context.Context, tenantId uuid.UUID) error + ClaimKey(ctx context.Context, tenantId uuid.UUID, key string, expiresAt pgtype.Timestamptz, claimedByExternalId uuid.UUID) (bool, error) } type idempotencyRepository struct { @@ -36,6 +37,27 @@ func (r *idempotencyRepository) EvictExpiredIdempotencyKeys(context context.Cont return r.queries.CleanUpExpiredIdempotencyKeys(context, r.pool, tenantId) } +func (r *idempotencyRepository) ClaimKey(ctx context.Context, tenantId uuid.UUID, key string, expiresAt pgtype.Timestamptz, claimedByExternalId uuid.UUID) (bool, error) { + results, err := r.queries.ClaimIdempotencyKeys(ctx, r.pool, sqlcv1.ClaimIdempotencyKeysParams{ + Keys: []string{key}, + Expiresats: []pgtype.Timestamptz{expiresAt}, + Claimedbyexternalids: []uuid.UUID{claimedByExternalId}, + Tenantid: tenantId, + }) + if err != nil { + return false, err + } + if len(results) == 0 { + return false, nil + } + return results[0].WasSuccessfullyClaimed, nil +} + +type IdempotencyCollision struct { + RequestedExternalId uuid.UUID + ExistingExternalId uuid.UUID +} + type KeyClaimantPair struct { IdempotencyKey IdempotencyKey ClaimedByExternalId uuid.UUID diff --git a/pkg/repository/ids.go b/pkg/repository/ids.go index cda9de11a0..b10c94c03e 100644 --- a/pkg/repository/ids.go +++ b/pkg/repository/ids.go @@ -29,6 +29,9 @@ type WorkflowNameTriggerOpts struct { // Whether to skip the creation of the child workflow ShouldSkip bool + + // Whether the workflow has an idempotency key expression configured. + HasIdempotencyKey bool } func (g *WorkflowNameTriggerOpts) childSpawnKey() string { diff --git a/pkg/repository/scheduler.go b/pkg/repository/scheduler.go index 7bb2218ed9..ba5b4cc95a 100644 --- a/pkg/repository/scheduler.go +++ b/pkg/repository/scheduler.go @@ -55,7 +55,7 @@ type OptimisticSchedulingRepository interface { TriggerFromEvents(ctx context.Context, tx *OptimisticTx, tenantId uuid.UUID, opts []EventTriggerOpts) ([]*sqlcv1.V1QueueItem, *TriggerFromEventsResult, error) - TriggerFromNames(ctx context.Context, tx *OptimisticTx, tenantId uuid.UUID, opts []*WorkflowNameTriggerOpts) ([]*sqlcv1.V1QueueItem, []*V1TaskWithPayload, []*DAGWithData, error) + TriggerFromNames(ctx context.Context, tx *OptimisticTx, tenantId uuid.UUID, opts []*WorkflowNameTriggerOpts) ([]*sqlcv1.V1QueueItem, []*V1TaskWithPayload, []*DAGWithData, []IdempotencyCollision, error) MarkQueueItemsProcessed(ctx context.Context, tx *OptimisticTx, tenantId uuid.UUID, r *AssignResults) (succeeded []*AssignedItem, failed []*AssignedItem, err error) } diff --git a/pkg/repository/scheduler_optimistic.go b/pkg/repository/scheduler_optimistic.go index 08ed9b2724..69b00b5fe4 100644 --- a/pkg/repository/scheduler_optimistic.go +++ b/pkg/repository/scheduler_optimistic.go @@ -72,17 +72,17 @@ func (r *optimisticSchedulingRepositoryImpl) TriggerFromEvents(ctx context.Conte return qis, result, nil } -func (r *optimisticSchedulingRepositoryImpl) TriggerFromNames(ctx context.Context, tx *OptimisticTx, tenantId uuid.UUID, opts []*WorkflowNameTriggerOpts) ([]*sqlcv1.V1QueueItem, []*V1TaskWithPayload, []*DAGWithData, error) { +func (r *optimisticSchedulingRepositoryImpl) TriggerFromNames(ctx context.Context, tx *OptimisticTx, tenantId uuid.UUID, opts []*WorkflowNameTriggerOpts) ([]*sqlcv1.V1QueueItem, []*V1TaskWithPayload, []*DAGWithData, []IdempotencyCollision, error) { triggerOpts, err := r.prepareTriggerFromWorkflowNames(ctx, tx.tx, tenantId, opts) if err != nil { - return nil, nil, nil, fmt.Errorf("failed to prepare trigger from workflow names: %w", err) + return nil, nil, nil, nil, fmt.Errorf("failed to prepare trigger from workflow names: %w", err) } - tasks, dags, err := r.triggerWorkflows(ctx, tx, tenantId, triggerOpts, nil) + tasks, dags, idempotencyKeyCollisions, err := r.triggerWorkflows(ctx, tx, tenantId, triggerOpts, nil) if err != nil { - return nil, nil, nil, fmt.Errorf("failed to trigger workflows: %w", err) + return nil, nil, nil, nil, fmt.Errorf("failed to trigger workflows: %w", err) } // get the queue items for the tasks that were created @@ -107,11 +107,11 @@ func (r *optimisticSchedulingRepositoryImpl) TriggerFromNames(ctx context.Contex if errors.Is(err, pgx.ErrNoRows) { qis = []*sqlcv1.V1QueueItem{} } else { - return nil, nil, nil, fmt.Errorf("failed to list queue items for tasks: %w", err) + return nil, nil, nil, nil, fmt.Errorf("failed to list queue items for tasks: %w", err) } } - return qis, tasks, dags, nil + return qis, tasks, dags, idempotencyKeyCollisions, nil } func (r *optimisticSchedulingRepositoryImpl) MarkQueueItemsProcessed(ctx context.Context, tx *OptimisticTx, tenantId uuid.UUID, r2 *AssignResults) (succeeded []*AssignedItem, failed []*AssignedItem, err error) { diff --git a/pkg/repository/trigger.go b/pkg/repository/trigger.go index 0688afdac8..9cd6f263c7 100644 --- a/pkg/repository/trigger.go +++ b/pkg/repository/trigger.go @@ -137,10 +137,12 @@ type createDAGOpts struct { type TriggerRepository interface { TriggerFromEvents(ctx context.Context, tenantId uuid.UUID, opts []EventTriggerOpts) (*TriggerFromEventsResult, error) - TriggerFromWorkflowNames(ctx context.Context, tenantId uuid.UUID, opts []*WorkflowNameTriggerOpts) ([]*V1TaskWithPayload, []*DAGWithData, error) + TriggerFromWorkflowNames(ctx context.Context, tenantId uuid.UUID, opts []*WorkflowNameTriggerOpts) ([]*V1TaskWithPayload, []*DAGWithData, []IdempotencyCollision, error) PopulateExternalIdsForWorkflow(ctx context.Context, tenantId uuid.UUID, opts []*WorkflowNameTriggerOpts) error + PopulateWorkflowIdempotencyPresence(ctx context.Context, tenantId uuid.UUID, opts []*WorkflowNameTriggerOpts) error + PreflightVerifyWorkflowNameOpts(ctx context.Context, tenantId uuid.UUID, opts []*WorkflowNameTriggerOpts) error NewTriggerTaskData(ctx context.Context, tenantId uuid.UUID, req *v1contracts.TriggerWorkflowRequest, parentTask *sqlcv1.FlattenExternalIdsRow) (*TriggerTaskData, error) @@ -310,7 +312,7 @@ func (r *sharedRepository) doTriggerFromEvents( return nil, fmt.Errorf("failed to prepare trigger from events: %w", err) } - tasks, dags, err := r.triggerWorkflows(ctx, tx, tenantId, triggerOpts, createCoreEventOpts) + tasks, dags, _, err := r.triggerWorkflows(ctx, tx, tenantId, triggerOpts, createCoreEventOpts) if err != nil { return nil, fmt.Errorf("failed to trigger workflows: %w", err) @@ -370,36 +372,36 @@ func getEventExternalIdToRuns(opts []EventTriggerOpts, externalIdToEventIdAndFil return eventExternalIdToRuns } -func (s *sharedRepository) triggerFromWorkflowNames(ctx context.Context, tx *OptimisticTx, tenantId uuid.UUID, opts []*WorkflowNameTriggerOpts) ([]*V1TaskWithPayload, []*DAGWithData, error) { +func (s *sharedRepository) triggerFromWorkflowNames(ctx context.Context, tx *OptimisticTx, tenantId uuid.UUID, opts []*WorkflowNameTriggerOpts) ([]*V1TaskWithPayload, []*DAGWithData, []IdempotencyCollision, error) { triggerOpts, err := s.prepareTriggerFromWorkflowNames(ctx, tx.tx, tenantId, opts) if err != nil { - return nil, nil, fmt.Errorf("failed to prepare trigger from workflow names: %w", err) + return nil, nil, nil, fmt.Errorf("failed to prepare trigger from workflow names: %w", err) } return s.triggerWorkflows(ctx, tx, tenantId, triggerOpts, nil) } -func (r *TriggerRepositoryImpl) TriggerFromWorkflowNames(ctx context.Context, tenantId uuid.UUID, opts []*WorkflowNameTriggerOpts) ([]*V1TaskWithPayload, []*DAGWithData, error) { +func (r *TriggerRepositoryImpl) TriggerFromWorkflowNames(ctx context.Context, tenantId uuid.UUID, opts []*WorkflowNameTriggerOpts) ([]*V1TaskWithPayload, []*DAGWithData, []IdempotencyCollision, error) { tx, err := r.PrepareOptimisticTx(ctx) if err != nil { - return nil, nil, fmt.Errorf("failed to prepare tx: %w", err) + return nil, nil, nil, fmt.Errorf("failed to prepare tx: %w", err) } defer tx.Rollback() - tasks, dags, err := r.triggerFromWorkflowNames(ctx, tx, tenantId, opts) + tasks, dags, idempotencyKeyCollisions, err := r.triggerFromWorkflowNames(ctx, tx, tenantId, opts) if err != nil { - return nil, nil, err + return nil, nil, nil, err } if err := tx.Commit(ctx); err != nil { - return nil, nil, err + return nil, nil, nil, err } - return tasks, dags, nil + return tasks, dags, idempotencyKeyCollisions, nil } type ErrNamesNotFound struct { @@ -451,6 +453,48 @@ func (r *TriggerRepositoryImpl) PreflightVerifyWorkflowNameOpts(ctx context.Cont return nil } +func (r *TriggerRepositoryImpl) PopulateWorkflowIdempotencyPresence(ctx context.Context, tenantId uuid.UUID, opts []*WorkflowNameTriggerOpts) error { + if len(opts) == 0 { + return nil + } + + uniqueNames := make(map[string]struct{}) + names := make([]string, 0, len(uniqueNames)) + + for _, opt := range opts { + if _, ok := uniqueNames[opt.WorkflowName]; !ok { + names = append(names, opt.WorkflowName) + } + + uniqueNames[opt.WorkflowName] = struct{}{} + } + + rows, err := r.listWorkflowsByNames(ctx, r.pool, tenantId, names) + + if err != nil { + return fmt.Errorf("failed to list workflows by names: %w", err) + } + + nameToRow := make(map[string]*sqlcv1.ListWorkflowsByNamesRow, len(rows)) + + for _, row := range rows { + nameToRow[row.WorkflowName] = row + } + + for _, opt := range opts { + row, ok := nameToRow[opt.WorkflowName] + + if !ok { + r.l.Error().Str("workflowName", opt.WorkflowName).Msg("workflow name not found when populating idempotency presence") + continue + } + + opt.HasIdempotencyKey = row.IdempotencyKeyExpression.Valid && row.IdempotencyKeyTtlMs.Valid + } + + return nil +} + type TriggeredBy interface { ToMetadata([]byte) []byte } @@ -596,14 +640,14 @@ type createCoreUserEventOpts struct { } func (r *sharedRepository) evalIdempotencyKey(tuple triggerTuple) (string, error) { - inputData := make(map[string]interface{}) + inputData := make(map[string]any) if len(tuple.input) > 0 { if err := json.Unmarshal(tuple.input, &inputData); err != nil { return "", fmt.Errorf("failed to unmarshal input for idempotency key evaluation: %w", err) } } - additionalMetadata := make(map[string]interface{}) + additionalMetadata := make(map[string]any) if len(tuple.additionalMetadata) > 0 { if err := json.Unmarshal(tuple.additionalMetadata, &additionalMetadata); err != nil { return "", fmt.Errorf("failed to unmarshal additional metadata for idempotency key evaluation: %w", err) @@ -630,7 +674,7 @@ func (r *sharedRepository) triggerWorkflows( tenantId uuid.UUID, triggerCandidateTuples []triggerTuple, coreEvents *createCoreUserEventOpts, -) ([]*V1TaskWithPayload, []*DAGWithData, error) { +) ([]*V1TaskWithPayload, []*DAGWithData, []IdempotencyCollision, error) { var preflightTx sqlcv1.DBTX = r.pool if existingTx != nil { @@ -651,7 +695,7 @@ func (r *sharedRepository) triggerWorkflows( externalIdToTuple[tuple.externalId] = tuple key, err := r.evalIdempotencyKey(tuple) if err != nil { - return nil, nil, err + return nil, nil, nil, err } keys = append(keys, key) @@ -663,8 +707,9 @@ func (r *sharedRepository) triggerWorkflows( } } + var idempotencyKeyCollisions []IdempotencyCollision + if len(keys) > 0 { - // todo: need to use tx here, find the right one (this might be nil) claims, err := r.queries.ClaimIdempotencyKeys(ctx, existingTx.tx, sqlcv1.ClaimIdempotencyKeysParams{ Keys: keys, Expiresats: expiresAts, @@ -673,22 +718,35 @@ func (r *sharedRepository) triggerWorkflows( }) if err != nil { - return nil, nil, fmt.Errorf("failed to claim idempotency keys: %w", err) + return nil, nil, nil, fmt.Errorf("failed to claim idempotency keys: %w", err) } + idempotencyKeyToLockHolder := make(map[string]uuid.UUID, len(claims)) for _, claim := range claims { - if !claim.WasSuccessfullyClaimed || claim.ClaimedByExternalID == nil { - continue + if claim.ClaimedByExternalID != nil { + idempotencyKeyToLockHolder[claim.Key] = *claim.ClaimedByExternalID } + } - // todo: this probably should not be a pointer - tuple, ok := externalIdToTuple[*claim.ClaimedByExternalID] - + for i, key := range keys { + requestedId := claimedByExternalIds[i] + lockHolderExternalId, ok := idempotencyKeyToLockHolder[key] if !ok { continue } - tuples = append(tuples, tuple) + if lockHolderExternalId == requestedId { + tuple, ok := externalIdToTuple[requestedId] + if !ok { + continue + } + tuples = append(tuples, tuple) + } else { + idempotencyKeyCollisions = append(idempotencyKeyCollisions, IdempotencyCollision{ + RequestedExternalId: requestedId, + ExistingExternalId: lockHolderExternalId, + }) + } } } @@ -710,7 +768,7 @@ func (r *sharedRepository) triggerWorkflows( workflowVersionToSteps, err := r.listStepsByWorkflowVersionIds(ctx, preflightTx, tenantId, workflowVersionIds) if err != nil { - return nil, nil, fmt.Errorf("failed to get workflow versions for engine: %w", err) + return nil, nil, nil, fmt.Errorf("failed to get workflow versions for engine: %w", err) } // group steps by workflow version ids @@ -745,7 +803,7 @@ func (r *sharedRepository) triggerWorkflows( preTask, postTask := r.m.Meter(ctx, sqlcv1.LimitResourceTASKRUN, tenantId, int32(countTasks)) // nolint: gosec if err := preTask(); err != nil { - return nil, nil, err + return nil, nil, nil, err } stepsToAdditionalMatches := make(map[uuid.UUID][]*sqlcv1.V1StepMatchCondition) @@ -757,7 +815,7 @@ func (r *sharedRepository) triggerWorkflows( }) if err != nil { - return nil, nil, fmt.Errorf("failed to list step match conditions: %w", err) + return nil, nil, nil, fmt.Errorf("failed to list step match conditions: %w", err) } for _, match := range additionalMatches { @@ -838,7 +896,7 @@ func (r *sharedRepository) triggerWorkflows( tx, commit, rollback, err = sqlchelpers.PrepareTx(ctx, r.pool, r.l) if err != nil { - return nil, nil, err + return nil, nil, nil, err } defer rollback() @@ -851,7 +909,7 @@ func (r *sharedRepository) triggerWorkflows( tuplesToSkip, err := r.registerChildWorkflows(ctx, tx, tenantId, tuples, stepsToExternalIds, workflowVersionToSteps) if err != nil { - return nil, nil, fmt.Errorf("failed to register child workflows: %w", err) + return nil, nil, nil, fmt.Errorf("failed to register child workflows: %w", err) } for i, tuple := range tuples { @@ -997,7 +1055,7 @@ func (r *sharedRepository) triggerWorkflows( ) if err != nil { - return nil, nil, fmt.Errorf("failed to create sleep condition: %w", err) + return nil, nil, nil, fmt.Errorf("failed to create sleep condition: %w", err) } groupConditions = append(groupConditions, *c) @@ -1254,7 +1312,7 @@ func (r *sharedRepository) triggerWorkflows( dags, err := r.createDAGs(ctx, tx, tenantId, dagOpts) if err != nil { - return nil, nil, fmt.Errorf("failed to create DAGs: %w", err) + return nil, nil, nil, fmt.Errorf("failed to create DAGs: %w", err) } // populate taskOpts with inserted DAG data @@ -1279,7 +1337,7 @@ func (r *sharedRepository) triggerWorkflows( tasks, err := r.createTasks(ctx, tx, tenantId, createTaskOpts) if err != nil { - return nil, nil, fmt.Errorf("failed to create tasks: %w", err) + return nil, nil, nil, fmt.Errorf("failed to create tasks: %w", err) } for _, dag := range dags { @@ -1296,7 +1354,7 @@ func (r *sharedRepository) triggerWorkflows( err = r.createEventMatches(ctx, tx, tenantId, createMatchOpts) if err != nil { - return nil, nil, fmt.Errorf("failed to create event matches: %w", err) + return nil, nil, nil, fmt.Errorf("failed to create event matches: %w", err) } storePayloadOpts := make([]StorePayloadOpts, 0, len(tasks)) @@ -1329,7 +1387,7 @@ func (r *sharedRepository) triggerWorkflows( createdEvents, err := r.queries.BulkCreateEvents(ctx, tx, coreEvents.params) if err != nil { - return nil, nil, fmt.Errorf("failed to create core events: %w", err) + return nil, nil, nil, fmt.Errorf("failed to create core events: %w", err) } for _, createdEvent := range createdEvents { @@ -1405,7 +1463,7 @@ func (r *sharedRepository) triggerWorkflows( }) if err != nil { - return nil, nil, fmt.Errorf("failed to create event to runs: %w", err) + return nil, nil, nil, fmt.Errorf("failed to create event to runs: %w", err) } for _, e := range createdEvents { @@ -1429,13 +1487,13 @@ func (r *sharedRepository) triggerWorkflows( err = r.payloadStore.Store(ctx, tx, storePayloadOpts...) if err != nil { - return nil, nil, fmt.Errorf("failed to store payloads: %w", err) + return nil, nil, nil, fmt.Errorf("failed to store payloads: %w", err) } // commit if we started the transaction if existingTx == nil { if err := commit(ctx); err != nil { - return nil, nil, err + return nil, nil, nil, err } postTask() @@ -1444,7 +1502,7 @@ func (r *sharedRepository) triggerWorkflows( existingTx.AddPostCommit(postTask) } - return tasks, dags, nil + return tasks, dags, idempotencyKeyCollisions, nil } type DAGWithData struct { diff --git a/pkg/scheduling/v1/pool.go b/pkg/scheduling/v1/pool.go index 189a883e81..bfba017928 100644 --- a/pkg/scheduling/v1/pool.go +++ b/pkg/scheduling/v1/pool.go @@ -236,9 +236,9 @@ func (p *SchedulingPool) getTenantManager(tenantId uuid.UUID, storeIfNotFound bo var ErrTenantNotFound = fmt.Errorf("tenant not found in pool") var ErrNoOptimisticSlots = fmt.Errorf("no optimistic slots for scheduling") -func (p *SchedulingPool) RunOptimisticScheduling(ctx context.Context, tenantId uuid.UUID, opts []*v1.WorkflowNameTriggerOpts, localWorkerIds map[uuid.UUID]struct{}) (map[uuid.UUID][]*AssignedItemWithTask, []*v1.V1TaskWithPayload, []*v1.DAGWithData, error) { +func (p *SchedulingPool) RunOptimisticScheduling(ctx context.Context, tenantId uuid.UUID, opts []*v1.WorkflowNameTriggerOpts, localWorkerIds map[uuid.UUID]struct{}) (map[uuid.UUID][]*AssignedItemWithTask, []*v1.V1TaskWithPayload, []*v1.DAGWithData, []v1.IdempotencyCollision, error) { if !p.optimisticSchedulingEnabled { - return nil, nil, nil, ErrNoOptimisticSlots + return nil, nil, nil, nil, ErrNoOptimisticSlots } // attempt to acquire a slot in the semaphore @@ -250,13 +250,13 @@ func (p *SchedulingPool) RunOptimisticScheduling(ctx context.Context, tenantId u }() default: // no slots available - return nil, nil, nil, ErrNoOptimisticSlots + return nil, nil, nil, nil, ErrNoOptimisticSlots } tm := p.getTenantManager(tenantId, false) if tm == nil { - return nil, nil, nil, ErrTenantNotFound + return nil, nil, nil, nil, ErrTenantNotFound } return tm.runOptimisticScheduling(ctx, opts, localWorkerIds) diff --git a/pkg/scheduling/v1/tenant_manager.go b/pkg/scheduling/v1/tenant_manager.go index 5797412195..e52b144f15 100644 --- a/pkg/scheduling/v1/tenant_manager.go +++ b/pkg/scheduling/v1/tenant_manager.go @@ -416,21 +416,21 @@ func (t *tenantManager) runOptimisticScheduling( ctx context.Context, opts []*v1.WorkflowNameTriggerOpts, localWorkerIds map[uuid.UUID]struct{}, -) (map[uuid.UUID][]*AssignedItemWithTask, []*v1.V1TaskWithPayload, []*v1.DAGWithData, error) { +) (map[uuid.UUID][]*AssignedItemWithTask, []*v1.V1TaskWithPayload, []*v1.DAGWithData, []v1.IdempotencyCollision, error) { // create a transaction tx, err := t.cf.repo.Optimistic().StartTx(ctx) if err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, err } defer tx.Rollback() // hook into the trigger transaction - qis, tasks, dags, err := t.cf.repo.Optimistic().TriggerFromNames(ctx, tx, t.tenantId, opts) + qis, tasks, dags, collisions, err := t.cf.repo.Optimistic().TriggerFromNames(ctx, tx, t.tenantId, opts) if err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, err } // read the queue items for the tasks we just created @@ -456,7 +456,7 @@ func (t *tenantManager) runOptimisticScheduling( if err != nil { t.queuersMu.RUnlock() - return nil, nil, nil, err + return nil, nil, nil, nil, err } allLocalAssigned = append(allLocalAssigned, localAssigned...) @@ -467,7 +467,7 @@ func (t *tenantManager) runOptimisticScheduling( } if err := tx.Commit(ctx); err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, err } for _, qr := range allQueueResults { @@ -504,7 +504,7 @@ func (t *tenantManager) runOptimisticScheduling( }) } - return res, tasks, dags, nil + return res, tasks, dags, collisions, nil } func (t *tenantManager) runOptimisticSchedulingFromEvents( From 222175a8b463f6fcc8af827fa57fc67d9e4589a1 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Mon, 1 Jun 2026 16:19:44 -0400 Subject: [PATCH 23/69] fix: remove todo, return the same id --- internal/services/admin/v1/server.go | 5 +++-- .../examples/idempotency/test_idempotency.py | 14 +++++++++----- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/internal/services/admin/v1/server.go b/internal/services/admin/v1/server.go index 88c46a4908..2cfa1b0502 100644 --- a/internal/services/admin/v1/server.go +++ b/internal/services/admin/v1/server.go @@ -431,9 +431,10 @@ func (a *AdminServiceImpl) TriggerWorkflowRun(ctx context.Context, req *contract } for _, collision := range idempotencyKeyCollisions { - // todo: can make this return the original external id instead? if collision.RequestedExternalId == opt.ExternalId { - return nil, status.Error(codes.AlreadyExists, collision.ExistingExternalId.String()) + return &contracts.TriggerWorkflowRunResponse{ + ExternalId: collision.ExistingExternalId.String(), + }, nil } } diff --git a/sdks/python/examples/idempotency/test_idempotency.py b/sdks/python/examples/idempotency/test_idempotency.py index 4d00989f6d..35f62c9122 100644 --- a/sdks/python/examples/idempotency/test_idempotency.py +++ b/sdks/python/examples/idempotency/test_idempotency.py @@ -11,12 +11,16 @@ async def test_idempotency_keys_prevent_duplicate_runs(hatchet: Hatchet) -> None ref1 = await idempotent_task.aio_run( input=IdempotencyInput(id="123"), wait_for_result=False ) + ref2 = await idempotent_task.aio_run( + input=IdempotencyInput(id="123"), wait_for_result=False + ) assert ref1 is not None + assert ref2 is not None + + assert ref1.workflow_run_id == ref2.workflow_run_id - with pytest.raises(DedupeViolationError) as exc_info: - await idempotent_task.aio_run( - input=IdempotencyInput(id="123"), wait_for_result=False - ) + result1 = await ref1.aio_result() + result2 = await ref2.aio_result() - assert str(exc_info.value) == ref1.workflow_run_id + assert result1 == result2 From 4942ad429751f10efac02c68409c8b79a5665061 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Mon, 1 Jun 2026 17:08:24 -0400 Subject: [PATCH 24/69] feat: wire up existing run ids --- api-contracts/v1/workflows.proto | 3 + internal/services/admin/v1/server.go | 14 +- .../services/shared/proto/v1/workflows.pb.go | 592 ++++++++++-------- pkg/repository/idempotency.go | 45 -- .../examples/idempotency/test_idempotency.py | 18 +- sdks/python/hatchet_sdk/__init__.py | 2 + sdks/python/hatchet_sdk/clients/admin.py | 29 +- .../hatchet_sdk/contracts/v1/workflows_pb2.py | 68 +- .../contracts/v1/workflows_pb2.pyi | 6 + sdks/python/hatchet_sdk/exceptions.py | 6 + sdks/python/poetry.lock | 24 +- sdks/python/pyproject.toml | 1 + sdks/python/uv.lock | 2 +- 13 files changed, 444 insertions(+), 366 deletions(-) diff --git a/api-contracts/v1/workflows.proto b/api-contracts/v1/workflows.proto index 794a8c61cc..3b822e8259 100644 --- a/api-contracts/v1/workflows.proto +++ b/api-contracts/v1/workflows.proto @@ -120,6 +120,9 @@ message IdempotencyConfig { int64 ttl_ms = 2; // time-to-live for idempotency keys in milliseconds } +message IdempotencyCollisionError { + string existing_run_external_id = 1; // the external ID of the existing workflow run that caused the collision +} message DefaultFilter { string expression = 1; // (required) the CEL expression for the filter diff --git a/internal/services/admin/v1/server.go b/internal/services/admin/v1/server.go index 2cfa1b0502..c8f8f91364 100644 --- a/internal/services/admin/v1/server.go +++ b/internal/services/admin/v1/server.go @@ -432,9 +432,17 @@ func (a *AdminServiceImpl) TriggerWorkflowRun(ctx context.Context, req *contract for _, collision := range idempotencyKeyCollisions { if collision.RequestedExternalId == opt.ExternalId { - return &contracts.TriggerWorkflowRunResponse{ - ExternalId: collision.ExistingExternalId.String(), - }, nil + st, err := status.New(codes.AlreadyExists, "idempotency key collision").WithDetails( + &contracts.IdempotencyCollisionError{ + ExistingRunExternalId: collision.ExistingExternalId.String(), + }, + ) + + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to build idempotency collision error: %v", err) + } + + return nil, st.Err() } } diff --git a/internal/services/shared/proto/v1/workflows.pb.go b/internal/services/shared/proto/v1/workflows.pb.go index 889f100fb8..eebd19062e 100644 --- a/internal/services/shared/proto/v1/workflows.pb.go +++ b/internal/services/shared/proto/v1/workflows.pb.go @@ -992,6 +992,53 @@ func (x *IdempotencyConfig) GetTtlMs() int64 { return 0 } +type IdempotencyCollisionError struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ExistingRunExternalId string `protobuf:"bytes,1,opt,name=existing_run_external_id,json=existingRunExternalId,proto3" json:"existing_run_external_id,omitempty"` // the external ID of the existing workflow run that caused the collision +} + +func (x *IdempotencyCollisionError) Reset() { + *x = IdempotencyCollisionError{} + if protoimpl.UnsafeEnabled { + mi := &file_v1_workflows_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *IdempotencyCollisionError) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*IdempotencyCollisionError) ProtoMessage() {} + +func (x *IdempotencyCollisionError) ProtoReflect() protoreflect.Message { + mi := &file_v1_workflows_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use IdempotencyCollisionError.ProtoReflect.Descriptor instead. +func (*IdempotencyCollisionError) Descriptor() ([]byte, []int) { + return file_v1_workflows_proto_rawDescGZIP(), []int{11} +} + +func (x *IdempotencyCollisionError) GetExistingRunExternalId() string { + if x != nil { + return x.ExistingRunExternalId + } + return "" +} + type DefaultFilter struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1005,7 +1052,7 @@ type DefaultFilter struct { func (x *DefaultFilter) Reset() { *x = DefaultFilter{} if protoimpl.UnsafeEnabled { - mi := &file_v1_workflows_proto_msgTypes[11] + mi := &file_v1_workflows_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1018,7 +1065,7 @@ func (x *DefaultFilter) String() string { func (*DefaultFilter) ProtoMessage() {} func (x *DefaultFilter) ProtoReflect() protoreflect.Message { - mi := &file_v1_workflows_proto_msgTypes[11] + mi := &file_v1_workflows_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1031,7 +1078,7 @@ func (x *DefaultFilter) ProtoReflect() protoreflect.Message { // Deprecated: Use DefaultFilter.ProtoReflect.Descriptor instead. func (*DefaultFilter) Descriptor() ([]byte, []int) { - return file_v1_workflows_proto_rawDescGZIP(), []int{11} + return file_v1_workflows_proto_rawDescGZIP(), []int{12} } func (x *DefaultFilter) GetExpression() string { @@ -1068,7 +1115,7 @@ type Concurrency struct { func (x *Concurrency) Reset() { *x = Concurrency{} if protoimpl.UnsafeEnabled { - mi := &file_v1_workflows_proto_msgTypes[12] + mi := &file_v1_workflows_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1081,7 +1128,7 @@ func (x *Concurrency) String() string { func (*Concurrency) ProtoMessage() {} func (x *Concurrency) ProtoReflect() protoreflect.Message { - mi := &file_v1_workflows_proto_msgTypes[12] + mi := &file_v1_workflows_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1094,7 +1141,7 @@ func (x *Concurrency) ProtoReflect() protoreflect.Message { // Deprecated: Use Concurrency.ProtoReflect.Descriptor instead. func (*Concurrency) Descriptor() ([]byte, []int) { - return file_v1_workflows_proto_rawDescGZIP(), []int{12} + return file_v1_workflows_proto_rawDescGZIP(), []int{13} } func (x *Concurrency) GetExpression() string { @@ -1144,7 +1191,7 @@ type CreateTaskOpts struct { func (x *CreateTaskOpts) Reset() { *x = CreateTaskOpts{} if protoimpl.UnsafeEnabled { - mi := &file_v1_workflows_proto_msgTypes[13] + mi := &file_v1_workflows_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1157,7 +1204,7 @@ func (x *CreateTaskOpts) String() string { func (*CreateTaskOpts) ProtoMessage() {} func (x *CreateTaskOpts) ProtoReflect() protoreflect.Message { - mi := &file_v1_workflows_proto_msgTypes[13] + mi := &file_v1_workflows_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1170,7 +1217,7 @@ func (x *CreateTaskOpts) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateTaskOpts.ProtoReflect.Descriptor instead. func (*CreateTaskOpts) Descriptor() ([]byte, []int) { - return file_v1_workflows_proto_rawDescGZIP(), []int{13} + return file_v1_workflows_proto_rawDescGZIP(), []int{14} } func (x *CreateTaskOpts) GetReadableId() string { @@ -1294,7 +1341,7 @@ type CreateTaskRateLimit struct { func (x *CreateTaskRateLimit) Reset() { *x = CreateTaskRateLimit{} if protoimpl.UnsafeEnabled { - mi := &file_v1_workflows_proto_msgTypes[14] + mi := &file_v1_workflows_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1307,7 +1354,7 @@ func (x *CreateTaskRateLimit) String() string { func (*CreateTaskRateLimit) ProtoMessage() {} func (x *CreateTaskRateLimit) ProtoReflect() protoreflect.Message { - mi := &file_v1_workflows_proto_msgTypes[14] + mi := &file_v1_workflows_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1320,7 +1367,7 @@ func (x *CreateTaskRateLimit) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateTaskRateLimit.ProtoReflect.Descriptor instead. func (*CreateTaskRateLimit) Descriptor() ([]byte, []int) { - return file_v1_workflows_proto_rawDescGZIP(), []int{14} + return file_v1_workflows_proto_rawDescGZIP(), []int{15} } func (x *CreateTaskRateLimit) GetKey() string { @@ -1378,7 +1425,7 @@ type CreateWorkflowVersionResponse struct { func (x *CreateWorkflowVersionResponse) Reset() { *x = CreateWorkflowVersionResponse{} if protoimpl.UnsafeEnabled { - mi := &file_v1_workflows_proto_msgTypes[15] + mi := &file_v1_workflows_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1391,7 +1438,7 @@ func (x *CreateWorkflowVersionResponse) String() string { func (*CreateWorkflowVersionResponse) ProtoMessage() {} func (x *CreateWorkflowVersionResponse) ProtoReflect() protoreflect.Message { - mi := &file_v1_workflows_proto_msgTypes[15] + mi := &file_v1_workflows_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1404,7 +1451,7 @@ func (x *CreateWorkflowVersionResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateWorkflowVersionResponse.ProtoReflect.Descriptor instead. func (*CreateWorkflowVersionResponse) Descriptor() ([]byte, []int) { - return file_v1_workflows_proto_rawDescGZIP(), []int{15} + return file_v1_workflows_proto_rawDescGZIP(), []int{16} } func (x *CreateWorkflowVersionResponse) GetId() string { @@ -1432,7 +1479,7 @@ type GetRunDetailsRequest struct { func (x *GetRunDetailsRequest) Reset() { *x = GetRunDetailsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_v1_workflows_proto_msgTypes[16] + mi := &file_v1_workflows_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1445,7 +1492,7 @@ func (x *GetRunDetailsRequest) String() string { func (*GetRunDetailsRequest) ProtoMessage() {} func (x *GetRunDetailsRequest) ProtoReflect() protoreflect.Message { - mi := &file_v1_workflows_proto_msgTypes[16] + mi := &file_v1_workflows_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1458,7 +1505,7 @@ func (x *GetRunDetailsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRunDetailsRequest.ProtoReflect.Descriptor instead. func (*GetRunDetailsRequest) Descriptor() ([]byte, []int) { - return file_v1_workflows_proto_rawDescGZIP(), []int{16} + return file_v1_workflows_proto_rawDescGZIP(), []int{17} } func (x *GetRunDetailsRequest) GetExternalId() string { @@ -1484,7 +1531,7 @@ type TaskRunDetail struct { func (x *TaskRunDetail) Reset() { *x = TaskRunDetail{} if protoimpl.UnsafeEnabled { - mi := &file_v1_workflows_proto_msgTypes[17] + mi := &file_v1_workflows_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1497,7 +1544,7 @@ func (x *TaskRunDetail) String() string { func (*TaskRunDetail) ProtoMessage() {} func (x *TaskRunDetail) ProtoReflect() protoreflect.Message { - mi := &file_v1_workflows_proto_msgTypes[17] + mi := &file_v1_workflows_proto_msgTypes[18] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1510,7 +1557,7 @@ func (x *TaskRunDetail) ProtoReflect() protoreflect.Message { // Deprecated: Use TaskRunDetail.ProtoReflect.Descriptor instead. func (*TaskRunDetail) Descriptor() ([]byte, []int) { - return file_v1_workflows_proto_rawDescGZIP(), []int{17} + return file_v1_workflows_proto_rawDescGZIP(), []int{18} } func (x *TaskRunDetail) GetExternalId() string { @@ -1571,7 +1618,7 @@ type GetRunDetailsResponse struct { func (x *GetRunDetailsResponse) Reset() { *x = GetRunDetailsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_v1_workflows_proto_msgTypes[18] + mi := &file_v1_workflows_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1584,7 +1631,7 @@ func (x *GetRunDetailsResponse) String() string { func (*GetRunDetailsResponse) ProtoMessage() {} func (x *GetRunDetailsResponse) ProtoReflect() protoreflect.Message { - mi := &file_v1_workflows_proto_msgTypes[18] + mi := &file_v1_workflows_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1597,7 +1644,7 @@ func (x *GetRunDetailsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRunDetailsResponse.ProtoReflect.Descriptor instead. func (*GetRunDetailsResponse) Descriptor() ([]byte, []int) { - return file_v1_workflows_proto_rawDescGZIP(), []int{18} + return file_v1_workflows_proto_rawDescGZIP(), []int{19} } func (x *GetRunDetailsResponse) GetInput() []byte { @@ -1788,202 +1835,208 @@ var file_v1_workflows_proto_rawDesc = []byte{ 0x69, 0x67, 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x15, 0x0a, 0x06, 0x74, 0x74, 0x6c, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x05, 0x74, 0x74, 0x6c, 0x4d, 0x73, 0x22, 0x70, 0x0a, 0x0d, 0x44, 0x65, 0x66, - 0x61, 0x75, 0x6c, 0x74, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x78, - 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, - 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, - 0x6f, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, - 0x12, 0x1d, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0c, 0x48, 0x00, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x88, 0x01, 0x01, 0x42, - 0x0a, 0x0a, 0x08, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0xb7, 0x01, 0x0a, 0x0b, - 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x65, - 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0a, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x08, 0x6d, - 0x61, 0x78, 0x5f, 0x72, 0x75, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, - 0x07, 0x6d, 0x61, 0x78, 0x52, 0x75, 0x6e, 0x73, 0x88, 0x01, 0x01, 0x12, 0x48, 0x0a, 0x0e, 0x6c, - 0x69, 0x6d, 0x69, 0x74, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, - 0x65, 0x6e, 0x63, 0x79, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, - 0x79, 0x48, 0x01, 0x52, 0x0d, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, - 0x67, 0x79, 0x88, 0x01, 0x01, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x72, 0x75, - 0x6e, 0x73, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x5f, 0x73, 0x74, 0x72, - 0x61, 0x74, 0x65, 0x67, 0x79, 0x22, 0x85, 0x07, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x54, 0x61, 0x73, 0x6b, 0x4f, 0x70, 0x74, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x61, 0x64, - 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, - 0x65, 0x61, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x69, - 0x6e, 0x70, 0x75, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x69, 0x6e, 0x70, - 0x75, 0x74, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x05, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x18, 0x0a, - 0x07, 0x72, 0x65, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, - 0x72, 0x65, 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x38, 0x0a, 0x0b, 0x72, 0x61, 0x74, 0x65, 0x5f, - 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x76, - 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x61, 0x74, 0x65, - 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x52, 0x0a, 0x72, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, - 0x73, 0x12, 0x49, 0x0a, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x5f, 0x6c, 0x61, 0x62, 0x65, - 0x6c, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x4f, 0x70, 0x74, 0x73, 0x2e, 0x57, 0x6f, 0x72, - 0x6b, 0x65, 0x72, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, - 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x2a, 0x0a, 0x0e, - 0x62, 0x61, 0x63, 0x6b, 0x6f, 0x66, 0x66, 0x5f, 0x66, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x09, - 0x20, 0x01, 0x28, 0x02, 0x48, 0x00, 0x52, 0x0d, 0x62, 0x61, 0x63, 0x6b, 0x6f, 0x66, 0x66, 0x46, - 0x61, 0x63, 0x74, 0x6f, 0x72, 0x88, 0x01, 0x01, 0x12, 0x33, 0x0a, 0x13, 0x62, 0x61, 0x63, 0x6b, - 0x6f, 0x66, 0x66, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, - 0x0a, 0x20, 0x01, 0x28, 0x05, 0x48, 0x01, 0x52, 0x11, 0x62, 0x61, 0x63, 0x6b, 0x6f, 0x66, 0x66, - 0x4d, 0x61, 0x78, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x88, 0x01, 0x01, 0x12, 0x31, 0x0a, - 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x0b, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, - 0x6e, 0x63, 0x79, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, - 0x12, 0x37, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0c, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x43, 0x6f, - 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x48, 0x02, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x64, - 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x88, 0x01, 0x01, 0x12, 0x2e, 0x0a, 0x10, 0x73, 0x63, 0x68, - 0x65, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x0d, 0x20, - 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x0f, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x54, - 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x73, 0x5f, - 0x64, 0x75, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, - 0x73, 0x44, 0x75, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x49, 0x0a, 0x0d, 0x73, 0x6c, 0x6f, 0x74, - 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x24, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x4f, - 0x70, 0x74, 0x73, 0x2e, 0x53, 0x6c, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, 0x73, 0x6c, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x73, 0x1a, 0x58, 0x0a, 0x11, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4c, 0x61, 0x62, - 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2d, 0x0a, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x76, 0x31, 0x2e, 0x44, - 0x65, 0x73, 0x69, 0x72, 0x65, 0x64, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4c, 0x61, 0x62, 0x65, - 0x6c, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x3f, 0x0a, - 0x11, 0x53, 0x6c, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x45, 0x6e, 0x74, + 0x28, 0x03, 0x52, 0x05, 0x74, 0x74, 0x6c, 0x4d, 0x73, 0x22, 0x54, 0x0a, 0x19, 0x49, 0x64, 0x65, + 0x6d, 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x6c, 0x6c, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x37, 0x0a, 0x18, 0x65, 0x78, 0x69, 0x73, 0x74, 0x69, + 0x6e, 0x67, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x65, 0x78, 0x69, 0x73, 0x74, 0x69, + 0x6e, 0x67, 0x52, 0x75, 0x6e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x49, 0x64, 0x22, + 0x70, 0x0a, 0x0d, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, + 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, + 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, + 0x61, 0x64, 0x88, 0x01, 0x01, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, + 0x64, 0x22, 0xb7, 0x01, 0x0a, 0x0b, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, + 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x12, 0x1e, 0x0a, 0x08, 0x6d, 0x61, 0x78, 0x5f, 0x72, 0x75, 0x6e, 0x73, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x07, 0x6d, 0x61, 0x78, 0x52, 0x75, 0x6e, 0x73, 0x88, 0x01, + 0x01, 0x12, 0x48, 0x0a, 0x0e, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, + 0x65, 0x67, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x76, 0x31, 0x2e, 0x43, + 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x53, + 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x48, 0x01, 0x52, 0x0d, 0x6c, 0x69, 0x6d, 0x69, 0x74, + 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x88, 0x01, 0x01, 0x42, 0x0b, 0x0a, 0x09, 0x5f, + 0x6d, 0x61, 0x78, 0x5f, 0x72, 0x75, 0x6e, 0x73, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x6c, 0x69, 0x6d, + 0x69, 0x74, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x22, 0x85, 0x07, 0x0a, 0x0e, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x4f, 0x70, 0x74, 0x73, 0x12, 0x1f, + 0x0a, 0x0b, 0x72, 0x65, 0x61, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x61, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x49, 0x64, 0x12, + 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, + 0x75, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, + 0x74, 0x12, 0x16, 0x0a, 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x72, + 0x65, 0x6e, 0x74, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x72, 0x65, + 0x6e, 0x74, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x72, 0x65, 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x38, 0x0a, + 0x0b, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x18, 0x07, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, + 0x73, 0x6b, 0x52, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x52, 0x0a, 0x72, 0x61, 0x74, + 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x12, 0x49, 0x0a, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x65, + 0x72, 0x5f, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, + 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x4f, 0x70, + 0x74, 0x73, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4c, 0x61, 0x62, 0x65, + 0x6c, 0x73, 0x12, 0x2a, 0x0a, 0x0e, 0x62, 0x61, 0x63, 0x6b, 0x6f, 0x66, 0x66, 0x5f, 0x66, 0x61, + 0x63, 0x74, 0x6f, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x02, 0x48, 0x00, 0x52, 0x0d, 0x62, 0x61, + 0x63, 0x6b, 0x6f, 0x66, 0x66, 0x46, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x88, 0x01, 0x01, 0x12, 0x33, + 0x0a, 0x13, 0x62, 0x61, 0x63, 0x6b, 0x6f, 0x66, 0x66, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x73, 0x65, + 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x48, 0x01, 0x52, 0x11, 0x62, + 0x61, 0x63, 0x6b, 0x6f, 0x66, 0x66, 0x4d, 0x61, 0x78, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, + 0x88, 0x01, 0x01, 0x12, 0x31, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, + 0x63, 0x79, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, + 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, + 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x37, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x76, 0x31, 0x2e, + 0x54, 0x61, 0x73, 0x6b, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x48, 0x02, + 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x88, 0x01, 0x01, 0x12, + 0x2e, 0x0a, 0x10, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, + 0x6f, 0x75, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x0f, 0x73, 0x63, 0x68, + 0x65, 0x64, 0x75, 0x6c, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x88, 0x01, 0x01, 0x12, + 0x1d, 0x0a, 0x0a, 0x69, 0x73, 0x5f, 0x64, 0x75, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x0e, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x73, 0x44, 0x75, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x49, + 0x0a, 0x0d, 0x73, 0x6c, 0x6f, 0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x18, + 0x0f, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x54, 0x61, 0x73, 0x6b, 0x4f, 0x70, 0x74, 0x73, 0x2e, 0x53, 0x6c, 0x6f, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, 0x73, 0x6c, 0x6f, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x1a, 0x58, 0x0a, 0x11, 0x57, 0x6f, 0x72, + 0x6b, 0x65, 0x72, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, + 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, + 0x12, 0x2d, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x17, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x73, 0x69, 0x72, 0x65, 0x64, 0x57, 0x6f, 0x72, 0x6b, + 0x65, 0x72, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, + 0x02, 0x38, 0x01, 0x1a, 0x3f, 0x0a, 0x11, 0x53, 0x6c, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x3a, 0x02, 0x38, 0x01, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x6f, 0x66, 0x66, + 0x5f, 0x66, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x42, 0x16, 0x0a, 0x14, 0x5f, 0x62, 0x61, 0x63, 0x6b, + 0x6f, 0x66, 0x66, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x42, + 0x0d, 0x0a, 0x0b, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x13, + 0x0a, 0x11, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, + 0x6f, 0x75, 0x74, 0x22, 0xb8, 0x02, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, + 0x73, 0x6b, 0x52, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, + 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x19, 0x0a, + 0x05, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x05, + 0x75, 0x6e, 0x69, 0x74, 0x73, 0x88, 0x01, 0x01, 0x12, 0x1e, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x5f, + 0x65, 0x78, 0x70, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x07, 0x6b, 0x65, + 0x79, 0x45, 0x78, 0x70, 0x72, 0x88, 0x01, 0x01, 0x12, 0x22, 0x0a, 0x0a, 0x75, 0x6e, 0x69, 0x74, + 0x73, 0x5f, 0x65, 0x78, 0x70, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x09, + 0x75, 0x6e, 0x69, 0x74, 0x73, 0x45, 0x78, 0x70, 0x72, 0x88, 0x01, 0x01, 0x12, 0x2f, 0x0a, 0x11, + 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x5f, 0x65, 0x78, 0x70, + 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x0f, 0x6c, 0x69, 0x6d, 0x69, 0x74, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x45, 0x78, 0x70, 0x72, 0x88, 0x01, 0x01, 0x12, 0x36, 0x0a, + 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x15, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x44, 0x75, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x04, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x42, + 0x0b, 0x0a, 0x09, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x65, 0x78, 0x70, 0x72, 0x42, 0x0d, 0x0a, 0x0b, + 0x5f, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x5f, 0x65, 0x78, 0x70, 0x72, 0x42, 0x14, 0x0a, 0x12, 0x5f, + 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x5f, 0x65, 0x78, 0x70, + 0x72, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x50, + 0x0a, 0x1d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, + 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, + 0x1f, 0x0a, 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x49, 0x64, + 0x22, 0x37, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x52, 0x75, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, + 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x49, 0x64, 0x22, 0xe4, 0x01, 0x0a, 0x0d, 0x54, 0x61, + 0x73, 0x6b, 0x52, 0x75, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x65, + 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0d, 0x2e, 0x76, + 0x31, 0x2e, 0x52, 0x75, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x19, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x88, 0x01, 0x01, 0x12, 0x1b, + 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x01, + 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x88, 0x01, 0x01, 0x12, 0x1f, 0x0a, 0x0b, 0x72, + 0x65, 0x61, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x72, 0x65, 0x61, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, + 0x69, 0x73, 0x5f, 0x65, 0x76, 0x69, 0x63, 0x74, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x09, 0x69, 0x73, 0x45, 0x76, 0x69, 0x63, 0x74, 0x65, 0x64, 0x42, 0x08, 0x0a, 0x06, 0x5f, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, + 0x22, 0xce, 0x02, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x52, 0x75, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, + 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, + 0x70, 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x69, 0x6e, 0x70, 0x75, 0x74, + 0x12, 0x25, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x0d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x75, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x44, 0x0a, 0x09, 0x74, 0x61, 0x73, 0x6b, 0x5f, + 0x72, 0x75, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x76, 0x31, 0x2e, + 0x47, 0x65, 0x74, 0x52, 0x75, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x75, 0x6e, 0x73, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x52, 0x08, 0x74, 0x61, 0x73, 0x6b, 0x52, 0x75, 0x6e, 0x73, 0x12, 0x12, 0x0a, + 0x04, 0x64, 0x6f, 0x6e, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x64, 0x6f, 0x6e, + 0x65, 0x12, 0x2f, 0x0a, 0x13, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, + 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x12, + 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x73, 0x5f, 0x65, 0x76, 0x69, 0x63, 0x74, 0x65, 0x64, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x73, 0x45, 0x76, 0x69, 0x63, 0x74, 0x65, + 0x64, 0x1a, 0x4e, 0x0a, 0x0d, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x75, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x11, - 0x0a, 0x0f, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x6f, 0x66, 0x66, 0x5f, 0x66, 0x61, 0x63, 0x74, 0x6f, - 0x72, 0x42, 0x16, 0x0a, 0x14, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x6f, 0x66, 0x66, 0x5f, 0x6d, 0x61, - 0x78, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x63, 0x6f, - 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x73, 0x63, 0x68, - 0x65, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x22, 0xb8, 0x02, - 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x61, 0x74, 0x65, - 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x19, 0x0a, 0x05, 0x75, 0x6e, 0x69, 0x74, 0x73, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x05, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x88, - 0x01, 0x01, 0x12, 0x1e, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x5f, 0x65, 0x78, 0x70, 0x72, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x45, 0x78, 0x70, 0x72, 0x88, - 0x01, 0x01, 0x12, 0x22, 0x0a, 0x0a, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x5f, 0x65, 0x78, 0x70, 0x72, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x09, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x45, - 0x78, 0x70, 0x72, 0x88, 0x01, 0x01, 0x12, 0x2f, 0x0a, 0x11, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x5f, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x5f, 0x65, 0x78, 0x70, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x09, 0x48, 0x03, 0x52, 0x0f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, - 0x45, 0x78, 0x70, 0x72, 0x88, 0x01, 0x01, 0x12, 0x36, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x76, 0x31, 0x2e, 0x52, - 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x48, 0x04, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x42, - 0x08, 0x0a, 0x06, 0x5f, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x6b, 0x65, - 0x79, 0x5f, 0x65, 0x78, 0x70, 0x72, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x75, 0x6e, 0x69, 0x74, 0x73, - 0x5f, 0x65, 0x78, 0x70, 0x72, 0x42, 0x14, 0x0a, 0x12, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x5f, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x5f, 0x65, 0x78, 0x70, 0x72, 0x42, 0x0b, 0x0a, 0x09, 0x5f, - 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x50, 0x0a, 0x1d, 0x43, 0x72, 0x65, 0x61, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x27, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x75, 0x6e, + 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, + 0x01, 0x2a, 0x24, 0x0a, 0x0e, 0x53, 0x74, 0x69, 0x63, 0x6b, 0x79, 0x53, 0x74, 0x72, 0x61, 0x74, + 0x65, 0x67, 0x79, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x4f, 0x46, 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, + 0x04, 0x48, 0x41, 0x52, 0x44, 0x10, 0x01, 0x2a, 0x5d, 0x0a, 0x11, 0x52, 0x61, 0x74, 0x65, 0x4c, + 0x69, 0x6d, 0x69, 0x74, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, + 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x4d, 0x49, 0x4e, 0x55, + 0x54, 0x45, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x4f, 0x55, 0x52, 0x10, 0x02, 0x12, 0x07, + 0x0a, 0x03, 0x44, 0x41, 0x59, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x45, 0x45, 0x4b, 0x10, + 0x04, 0x12, 0x09, 0x0a, 0x05, 0x4d, 0x4f, 0x4e, 0x54, 0x48, 0x10, 0x05, 0x12, 0x08, 0x0a, 0x04, + 0x59, 0x45, 0x41, 0x52, 0x10, 0x06, 0x2a, 0x5b, 0x0a, 0x09, 0x52, 0x75, 0x6e, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x0a, 0x0a, 0x06, 0x51, 0x55, 0x45, 0x55, 0x45, 0x44, 0x10, 0x00, 0x12, + 0x0b, 0x0a, 0x07, 0x52, 0x55, 0x4e, 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, + 0x43, 0x4f, 0x4d, 0x50, 0x4c, 0x45, 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x46, + 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x03, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x41, 0x4e, 0x43, 0x45, + 0x4c, 0x4c, 0x45, 0x44, 0x10, 0x04, 0x12, 0x0b, 0x0a, 0x07, 0x45, 0x56, 0x49, 0x43, 0x54, 0x45, + 0x44, 0x10, 0x05, 0x2a, 0x7f, 0x0a, 0x18, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, + 0x63, 0x79, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, + 0x16, 0x0a, 0x12, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x5f, 0x49, 0x4e, 0x5f, 0x50, 0x52, 0x4f, + 0x47, 0x52, 0x45, 0x53, 0x53, 0x10, 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x44, 0x52, 0x4f, 0x50, 0x5f, + 0x4e, 0x45, 0x57, 0x45, 0x53, 0x54, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x51, 0x55, 0x45, 0x55, + 0x45, 0x5f, 0x4e, 0x45, 0x57, 0x45, 0x53, 0x54, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x47, 0x52, + 0x4f, 0x55, 0x50, 0x5f, 0x52, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x52, 0x4f, 0x42, 0x49, 0x4e, 0x10, + 0x03, 0x12, 0x11, 0x0a, 0x0d, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x5f, 0x4e, 0x45, 0x57, 0x45, + 0x53, 0x54, 0x10, 0x04, 0x32, 0xcf, 0x03, 0x0a, 0x0c, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x52, 0x0a, 0x0b, 0x50, 0x75, 0x74, 0x57, 0x6f, 0x72, 0x6b, + 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x20, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x77, 0x6f, 0x72, - 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, - 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x49, 0x64, 0x22, 0x37, 0x0a, 0x14, 0x47, 0x65, - 0x74, 0x52, 0x75, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x6c, 0x49, 0x64, 0x22, 0xe4, 0x01, 0x0a, 0x0d, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x75, 0x6e, 0x44, - 0x65, 0x74, 0x61, 0x69, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x78, 0x74, 0x65, - 0x72, 0x6e, 0x61, 0x6c, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x75, 0x6e, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x19, 0x0a, - 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, - 0x65, 0x72, 0x72, 0x6f, 0x72, 0x88, 0x01, 0x01, 0x12, 0x1b, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, - 0x75, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x01, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, - 0x75, 0x74, 0x88, 0x01, 0x01, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x61, 0x64, 0x61, 0x62, 0x6c, - 0x65, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x61, 0x64, - 0x61, 0x62, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x73, 0x5f, 0x65, 0x76, 0x69, - 0x63, 0x74, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x73, 0x45, 0x76, - 0x69, 0x63, 0x74, 0x65, 0x64, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x42, - 0x09, 0x0a, 0x07, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0xce, 0x02, 0x0a, 0x15, 0x47, + 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x0b, 0x43, 0x61, 0x6e, + 0x63, 0x65, 0x6c, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x12, 0x16, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, + 0x6e, 0x63, 0x65, 0x6c, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x17, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x54, 0x61, 0x73, 0x6b, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x0b, 0x52, 0x65, 0x70, + 0x6c, 0x61, 0x79, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x12, 0x16, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, + 0x70, 0x6c, 0x61, 0x79, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x17, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x79, 0x54, 0x61, 0x73, 0x6b, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x12, 0x54, 0x72, 0x69, + 0x67, 0x67, 0x65, 0x72, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x75, 0x6e, 0x12, + 0x1d, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x57, 0x6f, 0x72, 0x6b, + 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, + 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x57, 0x6f, 0x72, 0x6b, 0x66, + 0x6c, 0x6f, 0x77, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, + 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x52, 0x75, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, + 0x18, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x75, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, + 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x75, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x05, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x25, 0x0a, 0x06, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0d, 0x2e, 0x76, 0x31, 0x2e, - 0x52, 0x75, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x12, 0x44, 0x0a, 0x09, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x72, 0x75, 0x6e, 0x73, 0x18, 0x03, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x75, 0x6e, - 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, - 0x54, 0x61, 0x73, 0x6b, 0x52, 0x75, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x74, - 0x61, 0x73, 0x6b, 0x52, 0x75, 0x6e, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x6f, 0x6e, 0x65, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x64, 0x6f, 0x6e, 0x65, 0x12, 0x2f, 0x0a, 0x13, 0x61, - 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x12, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, - 0x6f, 0x6e, 0x61, 0x6c, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, - 0x69, 0x73, 0x5f, 0x65, 0x76, 0x69, 0x63, 0x74, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x09, 0x69, 0x73, 0x45, 0x76, 0x69, 0x63, 0x74, 0x65, 0x64, 0x1a, 0x4e, 0x0a, 0x0d, 0x54, - 0x61, 0x73, 0x6b, 0x52, 0x75, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x27, - 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, - 0x76, 0x31, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x75, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, - 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x2a, 0x24, 0x0a, 0x0e, 0x53, - 0x74, 0x69, 0x63, 0x6b, 0x79, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x08, 0x0a, - 0x04, 0x53, 0x4f, 0x46, 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x41, 0x52, 0x44, 0x10, - 0x01, 0x2a, 0x5d, 0x0a, 0x11, 0x52, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x44, 0x75, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, - 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x4d, 0x49, 0x4e, 0x55, 0x54, 0x45, 0x10, 0x01, 0x12, 0x08, - 0x0a, 0x04, 0x48, 0x4f, 0x55, 0x52, 0x10, 0x02, 0x12, 0x07, 0x0a, 0x03, 0x44, 0x41, 0x59, 0x10, - 0x03, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x45, 0x45, 0x4b, 0x10, 0x04, 0x12, 0x09, 0x0a, 0x05, 0x4d, - 0x4f, 0x4e, 0x54, 0x48, 0x10, 0x05, 0x12, 0x08, 0x0a, 0x04, 0x59, 0x45, 0x41, 0x52, 0x10, 0x06, - 0x2a, 0x5b, 0x0a, 0x09, 0x52, 0x75, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0a, 0x0a, - 0x06, 0x51, 0x55, 0x45, 0x55, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x52, 0x55, 0x4e, - 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x4f, 0x4d, 0x50, 0x4c, 0x45, - 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, - 0x03, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x4c, 0x45, 0x44, 0x10, 0x04, - 0x12, 0x0b, 0x0a, 0x07, 0x45, 0x56, 0x49, 0x43, 0x54, 0x45, 0x44, 0x10, 0x05, 0x2a, 0x7f, 0x0a, - 0x18, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x4c, 0x69, 0x6d, 0x69, - 0x74, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x16, 0x0a, 0x12, 0x43, 0x41, 0x4e, - 0x43, 0x45, 0x4c, 0x5f, 0x49, 0x4e, 0x5f, 0x50, 0x52, 0x4f, 0x47, 0x52, 0x45, 0x53, 0x53, 0x10, - 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x44, 0x52, 0x4f, 0x50, 0x5f, 0x4e, 0x45, 0x57, 0x45, 0x53, 0x54, - 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x51, 0x55, 0x45, 0x55, 0x45, 0x5f, 0x4e, 0x45, 0x57, 0x45, - 0x53, 0x54, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x47, 0x52, 0x4f, 0x55, 0x50, 0x5f, 0x52, 0x4f, - 0x55, 0x4e, 0x44, 0x5f, 0x52, 0x4f, 0x42, 0x49, 0x4e, 0x10, 0x03, 0x12, 0x11, 0x0a, 0x0d, 0x43, - 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x5f, 0x4e, 0x45, 0x57, 0x45, 0x53, 0x54, 0x10, 0x04, 0x32, 0xcf, - 0x03, 0x0a, 0x0c, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, - 0x52, 0x0a, 0x0b, 0x50, 0x75, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x20, - 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, - 0x6f, 0x77, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x21, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, - 0x66, 0x6c, 0x6f, 0x77, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x0b, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x54, 0x61, 0x73, - 0x6b, 0x73, 0x12, 0x16, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x54, 0x61, - 0x73, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x76, 0x31, 0x2e, - 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x0b, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x79, 0x54, 0x61, 0x73, - 0x6b, 0x73, 0x12, 0x16, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x79, 0x54, 0x61, - 0x73, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x76, 0x31, 0x2e, - 0x52, 0x65, 0x70, 0x6c, 0x61, 0x79, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x12, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x57, 0x6f, - 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x75, 0x6e, 0x12, 0x1d, 0x2e, 0x76, 0x31, 0x2e, 0x54, - 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x75, - 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, - 0x69, 0x67, 0x67, 0x65, 0x72, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x75, 0x6e, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x52, - 0x75, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x18, 0x2e, 0x76, 0x31, 0x2e, 0x47, - 0x65, 0x74, 0x52, 0x75, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x75, 0x6e, 0x44, - 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, - 0x0a, 0x11, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x44, 0x75, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x54, - 0x61, 0x73, 0x6b, 0x12, 0x1c, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x44, - 0x75, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1d, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x44, 0x75, 0x72, - 0x61, 0x62, 0x6c, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x42, 0x42, 0x5a, 0x40, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, - 0x61, 0x74, 0x63, 0x68, 0x65, 0x74, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x68, 0x61, 0x74, 0x63, 0x68, - 0x65, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x73, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x73, 0x2f, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x11, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x44, 0x75, + 0x72, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x1c, 0x2e, 0x76, 0x31, 0x2e, 0x42, + 0x72, 0x61, 0x6e, 0x63, 0x68, 0x44, 0x75, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x61, 0x73, 0x6b, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x72, 0x61, + 0x6e, 0x63, 0x68, 0x44, 0x75, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x42, 0x5a, 0x40, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x74, 0x63, 0x68, 0x65, 0x74, 0x2d, 0x64, 0x65, 0x76, + 0x2f, 0x68, 0x61, 0x74, 0x63, 0x68, 0x65, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, + 0x6c, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x73, 0x68, 0x61, 0x72, 0x65, + 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, } var ( @@ -1999,7 +2052,7 @@ func file_v1_workflows_proto_rawDescGZIP() []byte { } var file_v1_workflows_proto_enumTypes = make([]protoimpl.EnumInfo, 4) -var file_v1_workflows_proto_msgTypes = make([]protoimpl.MessageInfo, 23) +var file_v1_workflows_proto_msgTypes = make([]protoimpl.MessageInfo, 24) var file_v1_workflows_proto_goTypes = []interface{}{ (StickyStrategy)(0), // 0: v1.StickyStrategy (RateLimitDuration)(0), // 1: v1.RateLimitDuration @@ -2016,59 +2069,60 @@ var file_v1_workflows_proto_goTypes = []interface{}{ (*BranchDurableTaskResponse)(nil), // 12: v1.BranchDurableTaskResponse (*CreateWorkflowVersionRequest)(nil), // 13: v1.CreateWorkflowVersionRequest (*IdempotencyConfig)(nil), // 14: v1.IdempotencyConfig - (*DefaultFilter)(nil), // 15: v1.DefaultFilter - (*Concurrency)(nil), // 16: v1.Concurrency - (*CreateTaskOpts)(nil), // 17: v1.CreateTaskOpts - (*CreateTaskRateLimit)(nil), // 18: v1.CreateTaskRateLimit - (*CreateWorkflowVersionResponse)(nil), // 19: v1.CreateWorkflowVersionResponse - (*GetRunDetailsRequest)(nil), // 20: v1.GetRunDetailsRequest - (*TaskRunDetail)(nil), // 21: v1.TaskRunDetail - (*GetRunDetailsResponse)(nil), // 22: v1.GetRunDetailsResponse - nil, // 23: v1.TriggerWorkflowRunRequest.DesiredWorkerLabelsEntry - nil, // 24: v1.CreateTaskOpts.WorkerLabelsEntry - nil, // 25: v1.CreateTaskOpts.SlotRequestsEntry - nil, // 26: v1.GetRunDetailsResponse.TaskRunsEntry - (*timestamppb.Timestamp)(nil), // 27: google.protobuf.Timestamp - (*TaskConditions)(nil), // 28: v1.TaskConditions - (*DesiredWorkerLabels)(nil), // 29: v1.DesiredWorkerLabels + (*IdempotencyCollisionError)(nil), // 15: v1.IdempotencyCollisionError + (*DefaultFilter)(nil), // 16: v1.DefaultFilter + (*Concurrency)(nil), // 17: v1.Concurrency + (*CreateTaskOpts)(nil), // 18: v1.CreateTaskOpts + (*CreateTaskRateLimit)(nil), // 19: v1.CreateTaskRateLimit + (*CreateWorkflowVersionResponse)(nil), // 20: v1.CreateWorkflowVersionResponse + (*GetRunDetailsRequest)(nil), // 21: v1.GetRunDetailsRequest + (*TaskRunDetail)(nil), // 22: v1.TaskRunDetail + (*GetRunDetailsResponse)(nil), // 23: v1.GetRunDetailsResponse + nil, // 24: v1.TriggerWorkflowRunRequest.DesiredWorkerLabelsEntry + nil, // 25: v1.CreateTaskOpts.WorkerLabelsEntry + nil, // 26: v1.CreateTaskOpts.SlotRequestsEntry + nil, // 27: v1.GetRunDetailsResponse.TaskRunsEntry + (*timestamppb.Timestamp)(nil), // 28: google.protobuf.Timestamp + (*TaskConditions)(nil), // 29: v1.TaskConditions + (*DesiredWorkerLabels)(nil), // 30: v1.DesiredWorkerLabels } var file_v1_workflows_proto_depIdxs = []int32{ 6, // 0: v1.CancelTasksRequest.filter:type_name -> v1.TasksFilter 6, // 1: v1.ReplayTasksRequest.filter:type_name -> v1.TasksFilter - 27, // 2: v1.TasksFilter.since:type_name -> google.protobuf.Timestamp - 27, // 3: v1.TasksFilter.until:type_name -> google.protobuf.Timestamp - 23, // 4: v1.TriggerWorkflowRunRequest.desired_worker_labels:type_name -> v1.TriggerWorkflowRunRequest.DesiredWorkerLabelsEntry - 17, // 5: v1.CreateWorkflowVersionRequest.tasks:type_name -> v1.CreateTaskOpts - 16, // 6: v1.CreateWorkflowVersionRequest.concurrency:type_name -> v1.Concurrency - 17, // 7: v1.CreateWorkflowVersionRequest.on_failure_task:type_name -> v1.CreateTaskOpts + 28, // 2: v1.TasksFilter.since:type_name -> google.protobuf.Timestamp + 28, // 3: v1.TasksFilter.until:type_name -> google.protobuf.Timestamp + 24, // 4: v1.TriggerWorkflowRunRequest.desired_worker_labels:type_name -> v1.TriggerWorkflowRunRequest.DesiredWorkerLabelsEntry + 18, // 5: v1.CreateWorkflowVersionRequest.tasks:type_name -> v1.CreateTaskOpts + 17, // 6: v1.CreateWorkflowVersionRequest.concurrency:type_name -> v1.Concurrency + 18, // 7: v1.CreateWorkflowVersionRequest.on_failure_task:type_name -> v1.CreateTaskOpts 0, // 8: v1.CreateWorkflowVersionRequest.sticky:type_name -> v1.StickyStrategy - 16, // 9: v1.CreateWorkflowVersionRequest.concurrency_arr:type_name -> v1.Concurrency - 15, // 10: v1.CreateWorkflowVersionRequest.default_filters:type_name -> v1.DefaultFilter + 17, // 9: v1.CreateWorkflowVersionRequest.concurrency_arr:type_name -> v1.Concurrency + 16, // 10: v1.CreateWorkflowVersionRequest.default_filters:type_name -> v1.DefaultFilter 14, // 11: v1.CreateWorkflowVersionRequest.idempotency:type_name -> v1.IdempotencyConfig 3, // 12: v1.Concurrency.limit_strategy:type_name -> v1.ConcurrencyLimitStrategy - 18, // 13: v1.CreateTaskOpts.rate_limits:type_name -> v1.CreateTaskRateLimit - 24, // 14: v1.CreateTaskOpts.worker_labels:type_name -> v1.CreateTaskOpts.WorkerLabelsEntry - 16, // 15: v1.CreateTaskOpts.concurrency:type_name -> v1.Concurrency - 28, // 16: v1.CreateTaskOpts.conditions:type_name -> v1.TaskConditions - 25, // 17: v1.CreateTaskOpts.slot_requests:type_name -> v1.CreateTaskOpts.SlotRequestsEntry + 19, // 13: v1.CreateTaskOpts.rate_limits:type_name -> v1.CreateTaskRateLimit + 25, // 14: v1.CreateTaskOpts.worker_labels:type_name -> v1.CreateTaskOpts.WorkerLabelsEntry + 17, // 15: v1.CreateTaskOpts.concurrency:type_name -> v1.Concurrency + 29, // 16: v1.CreateTaskOpts.conditions:type_name -> v1.TaskConditions + 26, // 17: v1.CreateTaskOpts.slot_requests:type_name -> v1.CreateTaskOpts.SlotRequestsEntry 1, // 18: v1.CreateTaskRateLimit.duration:type_name -> v1.RateLimitDuration 2, // 19: v1.TaskRunDetail.status:type_name -> v1.RunStatus 2, // 20: v1.GetRunDetailsResponse.status:type_name -> v1.RunStatus - 26, // 21: v1.GetRunDetailsResponse.task_runs:type_name -> v1.GetRunDetailsResponse.TaskRunsEntry - 29, // 22: v1.TriggerWorkflowRunRequest.DesiredWorkerLabelsEntry.value:type_name -> v1.DesiredWorkerLabels - 29, // 23: v1.CreateTaskOpts.WorkerLabelsEntry.value:type_name -> v1.DesiredWorkerLabels - 21, // 24: v1.GetRunDetailsResponse.TaskRunsEntry.value:type_name -> v1.TaskRunDetail + 27, // 21: v1.GetRunDetailsResponse.task_runs:type_name -> v1.GetRunDetailsResponse.TaskRunsEntry + 30, // 22: v1.TriggerWorkflowRunRequest.DesiredWorkerLabelsEntry.value:type_name -> v1.DesiredWorkerLabels + 30, // 23: v1.CreateTaskOpts.WorkerLabelsEntry.value:type_name -> v1.DesiredWorkerLabels + 22, // 24: v1.GetRunDetailsResponse.TaskRunsEntry.value:type_name -> v1.TaskRunDetail 13, // 25: v1.AdminService.PutWorkflow:input_type -> v1.CreateWorkflowVersionRequest 4, // 26: v1.AdminService.CancelTasks:input_type -> v1.CancelTasksRequest 5, // 27: v1.AdminService.ReplayTasks:input_type -> v1.ReplayTasksRequest 9, // 28: v1.AdminService.TriggerWorkflowRun:input_type -> v1.TriggerWorkflowRunRequest - 20, // 29: v1.AdminService.GetRunDetails:input_type -> v1.GetRunDetailsRequest + 21, // 29: v1.AdminService.GetRunDetails:input_type -> v1.GetRunDetailsRequest 11, // 30: v1.AdminService.BranchDurableTask:input_type -> v1.BranchDurableTaskRequest - 19, // 31: v1.AdminService.PutWorkflow:output_type -> v1.CreateWorkflowVersionResponse + 20, // 31: v1.AdminService.PutWorkflow:output_type -> v1.CreateWorkflowVersionResponse 7, // 32: v1.AdminService.CancelTasks:output_type -> v1.CancelTasksResponse 8, // 33: v1.AdminService.ReplayTasks:output_type -> v1.ReplayTasksResponse 10, // 34: v1.AdminService.TriggerWorkflowRun:output_type -> v1.TriggerWorkflowRunResponse - 22, // 35: v1.AdminService.GetRunDetails:output_type -> v1.GetRunDetailsResponse + 23, // 35: v1.AdminService.GetRunDetails:output_type -> v1.GetRunDetailsResponse 12, // 36: v1.AdminService.BranchDurableTask:output_type -> v1.BranchDurableTaskResponse 31, // [31:37] is the sub-list for method output_type 25, // [25:31] is the sub-list for method input_type @@ -2218,7 +2272,7 @@ func file_v1_workflows_proto_init() { } } file_v1_workflows_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DefaultFilter); i { + switch v := v.(*IdempotencyCollisionError); i { case 0: return &v.state case 1: @@ -2230,7 +2284,7 @@ func file_v1_workflows_proto_init() { } } file_v1_workflows_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Concurrency); i { + switch v := v.(*DefaultFilter); i { case 0: return &v.state case 1: @@ -2242,7 +2296,7 @@ func file_v1_workflows_proto_init() { } } file_v1_workflows_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CreateTaskOpts); i { + switch v := v.(*Concurrency); i { case 0: return &v.state case 1: @@ -2254,7 +2308,7 @@ func file_v1_workflows_proto_init() { } } file_v1_workflows_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CreateTaskRateLimit); i { + switch v := v.(*CreateTaskOpts); i { case 0: return &v.state case 1: @@ -2266,7 +2320,7 @@ func file_v1_workflows_proto_init() { } } file_v1_workflows_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CreateWorkflowVersionResponse); i { + switch v := v.(*CreateTaskRateLimit); i { case 0: return &v.state case 1: @@ -2278,7 +2332,7 @@ func file_v1_workflows_proto_init() { } } file_v1_workflows_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetRunDetailsRequest); i { + switch v := v.(*CreateWorkflowVersionResponse); i { case 0: return &v.state case 1: @@ -2290,7 +2344,7 @@ func file_v1_workflows_proto_init() { } } file_v1_workflows_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TaskRunDetail); i { + switch v := v.(*GetRunDetailsRequest); i { case 0: return &v.state case 1: @@ -2302,6 +2356,18 @@ func file_v1_workflows_proto_init() { } } file_v1_workflows_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TaskRunDetail); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_v1_workflows_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetRunDetailsResponse); i { case 0: return &v.state @@ -2319,18 +2385,18 @@ func file_v1_workflows_proto_init() { file_v1_workflows_proto_msgTypes[2].OneofWrappers = []interface{}{} file_v1_workflows_proto_msgTypes[5].OneofWrappers = []interface{}{} file_v1_workflows_proto_msgTypes[9].OneofWrappers = []interface{}{} - file_v1_workflows_proto_msgTypes[11].OneofWrappers = []interface{}{} file_v1_workflows_proto_msgTypes[12].OneofWrappers = []interface{}{} file_v1_workflows_proto_msgTypes[13].OneofWrappers = []interface{}{} file_v1_workflows_proto_msgTypes[14].OneofWrappers = []interface{}{} - file_v1_workflows_proto_msgTypes[17].OneofWrappers = []interface{}{} + file_v1_workflows_proto_msgTypes[15].OneofWrappers = []interface{}{} + file_v1_workflows_proto_msgTypes[18].OneofWrappers = []interface{}{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_v1_workflows_proto_rawDesc, NumEnums: 4, - NumMessages: 23, + NumMessages: 24, NumExtensions: 0, NumServices: 1, }, diff --git a/pkg/repository/idempotency.go b/pkg/repository/idempotency.go index 5350fc973e..40a8d181b0 100644 --- a/pkg/repository/idempotency.go +++ b/pkg/repository/idempotency.go @@ -57,48 +57,3 @@ type IdempotencyCollision struct { RequestedExternalId uuid.UUID ExistingExternalId uuid.UUID } - -type KeyClaimantPair struct { - IdempotencyKey IdempotencyKey - ClaimedByExternalId uuid.UUID -} - -func claimIdempotencyKeys(context context.Context, queries *sqlcv1.Queries, tx sqlcv1.DBTX, tenantId uuid.UUID, claims []KeyClaimantPair) (map[KeyClaimantPair]WasSuccessfullyClaimed, error) { - keyToClaimStatus := make(map[KeyClaimantPair]WasSuccessfullyClaimed) - - if len(claims) == 0 { - return keyToClaimStatus, nil - } - - keys := make([]string, len(claims)) - claimedByExternalIds := make([]uuid.UUID, len(claims)) - - for i, claim := range claims { - keys[i] = string(claim.IdempotencyKey) - claimedByExternalIds[i] = claim.ClaimedByExternalId - } - - claimResults, err := queries.ClaimIdempotencyKeys(context, tx, sqlcv1.ClaimIdempotencyKeysParams{ - Tenantid: tenantId, - Keys: keys, - Claimedbyexternalids: claimedByExternalIds, - }) - - if err != nil { - return nil, err - } - - for _, claimResult := range claimResults { - if claimResult.ClaimedByExternalID == nil { - continue - } - - keyClaimantPair := KeyClaimantPair{ - IdempotencyKey: IdempotencyKey(claimResult.Key), - ClaimedByExternalId: *claimResult.ClaimedByExternalID, - } - keyToClaimStatus[keyClaimantPair] = WasSuccessfullyClaimed(claimResult.WasSuccessfullyClaimed) - } - - return keyToClaimStatus, nil -} diff --git a/sdks/python/examples/idempotency/test_idempotency.py b/sdks/python/examples/idempotency/test_idempotency.py index 35f62c9122..d10a0f3514 100644 --- a/sdks/python/examples/idempotency/test_idempotency.py +++ b/sdks/python/examples/idempotency/test_idempotency.py @@ -3,7 +3,7 @@ from examples.idempotency.worker import idempotent_task, IdempotencyInput from hatchet_sdk import Hatchet -from hatchet_sdk.exceptions import DedupeViolationError +from hatchet_sdk.exceptions import IdempotencyCollisionError @pytest.mark.asyncio(loop_scope="session") @@ -11,16 +11,10 @@ async def test_idempotency_keys_prevent_duplicate_runs(hatchet: Hatchet) -> None ref1 = await idempotent_task.aio_run( input=IdempotencyInput(id="123"), wait_for_result=False ) - ref2 = await idempotent_task.aio_run( - input=IdempotencyInput(id="123"), wait_for_result=False - ) - - assert ref1 is not None - assert ref2 is not None - - assert ref1.workflow_run_id == ref2.workflow_run_id - result1 = await ref1.aio_result() - result2 = await ref2.aio_result() + with pytest.raises(IdempotencyCollisionError) as exc_info: + await idempotent_task.aio_run( + input=IdempotencyInput(id="123"), wait_for_result=False + ) - assert result1 == result2 + assert exc_info.value.existing_run_external_id == ref1.workflow_run_id diff --git a/sdks/python/hatchet_sdk/__init__.py b/sdks/python/hatchet_sdk/__init__.py index 97693178c1..58fbe2a718 100644 --- a/sdks/python/hatchet_sdk/__init__.py +++ b/sdks/python/hatchet_sdk/__init__.py @@ -134,6 +134,7 @@ DedupeViolationError, EvictionNotSupportedError, FailedTaskRunExceptionGroup, + IdempotencyCollisionError, NonDeterminismError, NonRetryableException, TaskRunError, @@ -224,6 +225,7 @@ "GithubBranch", "GithubRepo", "Hatchet", + "IdempotencyCollisionError", "IdempotencyConfig", "Job", "JobRun", diff --git a/sdks/python/hatchet_sdk/clients/admin.py b/sdks/python/hatchet_sdk/clients/admin.py index 21d90588d2..fc274f3935 100644 --- a/sdks/python/hatchet_sdk/clients/admin.py +++ b/sdks/python/hatchet_sdk/clients/admin.py @@ -3,10 +3,11 @@ from collections.abc import Generator from datetime import datetime from enum import Enum -from typing import TypeVar, cast +from typing import Any, TypeVar, cast import grpc from google.protobuf import timestamp_pb2 +from grpc_status import rpc_status from pydantic import BaseModel, ConfigDict, field_validator from hatchet_sdk.clients.listeners.run_event_listener import RunEventListenerClient @@ -20,7 +21,7 @@ from hatchet_sdk.contracts.v1.shared import trigger_pb2 as trigger_protos from hatchet_sdk.contracts.v1.workflows_pb2_grpc import AdminServiceStub from hatchet_sdk.contracts.workflows_pb2_grpc import WorkflowServiceStub -from hatchet_sdk.exceptions import DedupeViolationError +from hatchet_sdk.exceptions import DedupeViolationError, IdempotencyCollisionError from hatchet_sdk.logger import logger from hatchet_sdk.runnables.contextvars import ( ctx_action_key, @@ -218,9 +219,11 @@ def _prepare_workflow_request( return workflow_protos.TriggerWorkflowRunRequest( workflow_name=workflow_name, input=input.encode("utf-8") if input else None, - additional_metadata=_options.additional_metadata.encode("utf-8") - if _options.additional_metadata - else None, + additional_metadata=( + _options.additional_metadata.encode("utf-8") + if _options.additional_metadata + else None + ), priority=_options.priority, desired_worker_labels=desired_worker_labels, ) @@ -499,7 +502,21 @@ async def aio_run_workflow( ), ) except (grpc.RpcError, grpc.aio.AioRpcError) as e: - if e.code() == grpc.StatusCode.ALREADY_EXISTS: + if ( + e.code() == grpc.StatusCode.ALREADY_EXISTS + and e.details() == "idempotency key collision" + ): + status: list[Any] = rpc_status.from_call(e) # type: ignore[arg-type] + + for detail in status.details: # type: ignore[attr-defined] + if detail.Is(workflow_protos.IdempotencyCollisionError.DESCRIPTOR): + info = workflow_protos.IdempotencyCollisionError() + detail.Unpack(info) + + raise IdempotencyCollisionError( + existing_run_external_id=info.existing_run_external_id + ) from e + elif e.code() == grpc.StatusCode.ALREADY_EXISTS: raise DedupeViolationError(e.details()) from e raise e diff --git a/sdks/python/hatchet_sdk/contracts/v1/workflows_pb2.py b/sdks/python/hatchet_sdk/contracts/v1/workflows_pb2.py index 226bd7ed10..9ec0c18f81 100644 --- a/sdks/python/hatchet_sdk/contracts/v1/workflows_pb2.py +++ b/sdks/python/hatchet_sdk/contracts/v1/workflows_pb2.py @@ -27,7 +27,7 @@ from hatchet_sdk.contracts.v1.shared import trigger_pb2 as v1_dot_shared_dot_trigger__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x12v1/workflows.proto\x12\x02v1\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x19v1/shared/condition.proto\x1a\x17v1/shared/trigger.proto\"[\n\x12\x43\x61ncelTasksRequest\x12\x14\n\x0c\x65xternal_ids\x18\x01 \x03(\t\x12$\n\x06\x66ilter\x18\x02 \x01(\x0b\x32\x0f.v1.TasksFilterH\x00\x88\x01\x01\x42\t\n\x07_filter\"[\n\x12ReplayTasksRequest\x12\x14\n\x0c\x65xternal_ids\x18\x01 \x03(\t\x12$\n\x06\x66ilter\x18\x02 \x01(\x0b\x32\x0f.v1.TasksFilterH\x00\x88\x01\x01\x42\t\n\x07_filter\"\xb7\x01\n\x0bTasksFilter\x12\x10\n\x08statuses\x18\x01 \x03(\t\x12)\n\x05since\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12.\n\x05until\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampH\x00\x88\x01\x01\x12\x14\n\x0cworkflow_ids\x18\x04 \x03(\t\x12\x1b\n\x13\x61\x64\x64itional_metadata\x18\x05 \x03(\tB\x08\n\x06_until\".\n\x13\x43\x61ncelTasksResponse\x12\x17\n\x0f\x63\x61ncelled_tasks\x18\x01 \x03(\t\"-\n\x13ReplayTasksResponse\x12\x16\n\x0ereplayed_tasks\x18\x01 \x03(\t\"\xae\x02\n\x19TriggerWorkflowRunRequest\x12\x15\n\rworkflow_name\x18\x01 \x01(\t\x12\r\n\x05input\x18\x02 \x01(\x0c\x12\x1b\n\x13\x61\x64\x64itional_metadata\x18\x03 \x01(\x0c\x12\x15\n\x08priority\x18\x04 \x01(\x05H\x00\x88\x01\x01\x12U\n\x15\x64\x65sired_worker_labels\x18\x05 \x03(\x0b\x32\x36.v1.TriggerWorkflowRunRequest.DesiredWorkerLabelsEntry\x1aS\n\x18\x44\x65siredWorkerLabelsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12&\n\x05value\x18\x02 \x01(\x0b\x32\x17.v1.DesiredWorkerLabels:\x02\x38\x01\x42\x0b\n\t_priority\"1\n\x1aTriggerWorkflowRunResponse\x12\x13\n\x0b\x65xternal_id\x18\x01 \x01(\t\"X\n\x18\x42ranchDurableTaskRequest\x12\x18\n\x10task_external_id\x18\x01 \x01(\t\x12\x0f\n\x07node_id\x18\x02 \x01(\x03\x12\x11\n\tbranch_id\x18\x03 \x01(\x03\"Y\n\x19\x42ranchDurableTaskResponse\x12\x18\n\x10task_external_id\x18\x01 \x01(\t\x12\x0f\n\x07node_id\x18\x02 \x01(\x03\x12\x11\n\tbranch_id\x18\x03 \x01(\x03\"\xed\x04\n\x1c\x43reateWorkflowVersionRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x0f\n\x07version\x18\x03 \x01(\t\x12\x16\n\x0e\x65vent_triggers\x18\x04 \x03(\t\x12\x15\n\rcron_triggers\x18\x05 \x03(\t\x12!\n\x05tasks\x18\x06 \x03(\x0b\x32\x12.v1.CreateTaskOpts\x12$\n\x0b\x63oncurrency\x18\x07 \x01(\x0b\x32\x0f.v1.Concurrency\x12\x17\n\ncron_input\x18\x08 \x01(\tH\x00\x88\x01\x01\x12\x30\n\x0fon_failure_task\x18\t \x01(\x0b\x32\x12.v1.CreateTaskOptsH\x01\x88\x01\x01\x12\'\n\x06sticky\x18\n \x01(\x0e\x32\x12.v1.StickyStrategyH\x02\x88\x01\x01\x12\x1d\n\x10\x64\x65\x66\x61ult_priority\x18\x0b \x01(\x05H\x03\x88\x01\x01\x12(\n\x0f\x63oncurrency_arr\x18\x0c \x03(\x0b\x32\x0f.v1.Concurrency\x12*\n\x0f\x64\x65\x66\x61ult_filters\x18\r \x03(\x0b\x32\x11.v1.DefaultFilter\x12\x1e\n\x11input_json_schema\x18\x0e \x01(\x0cH\x04\x88\x01\x01\x12/\n\x0bidempotency\x18\x0f \x01(\x0b\x32\x15.v1.IdempotencyConfigH\x05\x88\x01\x01\x42\r\n\x0b_cron_inputB\x12\n\x10_on_failure_taskB\t\n\x07_stickyB\x13\n\x11_default_priorityB\x14\n\x12_input_json_schemaB\x0e\n\x0c_idempotency\"7\n\x11IdempotencyConfig\x12\x12\n\nexpression\x18\x01 \x01(\t\x12\x0e\n\x06ttl_ms\x18\x02 \x01(\x03\"T\n\rDefaultFilter\x12\x12\n\nexpression\x18\x01 \x01(\t\x12\r\n\x05scope\x18\x02 \x01(\t\x12\x14\n\x07payload\x18\x03 \x01(\x0cH\x00\x88\x01\x01\x42\n\n\x08_payload\"\x93\x01\n\x0b\x43oncurrency\x12\x12\n\nexpression\x18\x01 \x01(\t\x12\x15\n\x08max_runs\x18\x02 \x01(\x05H\x00\x88\x01\x01\x12\x39\n\x0elimit_strategy\x18\x03 \x01(\x0e\x32\x1c.v1.ConcurrencyLimitStrategyH\x01\x88\x01\x01\x42\x0b\n\t_max_runsB\x11\n\x0f_limit_strategy\"\xb7\x05\n\x0e\x43reateTaskOpts\x12\x13\n\x0breadable_id\x18\x01 \x01(\t\x12\x0e\n\x06\x61\x63tion\x18\x02 \x01(\t\x12\x0f\n\x07timeout\x18\x03 \x01(\t\x12\x0e\n\x06inputs\x18\x04 \x01(\t\x12\x0f\n\x07parents\x18\x05 \x03(\t\x12\x0f\n\x07retries\x18\x06 \x01(\x05\x12,\n\x0brate_limits\x18\x07 \x03(\x0b\x32\x17.v1.CreateTaskRateLimit\x12;\n\rworker_labels\x18\x08 \x03(\x0b\x32$.v1.CreateTaskOpts.WorkerLabelsEntry\x12\x1b\n\x0e\x62\x61\x63koff_factor\x18\t \x01(\x02H\x00\x88\x01\x01\x12 \n\x13\x62\x61\x63koff_max_seconds\x18\n \x01(\x05H\x01\x88\x01\x01\x12$\n\x0b\x63oncurrency\x18\x0b \x03(\x0b\x32\x0f.v1.Concurrency\x12+\n\nconditions\x18\x0c \x01(\x0b\x32\x12.v1.TaskConditionsH\x02\x88\x01\x01\x12\x1d\n\x10schedule_timeout\x18\r \x01(\tH\x03\x88\x01\x01\x12\x12\n\nis_durable\x18\x0e \x01(\x08\x12;\n\rslot_requests\x18\x0f \x03(\x0b\x32$.v1.CreateTaskOpts.SlotRequestsEntry\x1aL\n\x11WorkerLabelsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12&\n\x05value\x18\x02 \x01(\x0b\x32\x17.v1.DesiredWorkerLabels:\x02\x38\x01\x1a\x33\n\x11SlotRequestsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x05:\x02\x38\x01\x42\x11\n\x0f_backoff_factorB\x16\n\x14_backoff_max_secondsB\r\n\x0b_conditionsB\x13\n\x11_schedule_timeout\"\xfd\x01\n\x13\x43reateTaskRateLimit\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x12\n\x05units\x18\x02 \x01(\x05H\x00\x88\x01\x01\x12\x15\n\x08key_expr\x18\x03 \x01(\tH\x01\x88\x01\x01\x12\x17\n\nunits_expr\x18\x04 \x01(\tH\x02\x88\x01\x01\x12\x1e\n\x11limit_values_expr\x18\x05 \x01(\tH\x03\x88\x01\x01\x12,\n\x08\x64uration\x18\x06 \x01(\x0e\x32\x15.v1.RateLimitDurationH\x04\x88\x01\x01\x42\x08\n\x06_unitsB\x0b\n\t_key_exprB\r\n\x0b_units_exprB\x14\n\x12_limit_values_exprB\x0b\n\t_duration\"@\n\x1d\x43reateWorkflowVersionResponse\x12\n\n\x02id\x18\x01 \x01(\t\x12\x13\n\x0bworkflow_id\x18\x02 \x01(\t\"+\n\x14GetRunDetailsRequest\x12\x13\n\x0b\x65xternal_id\x18\x01 \x01(\t\"\xaa\x01\n\rTaskRunDetail\x12\x13\n\x0b\x65xternal_id\x18\x01 \x01(\t\x12\x1d\n\x06status\x18\x02 \x01(\x0e\x32\r.v1.RunStatus\x12\x12\n\x05\x65rror\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x13\n\x06output\x18\x04 \x01(\x0cH\x01\x88\x01\x01\x12\x13\n\x0breadable_id\x18\x05 \x01(\t\x12\x12\n\nis_evicted\x18\x06 \x01(\x08\x42\x08\n\x06_errorB\t\n\x07_output\"\x84\x02\n\x15GetRunDetailsResponse\x12\r\n\x05input\x18\x01 \x01(\x0c\x12\x1d\n\x06status\x18\x02 \x01(\x0e\x32\r.v1.RunStatus\x12:\n\ttask_runs\x18\x03 \x03(\x0b\x32\'.v1.GetRunDetailsResponse.TaskRunsEntry\x12\x0c\n\x04\x64one\x18\x04 \x01(\x08\x12\x1b\n\x13\x61\x64\x64itional_metadata\x18\x05 \x01(\x0c\x12\x12\n\nis_evicted\x18\x06 \x01(\x08\x1a\x42\n\rTaskRunsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12 \n\x05value\x18\x02 \x01(\x0b\x32\x11.v1.TaskRunDetail:\x02\x38\x01*$\n\x0eStickyStrategy\x12\x08\n\x04SOFT\x10\x00\x12\x08\n\x04HARD\x10\x01*]\n\x11RateLimitDuration\x12\n\n\x06SECOND\x10\x00\x12\n\n\x06MINUTE\x10\x01\x12\x08\n\x04HOUR\x10\x02\x12\x07\n\x03\x44\x41Y\x10\x03\x12\x08\n\x04WEEK\x10\x04\x12\t\n\x05MONTH\x10\x05\x12\x08\n\x04YEAR\x10\x06*[\n\tRunStatus\x12\n\n\x06QUEUED\x10\x00\x12\x0b\n\x07RUNNING\x10\x01\x12\r\n\tCOMPLETED\x10\x02\x12\n\n\x06\x46\x41ILED\x10\x03\x12\r\n\tCANCELLED\x10\x04\x12\x0b\n\x07\x45VICTED\x10\x05*\x7f\n\x18\x43oncurrencyLimitStrategy\x12\x16\n\x12\x43\x41NCEL_IN_PROGRESS\x10\x00\x12\x0f\n\x0b\x44ROP_NEWEST\x10\x01\x12\x10\n\x0cQUEUE_NEWEST\x10\x02\x12\x15\n\x11GROUP_ROUND_ROBIN\x10\x03\x12\x11\n\rCANCEL_NEWEST\x10\x04\x32\xcf\x03\n\x0c\x41\x64minService\x12R\n\x0bPutWorkflow\x12 .v1.CreateWorkflowVersionRequest\x1a!.v1.CreateWorkflowVersionResponse\x12>\n\x0b\x43\x61ncelTasks\x12\x16.v1.CancelTasksRequest\x1a\x17.v1.CancelTasksResponse\x12>\n\x0bReplayTasks\x12\x16.v1.ReplayTasksRequest\x1a\x17.v1.ReplayTasksResponse\x12S\n\x12TriggerWorkflowRun\x12\x1d.v1.TriggerWorkflowRunRequest\x1a\x1e.v1.TriggerWorkflowRunResponse\x12\x44\n\rGetRunDetails\x12\x18.v1.GetRunDetailsRequest\x1a\x19.v1.GetRunDetailsResponse\x12P\n\x11\x42ranchDurableTask\x12\x1c.v1.BranchDurableTaskRequest\x1a\x1d.v1.BranchDurableTaskResponseBBZ@github.com/hatchet-dev/hatchet/internal/services/shared/proto/v1b\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x12v1/workflows.proto\x12\x02v1\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x19v1/shared/condition.proto\x1a\x17v1/shared/trigger.proto\"[\n\x12\x43\x61ncelTasksRequest\x12\x14\n\x0c\x65xternal_ids\x18\x01 \x03(\t\x12$\n\x06\x66ilter\x18\x02 \x01(\x0b\x32\x0f.v1.TasksFilterH\x00\x88\x01\x01\x42\t\n\x07_filter\"[\n\x12ReplayTasksRequest\x12\x14\n\x0c\x65xternal_ids\x18\x01 \x03(\t\x12$\n\x06\x66ilter\x18\x02 \x01(\x0b\x32\x0f.v1.TasksFilterH\x00\x88\x01\x01\x42\t\n\x07_filter\"\xb7\x01\n\x0bTasksFilter\x12\x10\n\x08statuses\x18\x01 \x03(\t\x12)\n\x05since\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12.\n\x05until\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampH\x00\x88\x01\x01\x12\x14\n\x0cworkflow_ids\x18\x04 \x03(\t\x12\x1b\n\x13\x61\x64\x64itional_metadata\x18\x05 \x03(\tB\x08\n\x06_until\".\n\x13\x43\x61ncelTasksResponse\x12\x17\n\x0f\x63\x61ncelled_tasks\x18\x01 \x03(\t\"-\n\x13ReplayTasksResponse\x12\x16\n\x0ereplayed_tasks\x18\x01 \x03(\t\"\xae\x02\n\x19TriggerWorkflowRunRequest\x12\x15\n\rworkflow_name\x18\x01 \x01(\t\x12\r\n\x05input\x18\x02 \x01(\x0c\x12\x1b\n\x13\x61\x64\x64itional_metadata\x18\x03 \x01(\x0c\x12\x15\n\x08priority\x18\x04 \x01(\x05H\x00\x88\x01\x01\x12U\n\x15\x64\x65sired_worker_labels\x18\x05 \x03(\x0b\x32\x36.v1.TriggerWorkflowRunRequest.DesiredWorkerLabelsEntry\x1aS\n\x18\x44\x65siredWorkerLabelsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12&\n\x05value\x18\x02 \x01(\x0b\x32\x17.v1.DesiredWorkerLabels:\x02\x38\x01\x42\x0b\n\t_priority\"1\n\x1aTriggerWorkflowRunResponse\x12\x13\n\x0b\x65xternal_id\x18\x01 \x01(\t\"X\n\x18\x42ranchDurableTaskRequest\x12\x18\n\x10task_external_id\x18\x01 \x01(\t\x12\x0f\n\x07node_id\x18\x02 \x01(\x03\x12\x11\n\tbranch_id\x18\x03 \x01(\x03\"Y\n\x19\x42ranchDurableTaskResponse\x12\x18\n\x10task_external_id\x18\x01 \x01(\t\x12\x0f\n\x07node_id\x18\x02 \x01(\x03\x12\x11\n\tbranch_id\x18\x03 \x01(\x03\"\xed\x04\n\x1c\x43reateWorkflowVersionRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x0f\n\x07version\x18\x03 \x01(\t\x12\x16\n\x0e\x65vent_triggers\x18\x04 \x03(\t\x12\x15\n\rcron_triggers\x18\x05 \x03(\t\x12!\n\x05tasks\x18\x06 \x03(\x0b\x32\x12.v1.CreateTaskOpts\x12$\n\x0b\x63oncurrency\x18\x07 \x01(\x0b\x32\x0f.v1.Concurrency\x12\x17\n\ncron_input\x18\x08 \x01(\tH\x00\x88\x01\x01\x12\x30\n\x0fon_failure_task\x18\t \x01(\x0b\x32\x12.v1.CreateTaskOptsH\x01\x88\x01\x01\x12\'\n\x06sticky\x18\n \x01(\x0e\x32\x12.v1.StickyStrategyH\x02\x88\x01\x01\x12\x1d\n\x10\x64\x65\x66\x61ult_priority\x18\x0b \x01(\x05H\x03\x88\x01\x01\x12(\n\x0f\x63oncurrency_arr\x18\x0c \x03(\x0b\x32\x0f.v1.Concurrency\x12*\n\x0f\x64\x65\x66\x61ult_filters\x18\r \x03(\x0b\x32\x11.v1.DefaultFilter\x12\x1e\n\x11input_json_schema\x18\x0e \x01(\x0cH\x04\x88\x01\x01\x12/\n\x0bidempotency\x18\x0f \x01(\x0b\x32\x15.v1.IdempotencyConfigH\x05\x88\x01\x01\x42\r\n\x0b_cron_inputB\x12\n\x10_on_failure_taskB\t\n\x07_stickyB\x13\n\x11_default_priorityB\x14\n\x12_input_json_schemaB\x0e\n\x0c_idempotency\"7\n\x11IdempotencyConfig\x12\x12\n\nexpression\x18\x01 \x01(\t\x12\x0e\n\x06ttl_ms\x18\x02 \x01(\x03\"=\n\x19IdempotencyCollisionError\x12 \n\x18\x65xisting_run_external_id\x18\x01 \x01(\t\"T\n\rDefaultFilter\x12\x12\n\nexpression\x18\x01 \x01(\t\x12\r\n\x05scope\x18\x02 \x01(\t\x12\x14\n\x07payload\x18\x03 \x01(\x0cH\x00\x88\x01\x01\x42\n\n\x08_payload\"\x93\x01\n\x0b\x43oncurrency\x12\x12\n\nexpression\x18\x01 \x01(\t\x12\x15\n\x08max_runs\x18\x02 \x01(\x05H\x00\x88\x01\x01\x12\x39\n\x0elimit_strategy\x18\x03 \x01(\x0e\x32\x1c.v1.ConcurrencyLimitStrategyH\x01\x88\x01\x01\x42\x0b\n\t_max_runsB\x11\n\x0f_limit_strategy\"\xb7\x05\n\x0e\x43reateTaskOpts\x12\x13\n\x0breadable_id\x18\x01 \x01(\t\x12\x0e\n\x06\x61\x63tion\x18\x02 \x01(\t\x12\x0f\n\x07timeout\x18\x03 \x01(\t\x12\x0e\n\x06inputs\x18\x04 \x01(\t\x12\x0f\n\x07parents\x18\x05 \x03(\t\x12\x0f\n\x07retries\x18\x06 \x01(\x05\x12,\n\x0brate_limits\x18\x07 \x03(\x0b\x32\x17.v1.CreateTaskRateLimit\x12;\n\rworker_labels\x18\x08 \x03(\x0b\x32$.v1.CreateTaskOpts.WorkerLabelsEntry\x12\x1b\n\x0e\x62\x61\x63koff_factor\x18\t \x01(\x02H\x00\x88\x01\x01\x12 \n\x13\x62\x61\x63koff_max_seconds\x18\n \x01(\x05H\x01\x88\x01\x01\x12$\n\x0b\x63oncurrency\x18\x0b \x03(\x0b\x32\x0f.v1.Concurrency\x12+\n\nconditions\x18\x0c \x01(\x0b\x32\x12.v1.TaskConditionsH\x02\x88\x01\x01\x12\x1d\n\x10schedule_timeout\x18\r \x01(\tH\x03\x88\x01\x01\x12\x12\n\nis_durable\x18\x0e \x01(\x08\x12;\n\rslot_requests\x18\x0f \x03(\x0b\x32$.v1.CreateTaskOpts.SlotRequestsEntry\x1aL\n\x11WorkerLabelsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12&\n\x05value\x18\x02 \x01(\x0b\x32\x17.v1.DesiredWorkerLabels:\x02\x38\x01\x1a\x33\n\x11SlotRequestsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x05:\x02\x38\x01\x42\x11\n\x0f_backoff_factorB\x16\n\x14_backoff_max_secondsB\r\n\x0b_conditionsB\x13\n\x11_schedule_timeout\"\xfd\x01\n\x13\x43reateTaskRateLimit\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x12\n\x05units\x18\x02 \x01(\x05H\x00\x88\x01\x01\x12\x15\n\x08key_expr\x18\x03 \x01(\tH\x01\x88\x01\x01\x12\x17\n\nunits_expr\x18\x04 \x01(\tH\x02\x88\x01\x01\x12\x1e\n\x11limit_values_expr\x18\x05 \x01(\tH\x03\x88\x01\x01\x12,\n\x08\x64uration\x18\x06 \x01(\x0e\x32\x15.v1.RateLimitDurationH\x04\x88\x01\x01\x42\x08\n\x06_unitsB\x0b\n\t_key_exprB\r\n\x0b_units_exprB\x14\n\x12_limit_values_exprB\x0b\n\t_duration\"@\n\x1d\x43reateWorkflowVersionResponse\x12\n\n\x02id\x18\x01 \x01(\t\x12\x13\n\x0bworkflow_id\x18\x02 \x01(\t\"+\n\x14GetRunDetailsRequest\x12\x13\n\x0b\x65xternal_id\x18\x01 \x01(\t\"\xaa\x01\n\rTaskRunDetail\x12\x13\n\x0b\x65xternal_id\x18\x01 \x01(\t\x12\x1d\n\x06status\x18\x02 \x01(\x0e\x32\r.v1.RunStatus\x12\x12\n\x05\x65rror\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x13\n\x06output\x18\x04 \x01(\x0cH\x01\x88\x01\x01\x12\x13\n\x0breadable_id\x18\x05 \x01(\t\x12\x12\n\nis_evicted\x18\x06 \x01(\x08\x42\x08\n\x06_errorB\t\n\x07_output\"\x84\x02\n\x15GetRunDetailsResponse\x12\r\n\x05input\x18\x01 \x01(\x0c\x12\x1d\n\x06status\x18\x02 \x01(\x0e\x32\r.v1.RunStatus\x12:\n\ttask_runs\x18\x03 \x03(\x0b\x32\'.v1.GetRunDetailsResponse.TaskRunsEntry\x12\x0c\n\x04\x64one\x18\x04 \x01(\x08\x12\x1b\n\x13\x61\x64\x64itional_metadata\x18\x05 \x01(\x0c\x12\x12\n\nis_evicted\x18\x06 \x01(\x08\x1a\x42\n\rTaskRunsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12 \n\x05value\x18\x02 \x01(\x0b\x32\x11.v1.TaskRunDetail:\x02\x38\x01*$\n\x0eStickyStrategy\x12\x08\n\x04SOFT\x10\x00\x12\x08\n\x04HARD\x10\x01*]\n\x11RateLimitDuration\x12\n\n\x06SECOND\x10\x00\x12\n\n\x06MINUTE\x10\x01\x12\x08\n\x04HOUR\x10\x02\x12\x07\n\x03\x44\x41Y\x10\x03\x12\x08\n\x04WEEK\x10\x04\x12\t\n\x05MONTH\x10\x05\x12\x08\n\x04YEAR\x10\x06*[\n\tRunStatus\x12\n\n\x06QUEUED\x10\x00\x12\x0b\n\x07RUNNING\x10\x01\x12\r\n\tCOMPLETED\x10\x02\x12\n\n\x06\x46\x41ILED\x10\x03\x12\r\n\tCANCELLED\x10\x04\x12\x0b\n\x07\x45VICTED\x10\x05*\x7f\n\x18\x43oncurrencyLimitStrategy\x12\x16\n\x12\x43\x41NCEL_IN_PROGRESS\x10\x00\x12\x0f\n\x0b\x44ROP_NEWEST\x10\x01\x12\x10\n\x0cQUEUE_NEWEST\x10\x02\x12\x15\n\x11GROUP_ROUND_ROBIN\x10\x03\x12\x11\n\rCANCEL_NEWEST\x10\x04\x32\xcf\x03\n\x0c\x41\x64minService\x12R\n\x0bPutWorkflow\x12 .v1.CreateWorkflowVersionRequest\x1a!.v1.CreateWorkflowVersionResponse\x12>\n\x0b\x43\x61ncelTasks\x12\x16.v1.CancelTasksRequest\x1a\x17.v1.CancelTasksResponse\x12>\n\x0bReplayTasks\x12\x16.v1.ReplayTasksRequest\x1a\x17.v1.ReplayTasksResponse\x12S\n\x12TriggerWorkflowRun\x12\x1d.v1.TriggerWorkflowRunRequest\x1a\x1e.v1.TriggerWorkflowRunResponse\x12\x44\n\rGetRunDetails\x12\x18.v1.GetRunDetailsRequest\x1a\x19.v1.GetRunDetailsResponse\x12P\n\x11\x42ranchDurableTask\x12\x1c.v1.BranchDurableTaskRequest\x1a\x1d.v1.BranchDurableTaskResponseBBZ@github.com/hatchet-dev/hatchet/internal/services/shared/proto/v1b\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -43,14 +43,14 @@ _globals['_CREATETASKOPTS_SLOTREQUESTSENTRY']._serialized_options = b'8\001' _globals['_GETRUNDETAILSRESPONSE_TASKRUNSENTRY']._loaded_options = None _globals['_GETRUNDETAILSRESPONSE_TASKRUNSENTRY']._serialized_options = b'8\001' - _globals['_STICKYSTRATEGY']._serialized_start=3533 - _globals['_STICKYSTRATEGY']._serialized_end=3569 - _globals['_RATELIMITDURATION']._serialized_start=3571 - _globals['_RATELIMITDURATION']._serialized_end=3664 - _globals['_RUNSTATUS']._serialized_start=3666 - _globals['_RUNSTATUS']._serialized_end=3757 - _globals['_CONCURRENCYLIMITSTRATEGY']._serialized_start=3759 - _globals['_CONCURRENCYLIMITSTRATEGY']._serialized_end=3886 + _globals['_STICKYSTRATEGY']._serialized_start=3596 + _globals['_STICKYSTRATEGY']._serialized_end=3632 + _globals['_RATELIMITDURATION']._serialized_start=3634 + _globals['_RATELIMITDURATION']._serialized_end=3727 + _globals['_RUNSTATUS']._serialized_start=3729 + _globals['_RUNSTATUS']._serialized_end=3820 + _globals['_CONCURRENCYLIMITSTRATEGY']._serialized_start=3822 + _globals['_CONCURRENCYLIMITSTRATEGY']._serialized_end=3949 _globals['_CANCELTASKSREQUEST']._serialized_start=111 _globals['_CANCELTASKSREQUEST']._serialized_end=202 _globals['_REPLAYTASKSREQUEST']._serialized_start=204 @@ -75,28 +75,30 @@ _globals['_CREATEWORKFLOWVERSIONREQUEST']._serialized_end=1737 _globals['_IDEMPOTENCYCONFIG']._serialized_start=1739 _globals['_IDEMPOTENCYCONFIG']._serialized_end=1794 - _globals['_DEFAULTFILTER']._serialized_start=1796 - _globals['_DEFAULTFILTER']._serialized_end=1880 - _globals['_CONCURRENCY']._serialized_start=1883 - _globals['_CONCURRENCY']._serialized_end=2030 - _globals['_CREATETASKOPTS']._serialized_start=2033 - _globals['_CREATETASKOPTS']._serialized_end=2728 - _globals['_CREATETASKOPTS_WORKERLABELSENTRY']._serialized_start=2520 - _globals['_CREATETASKOPTS_WORKERLABELSENTRY']._serialized_end=2596 - _globals['_CREATETASKOPTS_SLOTREQUESTSENTRY']._serialized_start=2598 - _globals['_CREATETASKOPTS_SLOTREQUESTSENTRY']._serialized_end=2649 - _globals['_CREATETASKRATELIMIT']._serialized_start=2731 - _globals['_CREATETASKRATELIMIT']._serialized_end=2984 - _globals['_CREATEWORKFLOWVERSIONRESPONSE']._serialized_start=2986 - _globals['_CREATEWORKFLOWVERSIONRESPONSE']._serialized_end=3050 - _globals['_GETRUNDETAILSREQUEST']._serialized_start=3052 - _globals['_GETRUNDETAILSREQUEST']._serialized_end=3095 - _globals['_TASKRUNDETAIL']._serialized_start=3098 - _globals['_TASKRUNDETAIL']._serialized_end=3268 - _globals['_GETRUNDETAILSRESPONSE']._serialized_start=3271 - _globals['_GETRUNDETAILSRESPONSE']._serialized_end=3531 - _globals['_GETRUNDETAILSRESPONSE_TASKRUNSENTRY']._serialized_start=3465 - _globals['_GETRUNDETAILSRESPONSE_TASKRUNSENTRY']._serialized_end=3531 - _globals['_ADMINSERVICE']._serialized_start=3889 - _globals['_ADMINSERVICE']._serialized_end=4352 + _globals['_IDEMPOTENCYCOLLISIONERROR']._serialized_start=1796 + _globals['_IDEMPOTENCYCOLLISIONERROR']._serialized_end=1857 + _globals['_DEFAULTFILTER']._serialized_start=1859 + _globals['_DEFAULTFILTER']._serialized_end=1943 + _globals['_CONCURRENCY']._serialized_start=1946 + _globals['_CONCURRENCY']._serialized_end=2093 + _globals['_CREATETASKOPTS']._serialized_start=2096 + _globals['_CREATETASKOPTS']._serialized_end=2791 + _globals['_CREATETASKOPTS_WORKERLABELSENTRY']._serialized_start=2583 + _globals['_CREATETASKOPTS_WORKERLABELSENTRY']._serialized_end=2659 + _globals['_CREATETASKOPTS_SLOTREQUESTSENTRY']._serialized_start=2661 + _globals['_CREATETASKOPTS_SLOTREQUESTSENTRY']._serialized_end=2712 + _globals['_CREATETASKRATELIMIT']._serialized_start=2794 + _globals['_CREATETASKRATELIMIT']._serialized_end=3047 + _globals['_CREATEWORKFLOWVERSIONRESPONSE']._serialized_start=3049 + _globals['_CREATEWORKFLOWVERSIONRESPONSE']._serialized_end=3113 + _globals['_GETRUNDETAILSREQUEST']._serialized_start=3115 + _globals['_GETRUNDETAILSREQUEST']._serialized_end=3158 + _globals['_TASKRUNDETAIL']._serialized_start=3161 + _globals['_TASKRUNDETAIL']._serialized_end=3331 + _globals['_GETRUNDETAILSRESPONSE']._serialized_start=3334 + _globals['_GETRUNDETAILSRESPONSE']._serialized_end=3594 + _globals['_GETRUNDETAILSRESPONSE_TASKRUNSENTRY']._serialized_start=3528 + _globals['_GETRUNDETAILSRESPONSE_TASKRUNSENTRY']._serialized_end=3594 + _globals['_ADMINSERVICE']._serialized_start=3952 + _globals['_ADMINSERVICE']._serialized_end=4415 # @@protoc_insertion_point(module_scope) diff --git a/sdks/python/hatchet_sdk/contracts/v1/workflows_pb2.pyi b/sdks/python/hatchet_sdk/contracts/v1/workflows_pb2.pyi index 2a870a90e8..c262cb21a7 100644 --- a/sdks/python/hatchet_sdk/contracts/v1/workflows_pb2.pyi +++ b/sdks/python/hatchet_sdk/contracts/v1/workflows_pb2.pyi @@ -195,6 +195,12 @@ class IdempotencyConfig(_message.Message): ttl_ms: int def __init__(self, expression: _Optional[str] = ..., ttl_ms: _Optional[int] = ...) -> None: ... +class IdempotencyCollisionError(_message.Message): + __slots__ = ("existing_run_external_id",) + EXISTING_RUN_EXTERNAL_ID_FIELD_NUMBER: _ClassVar[int] + existing_run_external_id: str + def __init__(self, existing_run_external_id: _Optional[str] = ...) -> None: ... + class DefaultFilter(_message.Message): __slots__ = ("expression", "scope", "payload") EXPRESSION_FIELD_NUMBER: _ClassVar[int] diff --git a/sdks/python/hatchet_sdk/exceptions.py b/sdks/python/hatchet_sdk/exceptions.py index 0b383da9b3..ff1b8e37bc 100644 --- a/sdks/python/hatchet_sdk/exceptions.py +++ b/sdks/python/hatchet_sdk/exceptions.py @@ -37,6 +37,12 @@ class DedupeViolationError(Exception): """Raised by the Hatchet library to indicate that a workflow has already been run with this deduplication value.""" +class IdempotencyCollisionError(Exception): + def __init__(self, existing_run_external_id: str) -> None: + super().__init__() + self.existing_run_external_id = existing_run_external_id + + TASK_RUN_ERROR_METADATA_KEY = "__hatchet_error_metadata__" diff --git a/sdks/python/poetry.lock b/sdks/python/poetry.lock index 30fffa47e2..739e24f149 100644 --- a/sdks/python/poetry.lock +++ b/sdks/python/poetry.lock @@ -981,10 +981,10 @@ dev = ["flake8", "markdown", "twine", "wheel"] name = "googleapis-common-protos" version = "1.73.1" description = "Common protobufs used in Google APIs" -optional = true +optional = false python-versions = ">=3.9" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"otel\"" +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "googleapis_common_protos-1.73.1-py3-none-any.whl", hash = "sha256:e51f09eb0a43a8602f5a915870972e6b4a394088415c79d79605a46d8e826ee8"}, {file = "googleapis_common_protos-1.73.1.tar.gz", hash = "sha256:13114f0e9d2391756a0194c3a8131974ed7bffb06086569ba193364af59163b6"}, @@ -1090,6 +1090,24 @@ typing-extensions = ">=4.12,<5.0" [package.extras] protobuf = ["grpcio-tools (>=1.80.0)"] +[[package]] +name = "grpcio-status" +version = "1.80.0" +description = "Status proto mapping for gRPC" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" +files = [ + {file = "grpcio_status-1.80.0-py3-none-any.whl", hash = "sha256:4b56990363af50dbf2c2ebb80f1967185c07d87aa25aa2bea45ddb75fc181dbe"}, + {file = "grpcio_status-1.80.0.tar.gz", hash = "sha256:df73802a4c89a3ea88aa2aff971e886fccce162bc2e6511408b3d67a144381cd"}, +] + +[package.dependencies] +googleapis-common-protos = ">=1.5.5" +grpcio = ">=1.80.0" +protobuf = ">=6.31.1,<7.0.0" + [[package]] name = "grpcio-tools" version = "1.80.0" @@ -4186,4 +4204,4 @@ test = ["psycopg", "pytest", "pytest-asyncio", "pytest-env", "pytest-retry", "py [metadata] lock-version = "2.1" python-versions = ">=3.10,<4" -content-hash = "73f2ca460e9a535c8fdc75828b23a7abfc7bf2e5001c83496f874d7b78c79756" +content-hash = "fef9e81d40adf96f67d0fb2a5dd55ceb03af16dfc40cca4b4f6d961c1386cb63" diff --git a/sdks/python/pyproject.toml b/sdks/python/pyproject.toml index 1450ee3991..32b766e7c0 100644 --- a/sdks/python/pyproject.toml +++ b/sdks/python/pyproject.toml @@ -16,6 +16,7 @@ authors = [ dependencies = [ "grpcio>=1.76.0,<2", "grpcio-tools>=1.76.0,<2", + "grpcio-status>=1.76.0,<2", "protobuf>=6.33.5,<7", "pydantic>=2.13.4,<3", "python-dateutil>=2.9.0.post0,<3", diff --git a/sdks/python/uv.lock b/sdks/python/uv.lock index c639c67de5..c3434ecfca 100644 --- a/sdks/python/uv.lock +++ b/sdks/python/uv.lock @@ -916,7 +916,7 @@ wheels = [ [[package]] name = "hatchet-sdk" -version = "1.33.5" +version = "1.33.6" source = { editable = "." } dependencies = [ { name = "aiohttp" }, From 7a7e5f86e2e31590a9afde271da96eb46a3cf1f8 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Mon, 1 Jun 2026 17:12:35 -0400 Subject: [PATCH 25/69] fix: use test run id to dedupe across test runs --- sdks/python/examples/idempotency/test_idempotency.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sdks/python/examples/idempotency/test_idempotency.py b/sdks/python/examples/idempotency/test_idempotency.py index d10a0f3514..3a2e61c31d 100644 --- a/sdks/python/examples/idempotency/test_idempotency.py +++ b/sdks/python/examples/idempotency/test_idempotency.py @@ -4,17 +4,19 @@ from hatchet_sdk import Hatchet from hatchet_sdk.exceptions import IdempotencyCollisionError +from uuid import uuid4 @pytest.mark.asyncio(loop_scope="session") async def test_idempotency_keys_prevent_duplicate_runs(hatchet: Hatchet) -> None: + test_run_id = str(uuid4()) ref1 = await idempotent_task.aio_run( - input=IdempotencyInput(id="123"), wait_for_result=False + input=IdempotencyInput(id=test_run_id), wait_for_result=False ) with pytest.raises(IdempotencyCollisionError) as exc_info: await idempotent_task.aio_run( - input=IdempotencyInput(id="123"), wait_for_result=False + input=IdempotencyInput(id=test_run_id), wait_for_result=False ) assert exc_info.value.existing_run_external_id == ref1.workflow_run_id From a61c85881ed11336d9b01572c1c530fa065d4077 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Mon, 1 Jun 2026 17:24:53 -0400 Subject: [PATCH 26/69] fix: improve test a bit --- .../examples/idempotency/test_idempotency.py | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/sdks/python/examples/idempotency/test_idempotency.py b/sdks/python/examples/idempotency/test_idempotency.py index 3a2e61c31d..537746409b 100644 --- a/sdks/python/examples/idempotency/test_idempotency.py +++ b/sdks/python/examples/idempotency/test_idempotency.py @@ -2,16 +2,20 @@ from examples.idempotency.worker import idempotent_task, IdempotencyInput -from hatchet_sdk import Hatchet -from hatchet_sdk.exceptions import IdempotencyCollisionError +from hatchet_sdk import Hatchet, IdempotencyCollisionError +from hatchet_sdk.clients.rest.models.v1_task_summary_list import V1TaskSummaryList from uuid import uuid4 +from datetime import timedelta, datetime, timezone +import asyncio @pytest.mark.asyncio(loop_scope="session") async def test_idempotency_keys_prevent_duplicate_runs(hatchet: Hatchet) -> None: test_run_id = str(uuid4()) ref1 = await idempotent_task.aio_run( - input=IdempotencyInput(id=test_run_id), wait_for_result=False + input=IdempotencyInput(id=test_run_id), + wait_for_result=False, + additional_metadata={"test_run_id": test_run_id}, ) with pytest.raises(IdempotencyCollisionError) as exc_info: @@ -20,3 +24,21 @@ async def test_idempotency_keys_prevent_duplicate_runs(hatchet: Hatchet) -> None ) assert exc_info.value.existing_run_external_id == ref1.workflow_run_id + + runs: V1TaskSummaryList | None = None + + for _ in range(15): + runs = await hatchet.runs.aio_list( + since=datetime.now(timezone.utc) - timedelta(minutes=5), + additional_metadata={"test_run_id": test_run_id}, + ) + + if len(runs.rows) == 0: + await asyncio.sleep(1) + continue + + break + + assert runs is not None + assert len(runs.rows) == 1 + assert runs.rows[0].metadata.id == ref1.workflow_run_id From 3ceb405f6a2e309638a23332413a1f7ac84f386a Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Tue, 2 Jun 2026 09:35:55 -0400 Subject: [PATCH 27/69] chore: lock --- sdks/python/poetry.lock | 313 +++++++++++++++++++++++----------------- 1 file changed, 178 insertions(+), 135 deletions(-) diff --git a/sdks/python/poetry.lock b/sdks/python/poetry.lock index 6649897947..6b70b710d9 100644 --- a/sdks/python/poetry.lock +++ b/sdks/python/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.4.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.0.0 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -7,6 +7,7 @@ description = "Happy Eyeballs for asyncio" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, @@ -19,6 +20,7 @@ description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "aiohttp-3.13.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:02222e7e233295f40e011c1b00e3b0bd451f22cf853a0304c3595633ee47da4b"}, {file = "aiohttp-3.13.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bace460460ed20614fa6bc8cb09966c0b8517b8c58ad8046828c6078d25333b5"}, @@ -153,7 +155,7 @@ propcache = ">=0.2.0" yarl = ">=1.17.0,<2.0" [package.extras] -speedups = ["Brotli (>=1.2) ; platform_python_implementation == \"CPython\"", "aiodns (>=3.3.0)", "backports.zstd ; platform_python_implementation == \"CPython\" and python_version < \"3.14\"", "brotlicffi (>=1.2) ; platform_python_implementation != \"CPython\""] +speedups = ["Brotli (>=1.2)", "aiodns (>=3.3.0)", "backports.zstd", "brotlicffi (>=1.2)"] [[package]] name = "aiosignal" @@ -162,6 +164,7 @@ description = "aiosignal: a list of registered asynchronous callbacks" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e"}, {file = "aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7"}, @@ -178,7 +181,7 @@ description = "Document parameters, class attributes, return types, and variable optional = true python-versions = ">=3.8" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320"}, {file = "annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4"}, @@ -191,6 +194,7 @@ description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -203,7 +207,7 @@ description = "High-level concurrency and networking framework on top of asyncio optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"openai\" or extra == \"claude\" or extra == \"docs\"" +markers = "(extra == \"docs\" or extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708"}, {file = "anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc"}, @@ -224,7 +228,7 @@ description = "Timeout context manager for asyncio programs" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version == \"3.10\"" +markers = "python_version < \"3.11\"" files = [ {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, @@ -237,6 +241,7 @@ description = "Classes Without Boilerplate" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309"}, {file = "attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32"}, @@ -249,7 +254,7 @@ description = "Backport of asyncio.Runner, a context manager that controls event optional = true python-versions = "<3.11,>=3.8" groups = ["main"] -markers = "extra == \"test\" and python_version == \"3.10\"" +markers = "extra == \"test\" and python_version < \"3.11\"" files = [ {file = "backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5"}, {file = "backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162"}, @@ -262,7 +267,7 @@ description = "Screen-scraping library" optional = true python-versions = ">=3.7.0" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb"}, {file = "beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86"}, @@ -286,7 +291,7 @@ description = "The uncompromising code formatter." optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"lint\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"lint\"" files = [ {file = "black-26.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:86a8b5035fce64f5dcd1b794cf8ec4d31fe458cf6ce3986a30deb434df82a1d2"}, {file = "black-26.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5602bdb96d52d2d0672f24f6ffe5218795736dd34807fd0fd55ccd6bf206168b"}, @@ -331,7 +336,7 @@ typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} colorama = ["colorama (>=0.4.3)"] d = ["aiohttp (>=3.10)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2) ; sys_platform != \"win32\"", "winloop (>=0.5.0) ; sys_platform == \"win32\""] +uvloop = ["uvloop (>=0.15.2)", "winloop (>=0.5.0)"] [[package]] name = "boto3" @@ -340,7 +345,7 @@ description = "The AWS SDK for Python" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"test\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"test\"" files = [ {file = "boto3-1.43.4-py3-none-any.whl", hash = "sha256:c4910b54c1f2401ab55d9fbca3d92df0a68e4ac64a69f2bab47a079e384a9d4f"}, {file = "boto3-1.43.4.tar.gz", hash = "sha256:00f40e9ca704c0f860b70ef47b042813c8d9a66b3d5e7bf81d578f79ae17dcd3"}, @@ -361,7 +366,7 @@ description = "Low-level, data-driven core of boto 3." optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"test\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"test\"" files = [ {file = "botocore-1.43.4-py3-none-any.whl", hash = "sha256:f1897d3254965cacac7514cfed1b6ff016166b165ee2e06232b4a3954cf9d713"}, {file = "botocore-1.43.4.tar.gz", hash = "sha256:1a95c90fa9ca6ee85c1a02b04c8e05e948661d201b85b443000d676857905019"}, @@ -382,7 +387,7 @@ description = "Dummy package for Beautiful Soup (beautifulsoup4)" optional = true python-versions = "*" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "bs4-0.0.2-py2.py3-none-any.whl", hash = "sha256:abf8742c0805ef7f662dce4b51cca104cffe52b835238afc169142ab9b3fbccc"}, {file = "bs4-0.0.2.tar.gz", hash = "sha256:a48685c58f50fe127722417bae83fe6badf500d54b55f7e39ffe43b798653925"}, @@ -398,7 +403,7 @@ description = "Python package for providing Mozilla's CA Bundle." optional = true python-versions = ">=3.7" groups = ["main"] -markers = "extra == \"openai\" or extra == \"claude\" or extra == \"docs\" or extra == \"otel\" or extra == \"test\"" +markers = "(extra == \"otel\" or extra == \"test\" or extra == \"openai\" or extra == \"docs\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa"}, {file = "certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7"}, @@ -411,7 +416,7 @@ description = "Foreign Function Interface for Python calling C code." optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(extra == \"openai\" or extra == \"claude\") and platform_python_implementation != \"PyPy\"" +markers = "(extra == \"openai\" or extra == \"claude\") and platform_python_implementation != \"PyPy\" and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44"}, {file = "cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49"}, @@ -509,7 +514,7 @@ description = "The Real First Universal Charset Detector. Open, modern and activ optional = true python-versions = ">=3.7" groups = ["main"] -markers = "extra == \"openai\" or extra == \"otel\" or extra == \"test\"" +markers = "(extra == \"otel\" or extra == \"test\" or extra == \"openai\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "charset_normalizer-3.4.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2e1d8ca8611099001949d1cdfaefc510cf0f212484fe7c565f735b68c78c3c95"}, {file = "charset_normalizer-3.4.6-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e25369dc110d58ddf29b949377a93e0716d72a24f62bad72b2b39f155949c1fd"}, @@ -649,7 +654,7 @@ description = "Python SDK for Claude Code" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"claude\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"claude\"" files = [ {file = "claude_agent_sdk-0.1.55-py3-none-macosx_11_0_arm64.whl", hash = "sha256:70fa9f20f9b3a632d25d2666b533505a754d36429a4ad91a465eee87ac5798f7"}, {file = "claude_agent_sdk-0.1.55-py3-none-macosx_11_0_x86_64.whl", hash = "sha256:01c4ed18aa3dd38a5c1a37fc5a775549abc9d6658bad65a69e36dafbbecd3e29"}, @@ -674,7 +679,7 @@ description = "Composable command line interface toolkit" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "sys_platform != \"emscripten\" and (extra == \"openai\" or extra == \"claude\") or extra == \"docs\" or extra == \"lint\"" +markers = "(extra == \"lint\" or extra == \"docs\" or extra == \"openai\" or extra == \"claude\") and (extra == \"lint\" or extra == \"docs\" or sys_platform != \"emscripten\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "click-8.3.3-py3-none-any.whl", hash = "sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613"}, {file = "click-8.3.3.tar.gz", hash = "sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2"}, @@ -690,7 +695,7 @@ description = "Cross-platform colored terminal text." optional = true python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" groups = ["main"] -markers = "(extra == \"openai\" or extra == \"claude\" or extra == \"docs\" or extra == \"lint\") and sys_platform != \"emscripten\" and platform_system == \"Windows\" or (extra == \"docs\" or extra == \"lint\" or extra == \"openai\") and platform_system == \"Windows\" or extra == \"test\" and sys_platform == \"win32\"" +markers = "(extra == \"lint\" or extra == \"test\" or extra == \"docs\" or extra == \"openai\" or extra == \"claude\") and (platform_system == \"Windows\" or extra == \"test\") and (extra == \"lint\" or sys_platform != \"emscripten\" or extra == \"docs\" or extra == \"openai\") and (python_version <= \"3.13\" or python_version >= \"3.14\") and (extra == \"lint\" or extra == \"docs\" or extra == \"openai\" or extra == \"claude\" or sys_platform == \"win32\") and (platform_system == \"Windows\" or sys_platform == \"win32\")" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -703,7 +708,7 @@ description = "cryptography is a package which provides cryptographic recipes an optional = true python-versions = "!=3.9.0,!=3.9.1,>=3.9" groups = ["main"] -markers = "extra == \"openai\" or extra == \"claude\"" +markers = "(extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "cryptography-48.0.0-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:0c558d2cdffd8f4bbb30fc7134c74d2ca9a476f830bb053074498fbc86f41ed6"}, {file = "cryptography-48.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f5333311663ea94f75dd408665686aaf426563556bb5283554a3539177e03b8c"}, @@ -758,7 +763,7 @@ files = [ [package.dependencies] cffi = {version = ">=2.0.0", markers = "platform_python_implementation != \"PyPy\""} -typing-extensions = {version = ">=4.13.2", markers = "python_full_version < \"3.11.0\""} +typing-extensions = {version = ">=4.13.2", markers = "python_full_version < \"3.11\""} [package.extras] ssh = ["bcrypt (>=3.1.5)"] @@ -770,7 +775,7 @@ description = "Distro - an OS platform information API" optional = true python-versions = ">=3.6" groups = ["main"] -markers = "extra == \"docs\" or extra == \"openai\"" +markers = "(extra == \"docs\" or extra == \"openai\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, @@ -783,14 +788,14 @@ description = "Parse Python docstrings in reST, Google and Numpydoc format" optional = true python-versions = ">=3.8" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "docstring_parser_fork-0.0.14-py3-none-any.whl", hash = "sha256:4c544f234ef2cc2749a3df32b70c437d77888b1099143a1ad5454452c574b9af"}, {file = "docstring_parser_fork-0.0.14.tar.gz", hash = "sha256:a2743a63d8d36c09650594f7b4ab5b2758fee8629dcf794d1b221b23179baa5c"}, ] [package.extras] -dev = ["docstring-parser[docs]", "docstring-parser[test]", "pre-commit (>=2.16.0) ; python_version >= \"3.9\""] +dev = ["docstring-parser[docs]", "docstring-parser[test]", "pre-commit (>=2.16.0)"] docs = ["pydoctor (>=25.4.0)"] test = ["pytest"] @@ -801,7 +806,7 @@ description = "Backport of PEP 654 (exception groups)" optional = true python-versions = ">=3.7" groups = ["main"] -markers = "(extra == \"openai\" or extra == \"claude\" or extra == \"docs\" or extra == \"test\") and python_version == \"3.10\"" +markers = "(extra == \"test\" or extra == \"docs\" or extra == \"openai\" or extra == \"claude\") and python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598"}, {file = "exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219"}, @@ -820,7 +825,7 @@ description = "execnet: rapid multi-Python deployment" optional = true python-versions = ">=3.8" groups = ["main"] -markers = "extra == \"test\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"test\"" files = [ {file = "execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec"}, {file = "execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd"}, @@ -836,7 +841,7 @@ description = "FastAPI framework, high performance, easy to learn, fast to code, optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "fastapi-0.136.1-py3-none-any.whl", hash = "sha256:a6e9d7eeada96c93a4d69cb03836b44fa34e2854accb7244a1ece36cd4781c3f"}, {file = "fastapi-0.136.1.tar.gz", hash = "sha256:7af665ad7acfa0a3baf8983d393b6b471b9da10ede59c60045f49fbc89a0fa7f"}, @@ -861,6 +866,7 @@ description = "A list-like structure which implements collections.abc.MutableSeq optional = false python-versions = ">=3.9" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "frozenlist-1.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b37f6d31b3dcea7deb5e9696e529a6aa4a898adc33db82da12e4c60a7c4d2011"}, {file = "frozenlist-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef2b7b394f208233e471abc541cc6991f907ffd47dc72584acee3147899d6565"}, @@ -1001,7 +1007,7 @@ description = "Copy your docs directly to the gh-pages branch." optional = true python-versions = "*" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, @@ -1017,10 +1023,10 @@ dev = ["flake8", "markdown", "twine", "wheel"] name = "googleapis-common-protos" version = "1.73.1" description = "Common protobufs used in Google APIs" -optional = true +optional = false python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"otel\"" +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "googleapis_common_protos-1.73.1-py3-none-any.whl", hash = "sha256:e51f09eb0a43a8602f5a915870972e6b4a394088415c79d79605a46d8e826ee8"}, {file = "googleapis_common_protos-1.73.1.tar.gz", hash = "sha256:13114f0e9d2391756a0194c3a8131974ed7bffb06086569ba193364af59163b6"}, @@ -1039,7 +1045,7 @@ description = "Signatures for entire Python programs. Extract the structure, the optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"docs\" or extra == \"openai\"" +markers = "(extra == \"docs\" or extra == \"openai\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "griffelib-2.0.2-py3-none-any.whl", hash = "sha256:925c857658fb1ba40c0772c37acbc2ab650bd794d9c1b9726922e36ea4117ea1"}, {file = "griffelib-2.0.2.tar.gz", hash = "sha256:3cf20b3bc470e83763ffbf236e0076b1211bac1bc67de13daf494640f2de707e"}, @@ -1055,6 +1061,7 @@ description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "grpcio-1.80.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:886457a7768e408cdce226ad1ca67d2958917d306523a0e21e1a2fdaa75c9c9c"}, {file = "grpcio-1.80.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:7b641fc3f1dc647bfd80bd713addc68f6d145956f64677e56d9ebafc0bd72388"}, @@ -1125,6 +1132,24 @@ typing-extensions = ">=4.12,<5.0" [package.extras] protobuf = ["grpcio-tools (>=1.80.0)"] +[[package]] +name = "grpcio-status" +version = "1.80.0" +description = "Status proto mapping for gRPC" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" +files = [ + {file = "grpcio_status-1.80.0-py3-none-any.whl", hash = "sha256:4b56990363af50dbf2c2ebb80f1967185c07d87aa25aa2bea45ddb75fc181dbe"}, + {file = "grpcio_status-1.80.0.tar.gz", hash = "sha256:df73802a4c89a3ea88aa2aff971e886fccce162bc2e6511408b3d67a144381cd"}, +] + +[package.dependencies] +googleapis-common-protos = ">=1.5.5" +grpcio = ">=1.80.0" +protobuf = ">=6.31.1,<7.0.0" + [[package]] name = "grpcio-tools" version = "1.80.0" @@ -1132,6 +1157,7 @@ description = "Protobuf code generator for gRPC" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "grpcio_tools-1.80.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:727477b9afa4b53f5ec70cafb41c3965d893835e0d4ea9b542fe3d0d005602bf"}, {file = "grpcio_tools-1.80.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:85fe8d15f146c62cb76f38d963e256392d287442b9232717d30ae9e3bbda9bc3"}, @@ -1208,7 +1234,7 @@ description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = true python-versions = ">=3.8" groups = ["main"] -markers = "extra == \"openai\" or extra == \"claude\" or extra == \"docs\"" +markers = "(extra == \"docs\" or extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, @@ -1221,7 +1247,7 @@ description = "A minimal low-level HTTP client." optional = true python-versions = ">=3.8" groups = ["main"] -markers = "extra == \"openai\" or extra == \"claude\" or extra == \"docs\"" +markers = "(extra == \"docs\" or extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, @@ -1244,7 +1270,7 @@ description = "The next generation HTTP client." optional = true python-versions = ">=3.8" groups = ["main"] -markers = "extra == \"openai\" or extra == \"claude\" or extra == \"docs\"" +markers = "(extra == \"docs\" or extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, @@ -1257,7 +1283,7 @@ httpcore = "==1.*" idna = "*" [package.extras] -brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] +brotli = ["brotli", "brotlicffi"] cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] @@ -1270,7 +1296,7 @@ description = "Consume Server-Sent Event (SSE) messages with HTTPX." optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"openai\" or extra == \"claude\"" +markers = "(extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc"}, {file = "httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d"}, @@ -1283,6 +1309,7 @@ description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.8" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"}, {file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"}, @@ -1298,7 +1325,7 @@ description = "Read metadata from Python packages" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"otel\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"otel\"" files = [ {file = "importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151"}, {file = "importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb"}, @@ -1308,13 +1335,13 @@ files = [ zipp = ">=3.20" [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=3.4)"] perf = ["ipython"] test = ["flufl.flake8", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] -type = ["mypy (<1.19) ; platform_python_implementation == \"PyPy\"", "pytest-mypy (>=1.0.1)"] +type = ["mypy (<1.19)", "pytest-mypy (>=1.0.1)"] [[package]] name = "iniconfig" @@ -1323,7 +1350,7 @@ description = "brain-dead simple config-ini parsing" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"test\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"test\"" files = [ {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, @@ -1336,7 +1363,7 @@ description = "A very fast and expressive template engine." optional = true python-versions = ">=3.7" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, @@ -1355,7 +1382,7 @@ description = "Fast iterable JSON parser." optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"docs\" or extra == \"openai\"" +markers = "(extra == \"docs\" or extra == \"openai\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "jiter-0.13.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2ffc63785fd6c7977defe49b9824ae6ce2b2e2b77ce539bdaf006c26da06342e"}, {file = "jiter-0.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4a638816427006c1e3f0013eb66d391d7a3acda99a7b0cf091eff4497ccea33a"}, @@ -1468,7 +1495,7 @@ description = "JSON Matching Expressions" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"test\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"test\"" files = [ {file = "jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64"}, {file = "jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d"}, @@ -1481,7 +1508,7 @@ description = "An implementation of JSON Schema validation for Python" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"openai\" or extra == \"claude\"" +markers = "(extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce"}, {file = "jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326"}, @@ -1489,7 +1516,7 @@ files = [ [package.dependencies] attrs = ">=22.2.0" -jsonschema-specifications = ">=2023.3.6" +jsonschema-specifications = ">=2023.03.6" referencing = ">=0.28.4" rpds-py = ">=0.25.0" @@ -1504,7 +1531,7 @@ description = "The JSON Schema meta-schemas and vocabularies, exposed as a Regis optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"openai\" or extra == \"claude\"" +markers = "(extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe"}, {file = "jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d"}, @@ -1520,7 +1547,7 @@ description = "Mypyc runtime library" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"lint\" and platform_python_implementation != \"PyPy\"" +markers = "extra == \"lint\" and platform_python_implementation != \"PyPy\" and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "librt-0.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:81fd938344fecb9373ba1b155968c8a329491d2ce38e7ddb76f30ffb938f12dc"}, {file = "librt-0.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5db05697c82b3a2ec53f6e72b2ed373132b0c2e05135f0696784e97d7f5d48e7"}, @@ -1621,7 +1648,7 @@ description = "Python implementation of John Gruber's Markdown." optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36"}, {file = "markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950"}, @@ -1638,7 +1665,7 @@ description = "Convert HTML to markdown." optional = true python-versions = "*" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "markdownify-1.2.2-py3-none-any.whl", hash = "sha256:3f02d3cc52714084d6e589f70397b6fc9f2f3a8531481bf35e8cc39f975e186a"}, {file = "markdownify-1.2.2.tar.gz", hash = "sha256:b274f1b5943180b031b699b199cbaeb1e2ac938b75851849a31fd0c3d6603d09"}, @@ -1655,7 +1682,7 @@ description = "Safely add untrusted strings to HTML/XML markup." optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"}, {file = "markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"}, @@ -1755,7 +1782,7 @@ description = "Model Context Protocol SDK" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"openai\" or extra == \"claude\"" +markers = "(extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "mcp-1.27.0-py3-none-any.whl", hash = "sha256:5ce1fa81614958e267b21fb2aa34e0aea8e2c6ede60d52aba45fd47246b4d741"}, {file = "mcp-1.27.0.tar.gz", hash = "sha256:d3dc35a7eec0d458c1da4976a48f982097ddaab87e278c5511d5a4a56e852b83"}, @@ -1789,7 +1816,7 @@ description = "A deep merge function for 🐍." optional = true python-versions = ">=3.6" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, @@ -1802,7 +1829,7 @@ description = "Project documentation with Markdown." optional = true python-versions = ">=3.8" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e"}, {file = "mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2"}, @@ -1825,7 +1852,7 @@ watchdog = ">=2.0" [package.extras] i18n = ["babel (>=2.9.0)"] -min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4) ; platform_system == \"Windows\"", "ghp-import (==1.0)", "importlib-metadata (==4.4) ; python_version < \"3.10\"", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"] +min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.4)", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"] [[package]] name = "mkdocs-autorefs" @@ -1834,7 +1861,7 @@ description = "Automatically link across pages in MkDocs." optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "mkdocs_autorefs-1.4.4-py3-none-any.whl", hash = "sha256:834ef5408d827071ad1bc69e0f39704fa34c7fc05bc8e1c72b227dfdc5c76089"}, {file = "mkdocs_autorefs-1.4.4.tar.gz", hash = "sha256:d54a284f27a7346b9c38f1f852177940c222da508e66edc816a0fa55fc6da197"}, @@ -1852,7 +1879,7 @@ description = "An extra command for MkDocs that infers required PyPI packages fr optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "mkdocs_get_deps-0.2.2-py3-none-any.whl", hash = "sha256:e7878cbeac04860b8b5e0ca31d3abad3df9411a75a32cde82f8e44b6c16ff650"}, {file = "mkdocs_get_deps-0.2.2.tar.gz", hash = "sha256:8ee8d5f316cdbbb2834bc1df6e69c08fe769a83e040060de26d3c19fad3599a1"}, @@ -1870,7 +1897,7 @@ description = "Automatic documentation from sources, for MkDocs." optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "mkdocstrings-1.0.4-py3-none-any.whl", hash = "sha256:63464b4b29053514f32a1dbbf604e52876d5e638111b0c295ab7ed3cac73ca9b"}, {file = "mkdocstrings-1.0.4.tar.gz", hash = "sha256:3969a6515b77db65fd097b53c1b7aa4ae840bd71a2ee62a6a3e89503446d7172"}, @@ -1897,7 +1924,7 @@ description = "A Python handler for mkdocstrings." optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "mkdocstrings_python-2.0.3-py3-none-any.whl", hash = "sha256:0b83513478bdfd803ff05aa43e9b1fca9dd22bcd9471f09ca6257f009bc5ee12"}, {file = "mkdocstrings_python-2.0.3.tar.gz", hash = "sha256:c518632751cc869439b31c9d3177678ad2bfa5c21b79b863956ad68fc92c13b8"}, @@ -1916,6 +1943,7 @@ description = "multidict implementation" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "multidict-6.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c93c3db7ea657dd4637d57e74ab73de31bccefe144d3d4ce370052035bc85fb5"}, {file = "multidict-6.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:974e72a2474600827abaeda71af0c53d9ebbc3c2eb7da37b37d7829ae31232d8"}, @@ -2075,7 +2103,7 @@ description = "Optional static typing for Python" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"lint\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"lint\"" files = [ {file = "mypy-1.20.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d99f515f95fd03a90875fdb2cca12ff074aa04490db4d190905851bdf8a549a8"}, {file = "mypy-1.20.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bd0212976dc57a5bfeede7c219e7cd66568a32c05c9129686dd487c059c1b88a"}, @@ -2145,7 +2173,7 @@ description = "Type system extensions for programs checked with the mypy type ch optional = true python-versions = ">=3.8" groups = ["main"] -markers = "extra == \"lint\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"lint\"" files = [ {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, @@ -2158,7 +2186,7 @@ description = "The official Python library for the openai API" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"docs\" or extra == \"openai\"" +markers = "(extra == \"docs\" or extra == \"openai\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "openai-2.36.0-py3-none-any.whl", hash = "sha256:143f6194b548dbc2c921af1f1b03b9f14c85fed8a75b5b516f5bcc11a2a50c63"}, {file = "openai-2.36.0.tar.gz", hash = "sha256:139dea0edd2f1b30c33d46ae1a6929e03906254140318e4608e98fe8c566f2e7"}, @@ -2187,7 +2215,7 @@ description = "OpenAI Agents SDK" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"openai\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"openai\"" files = [ {file = "openai_agents-0.13.5-py3-none-any.whl", hash = "sha256:672c76830d25b7eb3d85580539ba7caca975288df4395fe80c84cb4edfe4665f"}, {file = "openai_agents-0.13.5.tar.gz", hash = "sha256:ebe5bfb3d7d702d133ff9fb335718d61b741300de25b409ab4a6c8212ee945c0"}, @@ -2203,7 +2231,7 @@ types-requests = ">=2.0,<3" typing-extensions = ">=4.12.2,<5" [package.extras] -any-llm = ["any-llm-sdk (>=1.11.0,<2) ; python_version >= \"3.11\""] +any-llm = ["any-llm-sdk (>=1.11.0,<2)"] dapr = ["dapr (>=1.16.0)", "grpcio (>=1.60.0)"] encrypt = ["cryptography (>=45.0,<46)"] litellm = ["litellm (>=1.81.0,<=1.82.6)"] @@ -2211,7 +2239,7 @@ realtime = ["websockets (>=15.0,<16)"] redis = ["redis (>=7)"] sqlalchemy = ["asyncpg (>=0.29.0)", "sqlalchemy (>=2.0)"] viz = ["graphviz (>=0.17)"] -voice = ["numpy (>=2.2.0,<3) ; python_version >= \"3.10\"", "websockets (>=15.0,<16)"] +voice = ["numpy (>=2.2.0,<3)", "websockets (>=15.0,<16)"] [[package]] name = "opentelemetry-api" @@ -2220,7 +2248,7 @@ description = "OpenTelemetry Python API" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"otel\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"otel\"" files = [ {file = "opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9"}, {file = "opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f"}, @@ -2237,7 +2265,7 @@ description = "OpenTelemetry Python Distro" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"otel\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"otel\"" files = [ {file = "opentelemetry_distro-0.61b0-py3-none-any.whl", hash = "sha256:f21d1ac0627549795d75e332006dd068877f00e461b1b2e8fe4568d6eb7b9590"}, {file = "opentelemetry_distro-0.61b0.tar.gz", hash = "sha256:975b845f50181ad53753becf4fd4b123b54fa04df5a9d78812264436d6518981"}, @@ -2258,7 +2286,7 @@ description = "OpenTelemetry Collector Exporters" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"otel\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"otel\"" files = [ {file = "opentelemetry_exporter_otlp-1.40.0-py3-none-any.whl", hash = "sha256:48c87e539ec9afb30dc443775a1334cc5487de2f72a770a4c00b1610bf6c697d"}, {file = "opentelemetry_exporter_otlp-1.40.0.tar.gz", hash = "sha256:7caa0870b95e2fcb59d64e16e2b639ecffb07771b6cd0000b5d12e5e4fef765a"}, @@ -2275,7 +2303,7 @@ description = "OpenTelemetry Protobuf encoding" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"otel\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"otel\"" files = [ {file = "opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149"}, {file = "opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa"}, @@ -2291,7 +2319,7 @@ description = "OpenTelemetry Collector Protobuf over gRPC Exporter" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"otel\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"otel\"" files = [ {file = "opentelemetry_exporter_otlp_proto_grpc-1.40.0-py3-none-any.whl", hash = "sha256:2aa0ca53483fe0cf6405087a7491472b70335bc5c7944378a0a8e72e86995c52"}, {file = "opentelemetry_exporter_otlp_proto_grpc-1.40.0.tar.gz", hash = "sha256:bd4015183e40b635b3dab8da528b27161ba83bf4ef545776b196f0fb4ec47740"}, @@ -2320,7 +2348,7 @@ description = "OpenTelemetry Collector Protobuf over HTTP Exporter" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"otel\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"otel\"" files = [ {file = "opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl", hash = "sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069"}, {file = "opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz", hash = "sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c"}, @@ -2345,7 +2373,7 @@ description = "Instrumentation Tools & Auto Instrumentation for OpenTelemetry Py optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"otel\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"otel\"" files = [ {file = "opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63"}, {file = "opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7"}, @@ -2364,7 +2392,7 @@ description = "OpenTelemetry Python Proto" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"otel\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"otel\"" files = [ {file = "opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f"}, {file = "opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd"}, @@ -2380,7 +2408,7 @@ description = "OpenTelemetry Python SDK" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"otel\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"otel\"" files = [ {file = "opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1"}, {file = "opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2"}, @@ -2398,7 +2426,7 @@ description = "OpenTelemetry Semantic Conventions" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"otel\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"otel\"" files = [ {file = "opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2"}, {file = "opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a"}, @@ -2415,7 +2443,7 @@ description = "Core utilities for Python packages" optional = true python-versions = ">=3.8" groups = ["main"] -markers = "extra == \"docs\" or extra == \"lint\" or extra == \"otel\" or extra == \"test\"" +markers = "(extra == \"otel\" or extra == \"lint\" or extra == \"test\" or extra == \"docs\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"}, {file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"}, @@ -2428,7 +2456,7 @@ description = "Utility library for gitignore style pattern matching of file path optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"docs\" or extra == \"lint\"" +markers = "(extra == \"lint\" or extra == \"docs\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723"}, {file = "pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645"}, @@ -2447,7 +2475,7 @@ description = "A small Python package for determining appropriate platform-speci optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"docs\" or extra == \"lint\"" +markers = "(extra == \"lint\" or extra == \"docs\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "platformdirs-4.9.4-py3-none-any.whl", hash = "sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868"}, {file = "platformdirs-4.9.4.tar.gz", hash = "sha256:1ec356301b7dc906d83f371c8f487070e99d3ccf9e501686456394622a01a934"}, @@ -2460,7 +2488,7 @@ description = "plugin and hook calling mechanisms for python" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"test\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"test\"" files = [ {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, @@ -2477,6 +2505,7 @@ description = "Python client for the Prometheus monitoring system." optional = false python-versions = ">=3.9" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "prometheus_client-0.25.0-py3-none-any.whl", hash = "sha256:d5aec89e349a6ec230805d0df882f3807f74fd6c1a2fa86864e3c2279059fed1"}, {file = "prometheus_client-0.25.0.tar.gz", hash = "sha256:5e373b75c31afb3c86f1a52fa1ad470c9aace18082d39ec0d2f918d11cc9ba28"}, @@ -2494,6 +2523,7 @@ description = "Accelerated property cache" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "propcache-0.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c2d1fa3201efaf55d730400d945b5b3ab6e672e100ba0f9a409d950ab25d7db"}, {file = "propcache-0.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1eb2994229cc8ce7fe9b3db88f5465f5fd8651672840b2e426b88cdb1a30aac8"}, @@ -2626,6 +2656,7 @@ description = "" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "protobuf-6.33.6-cp310-abi3-win32.whl", hash = "sha256:7d29d9b65f8afef196f8334e80d6bc1d5d4adedb449971fefd3723824e6e77d3"}, {file = "protobuf-6.33.6-cp310-abi3-win_amd64.whl", hash = "sha256:0cd27b587afca21b7cfa59a74dcbd48a50f0a6400cfb59391340ad729d91d326"}, @@ -2646,7 +2677,7 @@ description = "Cross-platform lib for process and system monitoring." optional = true python-versions = ">=3.6" groups = ["main"] -markers = "extra == \"lint\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"lint\"" files = [ {file = "psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b"}, {file = "psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea"}, @@ -2672,8 +2703,8 @@ files = [ ] [package.extras] -dev = ["abi3audit", "black", "check-manifest", "colorama ; os_name == \"nt\"", "coverage", "packaging", "psleak", "pylint", "pyperf", "pypinfo", "pyreadline3 ; os_name == \"nt\"", "pytest", "pytest-cov", "pytest-instafail", "pytest-xdist", "pywin32 ; os_name == \"nt\" and implementation_name != \"pypy\"", "requests", "rstcheck", "ruff", "setuptools", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "validate-pyproject[all]", "virtualenv", "vulture", "wheel", "wheel ; os_name == \"nt\" and implementation_name != \"pypy\"", "wmi ; os_name == \"nt\" and implementation_name != \"pypy\""] -test = ["psleak", "pytest", "pytest-instafail", "pytest-xdist", "pywin32 ; os_name == \"nt\" and implementation_name != \"pypy\"", "setuptools", "wheel ; os_name == \"nt\" and implementation_name != \"pypy\"", "wmi ; os_name == \"nt\" and implementation_name != \"pypy\""] +dev = ["abi3audit", "black", "check-manifest", "colorama", "coverage", "packaging", "psleak", "pylint", "pyperf", "pypinfo", "pyreadline3", "pytest", "pytest-cov", "pytest-instafail", "pytest-xdist", "pywin32", "requests", "rstcheck", "ruff", "setuptools", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "validate-pyproject[all]", "virtualenv", "vulture", "wheel", "wheel", "wmi"] +test = ["psleak", "pytest", "pytest-instafail", "pytest-xdist", "pywin32", "setuptools", "wheel", "wmi"] [[package]] name = "psycopg" @@ -2682,7 +2713,7 @@ description = "PostgreSQL database adapter for Python" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"test\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"test\"" files = [ {file = "psycopg-3.3.4-py3-none-any.whl", hash = "sha256:b6bbc25ccf05c8fad3b061d9db2ef0909a555171b84b07f29458a447253d679a"}, {file = "psycopg-3.3.4.tar.gz", hash = "sha256:e21207764952cff81b6b8bdacad9a3939f2793367fdac2987b3aac36a651b5bc"}, @@ -2694,12 +2725,12 @@ typing-extensions = {version = ">=4.6", markers = "python_version < \"3.13\""} tzdata = {version = "*", markers = "sys_platform == \"win32\""} [package.extras] -binary = ["psycopg-binary (==3.3.4) ; implementation_name != \"pypy\""] -c = ["psycopg-c (==3.3.4) ; implementation_name != \"pypy\""] +binary = ["psycopg-binary (==3.3.4)"] +c = ["psycopg-c (==3.3.4)"] dev = ["ast-comments (>=1.1.2)", "black (>=26.1.0)", "codespell (>=2.2)", "cython-lint (>=0.16)", "dnspython (>=2.1)", "flake8 (>=4.0)", "isort-psycopg", "isort[colors] (>=6.0)", "mypy (>=1.19.0)", "pre-commit (>=4.0.1)", "types-setuptools (>=57.4)", "types-shapely (>=2.0)", "wheel (>=0.37)"] docs = ["Sphinx (>=9.1)", "furo (==2025.12.19)", "sphinx-autobuild (>=2025.8.25)", "sphinx-autodoc-typehints (>=3.10.2)"] pool = ["psycopg-pool"] -test = ["anyio (>=4.0)", "mypy (>=1.19.0) ; implementation_name != \"pypy\"", "pproxy (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0)", "pytest-randomly (>=3.5)"] +test = ["anyio (>=4.0)", "mypy (>=1.19.0)", "pproxy (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0)", "pytest-randomly (>=3.5)"] [[package]] name = "psycopg-pool" @@ -2708,7 +2739,7 @@ description = "Connection Pool for Psycopg" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"test\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"test\"" files = [ {file = "psycopg_pool-3.3.0-py3-none-any.whl", hash = "sha256:2e44329155c410b5e8666372db44276a8b1ebd8c90f1c3026ebba40d4bc81063"}, {file = "psycopg_pool-3.3.0.tar.gz", hash = "sha256:fa115eb2860bd88fce1717d75611f41490dec6135efb619611142b24da3f6db5"}, @@ -2727,7 +2758,7 @@ description = "C parser in Python" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(extra == \"openai\" or extra == \"claude\") and platform_python_implementation != \"PyPy\" and implementation_name != \"PyPy\"" +markers = "(extra == \"openai\" or extra == \"claude\") and platform_python_implementation != \"PyPy\" and implementation_name != \"PyPy\" and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992"}, {file = "pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29"}, @@ -2740,6 +2771,7 @@ description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba"}, {file = "pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6"}, @@ -2753,7 +2785,7 @@ typing-inspection = ">=0.4.2" [package.extras] email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] +timezone = ["tzdata"] [[package]] name = "pydantic-core" @@ -2762,6 +2794,7 @@ description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "pydantic_core-2.46.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a396dcc17e5a0b164dbe026896245a4fa9ff402edca1dff0be3d53a517f74de4"}, {file = "pydantic_core-2.46.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da4b951fe36dc7c3a1ccb4e3cd1747c3542b8c9ceede8fc86cae054e764485f5"}, @@ -2895,6 +2928,7 @@ description = "Settings management using Pydantic" optional = false python-versions = ">=3.10" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "pydantic_settings-2.14.1-py3-none-any.whl", hash = "sha256:6e3c7edfd8277687cdc598f56e5cff0e9bfff0910a3749deaa8d4401c3a2b9de"}, {file = "pydantic_settings-2.14.1.tar.gz", hash = "sha256:e874d3bec7e787b0c9958277956ed9b4dd5de6a80e162188fdaff7c5e26fd5fa"}, @@ -2919,7 +2953,7 @@ description = "A Python docstring linter that checks arguments, returns, yields, optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "pydoclint-0.8.3-py3-none-any.whl", hash = "sha256:5fc9b82d0d515afce0908cb70e8ff695a68b19042785c248c4f227ad66b4a164"}, {file = "pydoclint-0.8.3.tar.gz", hash = "sha256:0c69c0ed92c6f6b5aec2a14371bbd9e0a77980da8e2fa0f708092aa06b3b20b3"}, @@ -2940,7 +2974,7 @@ description = "Pygments is a syntax highlighting package written in Python." optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"test\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"test\"" files = [ {file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"}, {file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"}, @@ -2956,7 +2990,7 @@ description = "JSON Web Token implementation in Python" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"openai\" or extra == \"claude\"" +markers = "(extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "pyjwt-2.12.1-py3-none-any.whl", hash = "sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c"}, {file = "pyjwt-2.12.1.tar.gz", hash = "sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b"}, @@ -2979,7 +3013,7 @@ description = "Extension pack for Python Markdown." optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "pymdown_extensions-10.21.2-py3-none-any.whl", hash = "sha256:5c0fd2a2bea14eb39af8ff284f1066d898ab2187d81b889b75d46d4348c01638"}, {file = "pymdown_extensions-10.21.2.tar.gz", hash = "sha256:c3f55a5b8a1d0edf6699e35dcbea71d978d34ff3fa79f3d807b8a5b3fa90fbdc"}, @@ -2999,7 +3033,7 @@ description = "pytest: simple powerful testing with Python" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"test\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"test\"" files = [ {file = "pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9"}, {file = "pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c"}, @@ -3024,7 +3058,7 @@ description = "Pytest support for asyncio" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"test\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"test\"" files = [ {file = "pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5"}, {file = "pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5"}, @@ -3046,7 +3080,7 @@ description = "pytest plugin that allows you to add environment variables." optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"test\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"test\"" files = [ {file = "pytest_env-1.6.0-py3-none-any.whl", hash = "sha256:1e7f8a62215e5885835daaed694de8657c908505b964ec8097a7ce77b403d9a3"}, {file = "pytest_env-1.6.0.tar.gz", hash = "sha256:ac02d6fba16af54d61e311dd70a3c61024a4e966881ea844affc3c8f0bf207d3"}, @@ -3067,7 +3101,7 @@ description = "Adds the ability to retry flaky tests in CI environments" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"test\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"test\"" files = [ {file = "pytest_retry-1.7.0-py3-none-any.whl", hash = "sha256:a2dac85b79a4e2375943f1429479c65beb6c69553e7dae6b8332be47a60954f4"}, {file = "pytest_retry-1.7.0.tar.gz", hash = "sha256:f8d52339f01e949df47c11ba9ee8d5b362f5824dff580d3870ec9ae0057df80f"}, @@ -3086,7 +3120,7 @@ description = "pytest xdist plugin for distributed testing, most importantly acr optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"test\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"test\"" files = [ {file = "pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88"}, {file = "pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1"}, @@ -3108,6 +3142,7 @@ description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -3123,6 +3158,7 @@ description = "Read key-value pairs from a .env file and set them as environment optional = false python-versions = ">=3.10" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a"}, {file = "python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3"}, @@ -3138,7 +3174,7 @@ description = "A streaming multipart parser for Python" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"openai\" or extra == \"claude\"" +markers = "(extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "python_multipart-0.0.27-py3-none-any.whl", hash = "sha256:6fccfad17a27334bd0193681b369f476eda3409f17381a2d65aa7df3f7275645"}, {file = "python_multipart-0.0.27.tar.gz", hash = "sha256:9870a6a8c5a20a5bf4f07c017bd1489006ff8836cff097b6933355ee2b49b602"}, @@ -3151,7 +3187,7 @@ description = "A Fast, spec compliant Python 3.14+ tokenizer that runs on older optional = true python-versions = ">=3.8" groups = ["main"] -markers = "extra == \"lint\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"lint\"" files = [ {file = "pytokens-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a44ed93ea23415c54f3face3b65ef2b844d96aeb3455b8a69b3df6beab6acc5"}, {file = "pytokens-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:add8bf86b71a5d9fb5b89f023a80b791e04fba57960aa790cc6125f7f1d39dfe"}, @@ -3207,7 +3243,7 @@ description = "Python for Window Extensions" optional = true python-versions = "*" groups = ["main"] -markers = "(extra == \"openai\" or extra == \"claude\") and sys_platform == \"win32\"" +markers = "(extra == \"openai\" or extra == \"claude\") and sys_platform == \"win32\" and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3"}, {file = "pywin32-311-cp310-cp310-win_amd64.whl", hash = "sha256:797c2772017851984b97180b0bebe4b620bb86328e8a884bb626156295a63b3b"}, @@ -3238,7 +3274,7 @@ description = "YAML parser and emitter for Python" optional = true python-versions = ">=3.8" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"}, {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"}, @@ -3322,7 +3358,7 @@ description = "A custom YAML tag for referencing environment variables in YAML f optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04"}, {file = "pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff"}, @@ -3338,7 +3374,7 @@ description = "JSON Referencing + Python" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"openai\" or extra == \"claude\"" +markers = "(extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231"}, {file = "referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8"}, @@ -3356,7 +3392,7 @@ description = "Python HTTP for Humans." optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"openai\" or extra == \"otel\" or extra == \"test\"" +markers = "(extra == \"otel\" or extra == \"test\" or extra == \"openai\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a"}, {file = "requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517"}, @@ -3379,7 +3415,7 @@ description = "Python bindings to Rust's persistent data structures (rpds)" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"openai\" or extra == \"claude\"" +markers = "(extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "rpds_py-0.30.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:679ae98e00c0e8d68a7fda324e16b90fd5260945b45d3b824c892cec9eea3288"}, {file = "rpds_py-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cc2206b76b4f576934f0ed374b10d7ca5f457858b157ca52064bdfc26b9fc00"}, @@ -3505,7 +3541,7 @@ description = "An extremely fast Python linter and code formatter, written in Ru optional = true python-versions = ">=3.7" groups = ["main"] -markers = "extra == \"lint\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"lint\"" files = [ {file = "ruff-0.15.10-py3-none-linux_armv6l.whl", hash = "sha256:0744e31482f8f7d0d10a11fcbf897af272fefdfcb10f5af907b18c2813ff4d5f"}, {file = "ruff-0.15.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b1e7c16ea0ff5a53b7c2df52d947e685973049be1cdfe2b59a9c43601897b22e"}, @@ -3534,17 +3570,17 @@ description = "An Amazon S3 Transfer Manager" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"test\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"test\"" files = [ {file = "s3transfer-0.17.0-py3-none-any.whl", hash = "sha256:ce3801712acf4ad3e89fb9990df97b4972e93f4b3b0004d214be5bce12814c20"}, {file = "s3transfer-0.17.0.tar.gz", hash = "sha256:9edeb6d1c3c2f89d6050348548834ad8289610d886e5bf7b7207728bd43ce33a"}, ] [package.dependencies] -botocore = ">=1.37.4,<2.0a0" +botocore = ">=1.37.4,<2.0a.0" [package.extras] -crt = ["botocore[crt] (>=1.37.4,<2.0a0)"] +crt = ["botocore[crt] (>=1.37.4,<2.0a.0)"] [[package]] name = "setuptools" @@ -3553,19 +3589,20 @@ description = "Most extensible Python build backend with support for C/C++ exten optional = false python-versions = ">=3.9" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb"}, {file = "setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.13.0) ; sys_platform != \"cygwin\""] -core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.13.0)"] +core = ["importlib_metadata (>=6)", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.18.*)", "pytest-mypy"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.18.*)", "pytest-mypy"] [[package]] name = "six" @@ -3574,6 +3611,7 @@ description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -3586,7 +3624,7 @@ description = "Sniff out which async library your code is running under" optional = true python-versions = ">=3.7" groups = ["main"] -markers = "extra == \"docs\" or extra == \"openai\"" +markers = "(extra == \"docs\" or extra == \"openai\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -3599,7 +3637,7 @@ description = "A modern CSS selector implementation for Beautiful Soup." optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95"}, {file = "soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349"}, @@ -3612,7 +3650,7 @@ description = "SSE plugin for Starlette" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"openai\" or extra == \"claude\"" +markers = "(extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "sse_starlette-3.3.4-py3-none-any.whl", hash = "sha256:84bb06e58939a8b38d8341f1bc9792f06c2b53f48c608dd207582b664fc8f3c1"}, {file = "sse_starlette-3.3.4.tar.gz", hash = "sha256:aaf92fc067af8a5427192895ac028e947b484ac01edbc3caf00e7e7137c7bef1"}, @@ -3635,7 +3673,7 @@ description = "The little ASGI library that shines." optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"openai\" or extra == \"claude\" or extra == \"docs\"" +markers = "(extra == \"openai\" or extra == \"claude\" or extra == \"docs\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "starlette-1.0.0-py3-none-any.whl", hash = "sha256:d3ec55e0bb321692d275455ddfd3df75fff145d009685eb40dc91fc66b03d38b"}, {file = "starlette-1.0.0.tar.gz", hash = "sha256:6a4beaf1f81bb472fd19ea9b918b50dc3a77a6f2e190a12954b25e6ed5eea149"}, @@ -3655,6 +3693,7 @@ description = "Retry code until it succeeds" optional = false python-versions = ">=3.10" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55"}, {file = "tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a"}, @@ -3671,7 +3710,7 @@ description = "A lil' TOML parser" optional = true python-versions = ">=3.8" groups = ["main"] -markers = "(extra == \"docs\" or extra == \"lint\" or extra == \"test\") and python_version == \"3.10\"" +markers = "(extra == \"lint\" or extra == \"test\" or extra == \"docs\") and python_version < \"3.11\"" files = [ {file = "tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30"}, {file = "tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a"}, @@ -3729,7 +3768,7 @@ description = "Fast, Extensible Progress Meter" optional = true python-versions = ">=3.7" groups = ["main"] -markers = "extra == \"docs\" or extra == \"openai\"" +markers = "(extra == \"docs\" or extra == \"openai\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf"}, {file = "tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb"}, @@ -3752,7 +3791,7 @@ description = "Typing stubs for beautifulsoup4" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "types_beautifulsoup4-4.12.0.20250516-py3-none-any.whl", hash = "sha256:5923399d4a1ba9cc8f0096fe334cc732e130269541d66261bb42ab039c0376ee"}, {file = "types_beautifulsoup4-4.12.0.20250516.tar.gz", hash = "sha256:aa19dd73b33b70d6296adf92da8ab8a0c945c507e6fb7d5db553415cc77b417e"}, @@ -3768,7 +3807,7 @@ description = "Typing stubs for grpcio" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"lint\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"lint\"" files = [ {file = "types_grpcio-1.0.0.20251009-py3-none-any.whl", hash = "sha256:112ac4312a5b0a273a4c414f7f2c7668f342990d9c6ab0f647391c36331f95ed"}, {file = "types_grpcio-1.0.0.20251009.tar.gz", hash = "sha256:a8f615ea7a47b31f10da028ab5258d4f1611fbd70719ca450fc0ab3fb9c62b63"}, @@ -3781,7 +3820,7 @@ description = "Typing stubs for html5lib" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "types_html5lib-1.1.11.20251117-py3-none-any.whl", hash = "sha256:2a3fc935de788a4d2659f4535002a421e05bea5e172b649d33232e99d4272d08"}, {file = "types_html5lib-1.1.11.20251117.tar.gz", hash = "sha256:1a6a3ac5394aa12bf547fae5d5eff91dceec46b6d07c4367d9b39a37f42f201a"}, @@ -3797,7 +3836,7 @@ description = "Typing stubs for protobuf" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"lint\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"lint\"" files = [ {file = "types_protobuf-6.32.1.20260221-py3-none-any.whl", hash = "sha256:da7cdd947975964a93c30bfbcc2c6841ee646b318d3816b033adc2c4eb6448e4"}, {file = "types_protobuf-6.32.1.20260221.tar.gz", hash = "sha256:6d5fb060a616bfb076cbb61b4b3c3969f5fc8bec5810f9a2f7e648ee5cbcbf6e"}, @@ -3810,7 +3849,7 @@ description = "Typing stubs for psutil" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"lint\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"lint\"" files = [ {file = "types_psutil-7.2.2.20260508-py3-none-any.whl", hash = "sha256:b142452e0953f2d07dbdbb98d81f3a629f5906cc2d94bb7e34da0fba55fbab4a"}, {file = "types_psutil-7.2.2.20260508.tar.gz", hash = "sha256:8cfd8339f5e898570f80486423e65d87558d89d0181bf723d20ac5e778fe218e"}, @@ -3823,7 +3862,7 @@ description = "Typing stubs for requests" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"lint\" or extra == \"openai\"" +markers = "(extra == \"lint\" or extra == \"openai\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "types_requests-2.33.0.20260518-py3-none-any.whl", hash = "sha256:626d697d1adaaff76e2044dc8c5c051d8f21abc157bdfe204a75558076fe0bf0"}, {file = "types_requests-2.33.0.20260518.tar.gz", hash = "sha256:df7bd3bfe0ca8402dfb841e7d9be714bb5578203283d66d7dc4ef69343449a5e"}, @@ -3839,7 +3878,7 @@ description = "Typing stubs for webencodings" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "types_webencodings-0.5.0.20251108-py3-none-any.whl", hash = "sha256:e21f81ff750795faffddaffd70a3d8bfff77d006f22c27e393eb7812586249d8"}, {file = "types_webencodings-0.5.0.20251108.tar.gz", hash = "sha256:2378e2ceccced3d41bb5e21387586e7b5305e11519fc6b0659c629f23b2e5de4"}, @@ -3852,6 +3891,7 @@ description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, @@ -3864,6 +3904,7 @@ description = "Runtime typing introspection tools" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, @@ -3879,7 +3920,7 @@ description = "Provider of IANA time zone data" optional = true python-versions = ">=2" groups = ["main"] -markers = "extra == \"test\" and sys_platform == \"win32\"" +markers = "extra == \"test\" and sys_platform == \"win32\" and (python_version <= \"3.13\" or python_version >= \"3.14\")" files = [ {file = "tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1"}, {file = "tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7"}, @@ -3892,16 +3933,17 @@ description = "HTTP library with thread-safe connection pooling, file post, and optional = false python-versions = ">=3.10" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897"}, {file = "urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c"}, ] [package.extras] -brotli = ["brotli (>=1.2.0) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=1.2.0.0) ; platform_python_implementation != \"CPython\""] +brotli = ["brotli (>=1.2.0)", "brotlicffi (>=1.2.0.0)"] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""] +zstd = ["backports-zstd (>=1.0.0)"] [[package]] name = "uvicorn" @@ -3910,7 +3952,7 @@ description = "The lightning-fast ASGI server." optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(extra == \"openai\" or extra == \"claude\") and sys_platform != \"emscripten\" or extra == \"docs\"" +markers = "(extra == \"openai\" or extra == \"claude\" or extra == \"docs\") and (python_version <= \"3.13\" or python_version >= \"3.14\") and (sys_platform != \"emscripten\" or extra == \"docs\")" files = [ {file = "uvicorn-0.46.0-py3-none-any.whl", hash = "sha256:bbebbcbed972d162afca128605223022bedd345b7bc7855ce66deb31487a9048"}, {file = "uvicorn-0.46.0.tar.gz", hash = "sha256:fb9da0926999cc6cb22dc7cd71a94a632f078e6ae47ff683c5c420750fb7413d"}, @@ -3922,7 +3964,7 @@ h11 = ">=0.8" typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} [package.extras] -standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.15.1) ; sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"", "watchfiles (>=0.20)", "websockets (>=10.4)"] +standard = ["colorama (>=0.4)", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.15.1)", "watchfiles (>=0.20)", "websockets (>=10.4)"] [[package]] name = "watchdog" @@ -3931,7 +3973,7 @@ description = "Filesystem events monitoring" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"docs\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" files = [ {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26"}, {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112"}, @@ -3975,7 +4017,7 @@ description = "Module for decorators, wrappers and monkey patching." optional = true python-versions = ">=3.8" groups = ["main"] -markers = "extra == \"otel\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"otel\"" files = [ {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88bbae4d40d5a46142e70d58bf664a89b6b4befaea7b2ecc14e03cedb8e06c04"}, {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b13af258d6a9ad602d57d889f83b9d5543acd471eee12eb51f5b01f8eb1bc2"}, @@ -4067,6 +4109,7 @@ description = "Yet another URL library" optional = false python-versions = ">=3.10" groups = ["main"] +markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "yarl-1.23.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cff6d44cb13d39db2663a22b22305d10855efa0fa8015ddeacc40bc59b9d8107"}, {file = "yarl-1.23.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4c53f8347cd4200f0d70a48ad059cabaf24f5adc6ba08622a23423bc7efa10d"}, @@ -4210,14 +4253,14 @@ description = "Backport of pathlib-compatible object wrapper for zip files" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"otel\"" +markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"otel\"" files = [ {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] @@ -4235,4 +4278,4 @@ test = ["boto3", "botocore", "psycopg", "pytest", "pytest-asyncio", "pytest-env" [metadata] lock-version = "2.1" python-versions = ">=3.10,<4" -content-hash = "fdef8ce55bd1e5db1353642eca123e801ead5499e33c806e2c8c167534bfa1b2" +content-hash = "03cc0e4d6d768e9565f0e0ad9fe3960a77357cc42683e0b63f40963e830c3b03" From ca5f63c4e99c0c0e46f32480ead151790b6bd647 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Tue, 2 Jun 2026 10:24:32 -0400 Subject: [PATCH 28/69] fix: add grpcio types --- sdks/python/poetry.lock | 311 ++++++++++++++++++------------------- sdks/python/pyproject.toml | 1 + 2 files changed, 151 insertions(+), 161 deletions(-) diff --git a/sdks/python/poetry.lock b/sdks/python/poetry.lock index 6b70b710d9..2da5306380 100644 --- a/sdks/python/poetry.lock +++ b/sdks/python/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.0.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.4.1 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -7,7 +7,6 @@ description = "Happy Eyeballs for asyncio" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, @@ -20,7 +19,6 @@ description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "aiohttp-3.13.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:02222e7e233295f40e011c1b00e3b0bd451f22cf853a0304c3595633ee47da4b"}, {file = "aiohttp-3.13.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bace460460ed20614fa6bc8cb09966c0b8517b8c58ad8046828c6078d25333b5"}, @@ -155,7 +153,7 @@ propcache = ">=0.2.0" yarl = ">=1.17.0,<2.0" [package.extras] -speedups = ["Brotli (>=1.2)", "aiodns (>=3.3.0)", "backports.zstd", "brotlicffi (>=1.2)"] +speedups = ["Brotli (>=1.2) ; platform_python_implementation == \"CPython\"", "aiodns (>=3.3.0)", "backports.zstd ; platform_python_implementation == \"CPython\" and python_version < \"3.14\"", "brotlicffi (>=1.2) ; platform_python_implementation != \"CPython\""] [[package]] name = "aiosignal" @@ -164,7 +162,6 @@ description = "aiosignal: a list of registered asynchronous callbacks" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e"}, {file = "aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7"}, @@ -181,7 +178,7 @@ description = "Document parameters, class attributes, return types, and variable optional = true python-versions = ">=3.8" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" +markers = "extra == \"docs\"" files = [ {file = "annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320"}, {file = "annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4"}, @@ -194,7 +191,6 @@ description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -207,7 +203,7 @@ description = "High-level concurrency and networking framework on top of asyncio optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(extra == \"docs\" or extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" +markers = "extra == \"openai\" or extra == \"claude\" or extra == \"docs\"" files = [ {file = "anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708"}, {file = "anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc"}, @@ -228,7 +224,7 @@ description = "Timeout context manager for asyncio programs" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version < \"3.11\"" +markers = "python_version == \"3.10\"" files = [ {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, @@ -241,7 +237,6 @@ description = "Classes Without Boilerplate" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309"}, {file = "attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32"}, @@ -254,7 +249,7 @@ description = "Backport of asyncio.Runner, a context manager that controls event optional = true python-versions = "<3.11,>=3.8" groups = ["main"] -markers = "extra == \"test\" and python_version < \"3.11\"" +markers = "extra == \"test\" and python_version == \"3.10\"" files = [ {file = "backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5"}, {file = "backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162"}, @@ -267,7 +262,7 @@ description = "Screen-scraping library" optional = true python-versions = ">=3.7.0" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" +markers = "extra == \"docs\"" files = [ {file = "beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb"}, {file = "beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86"}, @@ -291,7 +286,7 @@ description = "The uncompromising code formatter." optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"lint\"" +markers = "extra == \"lint\"" files = [ {file = "black-26.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:86a8b5035fce64f5dcd1b794cf8ec4d31fe458cf6ce3986a30deb434df82a1d2"}, {file = "black-26.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5602bdb96d52d2d0672f24f6ffe5218795736dd34807fd0fd55ccd6bf206168b"}, @@ -336,7 +331,7 @@ typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} colorama = ["colorama (>=0.4.3)"] d = ["aiohttp (>=3.10)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)", "winloop (>=0.5.0)"] +uvloop = ["uvloop (>=0.15.2) ; sys_platform != \"win32\"", "winloop (>=0.5.0) ; sys_platform == \"win32\""] [[package]] name = "boto3" @@ -345,7 +340,7 @@ description = "The AWS SDK for Python" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"test\"" +markers = "extra == \"test\"" files = [ {file = "boto3-1.43.4-py3-none-any.whl", hash = "sha256:c4910b54c1f2401ab55d9fbca3d92df0a68e4ac64a69f2bab47a079e384a9d4f"}, {file = "boto3-1.43.4.tar.gz", hash = "sha256:00f40e9ca704c0f860b70ef47b042813c8d9a66b3d5e7bf81d578f79ae17dcd3"}, @@ -366,7 +361,7 @@ description = "Low-level, data-driven core of boto 3." optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"test\"" +markers = "extra == \"test\"" files = [ {file = "botocore-1.43.4-py3-none-any.whl", hash = "sha256:f1897d3254965cacac7514cfed1b6ff016166b165ee2e06232b4a3954cf9d713"}, {file = "botocore-1.43.4.tar.gz", hash = "sha256:1a95c90fa9ca6ee85c1a02b04c8e05e948661d201b85b443000d676857905019"}, @@ -387,7 +382,7 @@ description = "Dummy package for Beautiful Soup (beautifulsoup4)" optional = true python-versions = "*" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" +markers = "extra == \"docs\"" files = [ {file = "bs4-0.0.2-py2.py3-none-any.whl", hash = "sha256:abf8742c0805ef7f662dce4b51cca104cffe52b835238afc169142ab9b3fbccc"}, {file = "bs4-0.0.2.tar.gz", hash = "sha256:a48685c58f50fe127722417bae83fe6badf500d54b55f7e39ffe43b798653925"}, @@ -403,7 +398,7 @@ description = "Python package for providing Mozilla's CA Bundle." optional = true python-versions = ">=3.7" groups = ["main"] -markers = "(extra == \"otel\" or extra == \"test\" or extra == \"openai\" or extra == \"docs\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" +markers = "extra == \"otel\" or extra == \"test\" or extra == \"openai\" or extra == \"claude\" or extra == \"docs\"" files = [ {file = "certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa"}, {file = "certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7"}, @@ -416,7 +411,7 @@ description = "Foreign Function Interface for Python calling C code." optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(extra == \"openai\" or extra == \"claude\") and platform_python_implementation != \"PyPy\" and (python_version <= \"3.13\" or python_version >= \"3.14\")" +markers = "(extra == \"openai\" or extra == \"claude\") and platform_python_implementation != \"PyPy\"" files = [ {file = "cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44"}, {file = "cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49"}, @@ -514,7 +509,7 @@ description = "The Real First Universal Charset Detector. Open, modern and activ optional = true python-versions = ">=3.7" groups = ["main"] -markers = "(extra == \"otel\" or extra == \"test\" or extra == \"openai\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" +markers = "extra == \"otel\" or extra == \"test\" or extra == \"openai\"" files = [ {file = "charset_normalizer-3.4.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2e1d8ca8611099001949d1cdfaefc510cf0f212484fe7c565f735b68c78c3c95"}, {file = "charset_normalizer-3.4.6-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e25369dc110d58ddf29b949377a93e0716d72a24f62bad72b2b39f155949c1fd"}, @@ -654,7 +649,7 @@ description = "Python SDK for Claude Code" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"claude\"" +markers = "extra == \"claude\"" files = [ {file = "claude_agent_sdk-0.1.55-py3-none-macosx_11_0_arm64.whl", hash = "sha256:70fa9f20f9b3a632d25d2666b533505a754d36429a4ad91a465eee87ac5798f7"}, {file = "claude_agent_sdk-0.1.55-py3-none-macosx_11_0_x86_64.whl", hash = "sha256:01c4ed18aa3dd38a5c1a37fc5a775549abc9d6658bad65a69e36dafbbecd3e29"}, @@ -679,7 +674,7 @@ description = "Composable command line interface toolkit" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(extra == \"lint\" or extra == \"docs\" or extra == \"openai\" or extra == \"claude\") and (extra == \"lint\" or extra == \"docs\" or sys_platform != \"emscripten\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" +markers = "sys_platform != \"emscripten\" and (extra == \"openai\" or extra == \"claude\") or extra == \"docs\" or extra == \"lint\"" files = [ {file = "click-8.3.3-py3-none-any.whl", hash = "sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613"}, {file = "click-8.3.3.tar.gz", hash = "sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2"}, @@ -695,7 +690,7 @@ description = "Cross-platform colored terminal text." optional = true python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" groups = ["main"] -markers = "(extra == \"lint\" or extra == \"test\" or extra == \"docs\" or extra == \"openai\" or extra == \"claude\") and (platform_system == \"Windows\" or extra == \"test\") and (extra == \"lint\" or sys_platform != \"emscripten\" or extra == \"docs\" or extra == \"openai\") and (python_version <= \"3.13\" or python_version >= \"3.14\") and (extra == \"lint\" or extra == \"docs\" or extra == \"openai\" or extra == \"claude\" or sys_platform == \"win32\") and (platform_system == \"Windows\" or sys_platform == \"win32\")" +markers = "(sys_platform != \"emscripten\" and (extra == \"openai\" or extra == \"claude\") or extra == \"docs\" or extra == \"lint\") and platform_system == \"Windows\" or extra == \"test\" and sys_platform == \"win32\" or platform_system == \"Windows\" and (extra == \"docs\" or extra == \"openai\")" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -708,7 +703,7 @@ description = "cryptography is a package which provides cryptographic recipes an optional = true python-versions = "!=3.9.0,!=3.9.1,>=3.9" groups = ["main"] -markers = "(extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" +markers = "extra == \"openai\" or extra == \"claude\"" files = [ {file = "cryptography-48.0.0-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:0c558d2cdffd8f4bbb30fc7134c74d2ca9a476f830bb053074498fbc86f41ed6"}, {file = "cryptography-48.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f5333311663ea94f75dd408665686aaf426563556bb5283554a3539177e03b8c"}, @@ -763,7 +758,7 @@ files = [ [package.dependencies] cffi = {version = ">=2.0.0", markers = "platform_python_implementation != \"PyPy\""} -typing-extensions = {version = ">=4.13.2", markers = "python_full_version < \"3.11\""} +typing-extensions = {version = ">=4.13.2", markers = "python_full_version < \"3.11.0\""} [package.extras] ssh = ["bcrypt (>=3.1.5)"] @@ -775,7 +770,7 @@ description = "Distro - an OS platform information API" optional = true python-versions = ">=3.6" groups = ["main"] -markers = "(extra == \"docs\" or extra == \"openai\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" +markers = "extra == \"docs\" or extra == \"openai\"" files = [ {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, @@ -788,14 +783,14 @@ description = "Parse Python docstrings in reST, Google and Numpydoc format" optional = true python-versions = ">=3.8" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" +markers = "extra == \"docs\"" files = [ {file = "docstring_parser_fork-0.0.14-py3-none-any.whl", hash = "sha256:4c544f234ef2cc2749a3df32b70c437d77888b1099143a1ad5454452c574b9af"}, {file = "docstring_parser_fork-0.0.14.tar.gz", hash = "sha256:a2743a63d8d36c09650594f7b4ab5b2758fee8629dcf794d1b221b23179baa5c"}, ] [package.extras] -dev = ["docstring-parser[docs]", "docstring-parser[test]", "pre-commit (>=2.16.0)"] +dev = ["docstring-parser[docs]", "docstring-parser[test]", "pre-commit (>=2.16.0) ; python_version >= \"3.9\""] docs = ["pydoctor (>=25.4.0)"] test = ["pytest"] @@ -806,7 +801,7 @@ description = "Backport of PEP 654 (exception groups)" optional = true python-versions = ">=3.7" groups = ["main"] -markers = "(extra == \"test\" or extra == \"docs\" or extra == \"openai\" or extra == \"claude\") and python_version < \"3.11\"" +markers = "(extra == \"test\" or extra == \"openai\" or extra == \"claude\" or extra == \"docs\") and python_version == \"3.10\"" files = [ {file = "exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598"}, {file = "exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219"}, @@ -825,7 +820,7 @@ description = "execnet: rapid multi-Python deployment" optional = true python-versions = ">=3.8" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"test\"" +markers = "extra == \"test\"" files = [ {file = "execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec"}, {file = "execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd"}, @@ -841,7 +836,7 @@ description = "FastAPI framework, high performance, easy to learn, fast to code, optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" +markers = "extra == \"docs\"" files = [ {file = "fastapi-0.136.1-py3-none-any.whl", hash = "sha256:a6e9d7eeada96c93a4d69cb03836b44fa34e2854accb7244a1ece36cd4781c3f"}, {file = "fastapi-0.136.1.tar.gz", hash = "sha256:7af665ad7acfa0a3baf8983d393b6b471b9da10ede59c60045f49fbc89a0fa7f"}, @@ -866,7 +861,6 @@ description = "A list-like structure which implements collections.abc.MutableSeq optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "frozenlist-1.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b37f6d31b3dcea7deb5e9696e529a6aa4a898adc33db82da12e4c60a7c4d2011"}, {file = "frozenlist-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef2b7b394f208233e471abc541cc6991f907ffd47dc72584acee3147899d6565"}, @@ -1007,7 +1001,7 @@ description = "Copy your docs directly to the gh-pages branch." optional = true python-versions = "*" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" +markers = "extra == \"docs\"" files = [ {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, @@ -1026,7 +1020,6 @@ description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "googleapis_common_protos-1.73.1-py3-none-any.whl", hash = "sha256:e51f09eb0a43a8602f5a915870972e6b4a394088415c79d79605a46d8e826ee8"}, {file = "googleapis_common_protos-1.73.1.tar.gz", hash = "sha256:13114f0e9d2391756a0194c3a8131974ed7bffb06086569ba193364af59163b6"}, @@ -1045,7 +1038,7 @@ description = "Signatures for entire Python programs. Extract the structure, the optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(extra == \"docs\" or extra == \"openai\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" +markers = "extra == \"docs\" or extra == \"openai\"" files = [ {file = "griffelib-2.0.2-py3-none-any.whl", hash = "sha256:925c857658fb1ba40c0772c37acbc2ab650bd794d9c1b9726922e36ea4117ea1"}, {file = "griffelib-2.0.2.tar.gz", hash = "sha256:3cf20b3bc470e83763ffbf236e0076b1211bac1bc67de13daf494640f2de707e"}, @@ -1061,7 +1054,6 @@ description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "grpcio-1.80.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:886457a7768e408cdce226ad1ca67d2958917d306523a0e21e1a2fdaa75c9c9c"}, {file = "grpcio-1.80.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:7b641fc3f1dc647bfd80bd713addc68f6d145956f64677e56d9ebafc0bd72388"}, @@ -1139,7 +1131,6 @@ description = "Status proto mapping for gRPC" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "grpcio_status-1.80.0-py3-none-any.whl", hash = "sha256:4b56990363af50dbf2c2ebb80f1967185c07d87aa25aa2bea45ddb75fc181dbe"}, {file = "grpcio_status-1.80.0.tar.gz", hash = "sha256:df73802a4c89a3ea88aa2aff971e886fccce162bc2e6511408b3d67a144381cd"}, @@ -1157,7 +1148,6 @@ description = "Protobuf code generator for gRPC" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "grpcio_tools-1.80.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:727477b9afa4b53f5ec70cafb41c3965d893835e0d4ea9b542fe3d0d005602bf"}, {file = "grpcio_tools-1.80.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:85fe8d15f146c62cb76f38d963e256392d287442b9232717d30ae9e3bbda9bc3"}, @@ -1234,7 +1224,7 @@ description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = true python-versions = ">=3.8" groups = ["main"] -markers = "(extra == \"docs\" or extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" +markers = "extra == \"openai\" or extra == \"claude\" or extra == \"docs\"" files = [ {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, @@ -1247,7 +1237,7 @@ description = "A minimal low-level HTTP client." optional = true python-versions = ">=3.8" groups = ["main"] -markers = "(extra == \"docs\" or extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" +markers = "extra == \"openai\" or extra == \"claude\" or extra == \"docs\"" files = [ {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, @@ -1270,7 +1260,7 @@ description = "The next generation HTTP client." optional = true python-versions = ">=3.8" groups = ["main"] -markers = "(extra == \"docs\" or extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" +markers = "extra == \"openai\" or extra == \"claude\" or extra == \"docs\"" files = [ {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, @@ -1283,7 +1273,7 @@ httpcore = "==1.*" idna = "*" [package.extras] -brotli = ["brotli", "brotlicffi"] +brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] @@ -1296,7 +1286,7 @@ description = "Consume Server-Sent Event (SSE) messages with HTTPX." optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" +markers = "extra == \"openai\" or extra == \"claude\"" files = [ {file = "httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc"}, {file = "httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d"}, @@ -1309,7 +1299,6 @@ description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"}, {file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"}, @@ -1325,7 +1314,7 @@ description = "Read metadata from Python packages" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"otel\"" +markers = "extra == \"otel\"" files = [ {file = "importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151"}, {file = "importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb"}, @@ -1335,13 +1324,13 @@ files = [ zipp = ">=3.20" [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=3.4)"] perf = ["ipython"] test = ["flufl.flake8", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] -type = ["mypy (<1.19)", "pytest-mypy (>=1.0.1)"] +type = ["mypy (<1.19) ; platform_python_implementation == \"PyPy\"", "pytest-mypy (>=1.0.1)"] [[package]] name = "iniconfig" @@ -1350,7 +1339,7 @@ description = "brain-dead simple config-ini parsing" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"test\"" +markers = "extra == \"test\"" files = [ {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, @@ -1363,7 +1352,7 @@ description = "A very fast and expressive template engine." optional = true python-versions = ">=3.7" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" +markers = "extra == \"docs\"" files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, @@ -1382,7 +1371,7 @@ description = "Fast iterable JSON parser." optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(extra == \"docs\" or extra == \"openai\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" +markers = "extra == \"docs\" or extra == \"openai\"" files = [ {file = "jiter-0.13.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2ffc63785fd6c7977defe49b9824ae6ce2b2e2b77ce539bdaf006c26da06342e"}, {file = "jiter-0.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4a638816427006c1e3f0013eb66d391d7a3acda99a7b0cf091eff4497ccea33a"}, @@ -1495,7 +1484,7 @@ description = "JSON Matching Expressions" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"test\"" +markers = "extra == \"test\"" files = [ {file = "jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64"}, {file = "jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d"}, @@ -1508,7 +1497,7 @@ description = "An implementation of JSON Schema validation for Python" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" +markers = "extra == \"openai\" or extra == \"claude\"" files = [ {file = "jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce"}, {file = "jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326"}, @@ -1516,7 +1505,7 @@ files = [ [package.dependencies] attrs = ">=22.2.0" -jsonschema-specifications = ">=2023.03.6" +jsonschema-specifications = ">=2023.3.6" referencing = ">=0.28.4" rpds-py = ">=0.25.0" @@ -1531,7 +1520,7 @@ description = "The JSON Schema meta-schemas and vocabularies, exposed as a Regis optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" +markers = "extra == \"openai\" or extra == \"claude\"" files = [ {file = "jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe"}, {file = "jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d"}, @@ -1547,7 +1536,7 @@ description = "Mypyc runtime library" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"lint\" and platform_python_implementation != \"PyPy\" and (python_version <= \"3.13\" or python_version >= \"3.14\")" +markers = "extra == \"lint\" and platform_python_implementation != \"PyPy\"" files = [ {file = "librt-0.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:81fd938344fecb9373ba1b155968c8a329491d2ce38e7ddb76f30ffb938f12dc"}, {file = "librt-0.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5db05697c82b3a2ec53f6e72b2ed373132b0c2e05135f0696784e97d7f5d48e7"}, @@ -1648,7 +1637,7 @@ description = "Python implementation of John Gruber's Markdown." optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" +markers = "extra == \"docs\"" files = [ {file = "markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36"}, {file = "markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950"}, @@ -1665,7 +1654,7 @@ description = "Convert HTML to markdown." optional = true python-versions = "*" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" +markers = "extra == \"docs\"" files = [ {file = "markdownify-1.2.2-py3-none-any.whl", hash = "sha256:3f02d3cc52714084d6e589f70397b6fc9f2f3a8531481bf35e8cc39f975e186a"}, {file = "markdownify-1.2.2.tar.gz", hash = "sha256:b274f1b5943180b031b699b199cbaeb1e2ac938b75851849a31fd0c3d6603d09"}, @@ -1682,7 +1671,7 @@ description = "Safely add untrusted strings to HTML/XML markup." optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" +markers = "extra == \"docs\"" files = [ {file = "markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"}, {file = "markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"}, @@ -1782,7 +1771,7 @@ description = "Model Context Protocol SDK" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" +markers = "extra == \"openai\" or extra == \"claude\"" files = [ {file = "mcp-1.27.0-py3-none-any.whl", hash = "sha256:5ce1fa81614958e267b21fb2aa34e0aea8e2c6ede60d52aba45fd47246b4d741"}, {file = "mcp-1.27.0.tar.gz", hash = "sha256:d3dc35a7eec0d458c1da4976a48f982097ddaab87e278c5511d5a4a56e852b83"}, @@ -1816,7 +1805,7 @@ description = "A deep merge function for 🐍." optional = true python-versions = ">=3.6" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" +markers = "extra == \"docs\"" files = [ {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, @@ -1829,7 +1818,7 @@ description = "Project documentation with Markdown." optional = true python-versions = ">=3.8" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" +markers = "extra == \"docs\"" files = [ {file = "mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e"}, {file = "mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2"}, @@ -1852,7 +1841,7 @@ watchdog = ">=2.0" [package.extras] i18n = ["babel (>=2.9.0)"] -min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.4)", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"] +min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4) ; platform_system == \"Windows\"", "ghp-import (==1.0)", "importlib-metadata (==4.4) ; python_version < \"3.10\"", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"] [[package]] name = "mkdocs-autorefs" @@ -1861,7 +1850,7 @@ description = "Automatically link across pages in MkDocs." optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" +markers = "extra == \"docs\"" files = [ {file = "mkdocs_autorefs-1.4.4-py3-none-any.whl", hash = "sha256:834ef5408d827071ad1bc69e0f39704fa34c7fc05bc8e1c72b227dfdc5c76089"}, {file = "mkdocs_autorefs-1.4.4.tar.gz", hash = "sha256:d54a284f27a7346b9c38f1f852177940c222da508e66edc816a0fa55fc6da197"}, @@ -1879,7 +1868,7 @@ description = "An extra command for MkDocs that infers required PyPI packages fr optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" +markers = "extra == \"docs\"" files = [ {file = "mkdocs_get_deps-0.2.2-py3-none-any.whl", hash = "sha256:e7878cbeac04860b8b5e0ca31d3abad3df9411a75a32cde82f8e44b6c16ff650"}, {file = "mkdocs_get_deps-0.2.2.tar.gz", hash = "sha256:8ee8d5f316cdbbb2834bc1df6e69c08fe769a83e040060de26d3c19fad3599a1"}, @@ -1897,7 +1886,7 @@ description = "Automatic documentation from sources, for MkDocs." optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" +markers = "extra == \"docs\"" files = [ {file = "mkdocstrings-1.0.4-py3-none-any.whl", hash = "sha256:63464b4b29053514f32a1dbbf604e52876d5e638111b0c295ab7ed3cac73ca9b"}, {file = "mkdocstrings-1.0.4.tar.gz", hash = "sha256:3969a6515b77db65fd097b53c1b7aa4ae840bd71a2ee62a6a3e89503446d7172"}, @@ -1924,7 +1913,7 @@ description = "A Python handler for mkdocstrings." optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" +markers = "extra == \"docs\"" files = [ {file = "mkdocstrings_python-2.0.3-py3-none-any.whl", hash = "sha256:0b83513478bdfd803ff05aa43e9b1fca9dd22bcd9471f09ca6257f009bc5ee12"}, {file = "mkdocstrings_python-2.0.3.tar.gz", hash = "sha256:c518632751cc869439b31c9d3177678ad2bfa5c21b79b863956ad68fc92c13b8"}, @@ -1943,7 +1932,6 @@ description = "multidict implementation" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "multidict-6.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c93c3db7ea657dd4637d57e74ab73de31bccefe144d3d4ce370052035bc85fb5"}, {file = "multidict-6.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:974e72a2474600827abaeda71af0c53d9ebbc3c2eb7da37b37d7829ae31232d8"}, @@ -2103,7 +2091,7 @@ description = "Optional static typing for Python" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"lint\"" +markers = "extra == \"lint\"" files = [ {file = "mypy-1.20.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d99f515f95fd03a90875fdb2cca12ff074aa04490db4d190905851bdf8a549a8"}, {file = "mypy-1.20.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bd0212976dc57a5bfeede7c219e7cd66568a32c05c9129686dd487c059c1b88a"}, @@ -2173,7 +2161,7 @@ description = "Type system extensions for programs checked with the mypy type ch optional = true python-versions = ">=3.8" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"lint\"" +markers = "extra == \"lint\"" files = [ {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, @@ -2186,7 +2174,7 @@ description = "The official Python library for the openai API" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(extra == \"docs\" or extra == \"openai\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" +markers = "extra == \"docs\" or extra == \"openai\"" files = [ {file = "openai-2.36.0-py3-none-any.whl", hash = "sha256:143f6194b548dbc2c921af1f1b03b9f14c85fed8a75b5b516f5bcc11a2a50c63"}, {file = "openai-2.36.0.tar.gz", hash = "sha256:139dea0edd2f1b30c33d46ae1a6929e03906254140318e4608e98fe8c566f2e7"}, @@ -2215,7 +2203,7 @@ description = "OpenAI Agents SDK" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"openai\"" +markers = "extra == \"openai\"" files = [ {file = "openai_agents-0.13.5-py3-none-any.whl", hash = "sha256:672c76830d25b7eb3d85580539ba7caca975288df4395fe80c84cb4edfe4665f"}, {file = "openai_agents-0.13.5.tar.gz", hash = "sha256:ebe5bfb3d7d702d133ff9fb335718d61b741300de25b409ab4a6c8212ee945c0"}, @@ -2231,7 +2219,7 @@ types-requests = ">=2.0,<3" typing-extensions = ">=4.12.2,<5" [package.extras] -any-llm = ["any-llm-sdk (>=1.11.0,<2)"] +any-llm = ["any-llm-sdk (>=1.11.0,<2) ; python_version >= \"3.11\""] dapr = ["dapr (>=1.16.0)", "grpcio (>=1.60.0)"] encrypt = ["cryptography (>=45.0,<46)"] litellm = ["litellm (>=1.81.0,<=1.82.6)"] @@ -2239,7 +2227,7 @@ realtime = ["websockets (>=15.0,<16)"] redis = ["redis (>=7)"] sqlalchemy = ["asyncpg (>=0.29.0)", "sqlalchemy (>=2.0)"] viz = ["graphviz (>=0.17)"] -voice = ["numpy (>=2.2.0,<3)", "websockets (>=15.0,<16)"] +voice = ["numpy (>=2.2.0,<3) ; python_version >= \"3.10\"", "websockets (>=15.0,<16)"] [[package]] name = "opentelemetry-api" @@ -2248,7 +2236,7 @@ description = "OpenTelemetry Python API" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"otel\"" +markers = "extra == \"otel\"" files = [ {file = "opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9"}, {file = "opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f"}, @@ -2265,7 +2253,7 @@ description = "OpenTelemetry Python Distro" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"otel\"" +markers = "extra == \"otel\"" files = [ {file = "opentelemetry_distro-0.61b0-py3-none-any.whl", hash = "sha256:f21d1ac0627549795d75e332006dd068877f00e461b1b2e8fe4568d6eb7b9590"}, {file = "opentelemetry_distro-0.61b0.tar.gz", hash = "sha256:975b845f50181ad53753becf4fd4b123b54fa04df5a9d78812264436d6518981"}, @@ -2286,7 +2274,7 @@ description = "OpenTelemetry Collector Exporters" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"otel\"" +markers = "extra == \"otel\"" files = [ {file = "opentelemetry_exporter_otlp-1.40.0-py3-none-any.whl", hash = "sha256:48c87e539ec9afb30dc443775a1334cc5487de2f72a770a4c00b1610bf6c697d"}, {file = "opentelemetry_exporter_otlp-1.40.0.tar.gz", hash = "sha256:7caa0870b95e2fcb59d64e16e2b639ecffb07771b6cd0000b5d12e5e4fef765a"}, @@ -2303,7 +2291,7 @@ description = "OpenTelemetry Protobuf encoding" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"otel\"" +markers = "extra == \"otel\"" files = [ {file = "opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149"}, {file = "opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa"}, @@ -2319,7 +2307,7 @@ description = "OpenTelemetry Collector Protobuf over gRPC Exporter" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"otel\"" +markers = "extra == \"otel\"" files = [ {file = "opentelemetry_exporter_otlp_proto_grpc-1.40.0-py3-none-any.whl", hash = "sha256:2aa0ca53483fe0cf6405087a7491472b70335bc5c7944378a0a8e72e86995c52"}, {file = "opentelemetry_exporter_otlp_proto_grpc-1.40.0.tar.gz", hash = "sha256:bd4015183e40b635b3dab8da528b27161ba83bf4ef545776b196f0fb4ec47740"}, @@ -2348,7 +2336,7 @@ description = "OpenTelemetry Collector Protobuf over HTTP Exporter" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"otel\"" +markers = "extra == \"otel\"" files = [ {file = "opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl", hash = "sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069"}, {file = "opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz", hash = "sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c"}, @@ -2373,7 +2361,7 @@ description = "Instrumentation Tools & Auto Instrumentation for OpenTelemetry Py optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"otel\"" +markers = "extra == \"otel\"" files = [ {file = "opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63"}, {file = "opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7"}, @@ -2392,7 +2380,7 @@ description = "OpenTelemetry Python Proto" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"otel\"" +markers = "extra == \"otel\"" files = [ {file = "opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f"}, {file = "opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd"}, @@ -2408,7 +2396,7 @@ description = "OpenTelemetry Python SDK" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"otel\"" +markers = "extra == \"otel\"" files = [ {file = "opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1"}, {file = "opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2"}, @@ -2426,7 +2414,7 @@ description = "OpenTelemetry Semantic Conventions" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"otel\"" +markers = "extra == \"otel\"" files = [ {file = "opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2"}, {file = "opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a"}, @@ -2443,7 +2431,7 @@ description = "Core utilities for Python packages" optional = true python-versions = ">=3.8" groups = ["main"] -markers = "(extra == \"otel\" or extra == \"lint\" or extra == \"test\" or extra == \"docs\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" +markers = "extra == \"otel\" or extra == \"lint\" or extra == \"test\" or extra == \"docs\"" files = [ {file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"}, {file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"}, @@ -2456,7 +2444,7 @@ description = "Utility library for gitignore style pattern matching of file path optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(extra == \"lint\" or extra == \"docs\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" +markers = "extra == \"lint\" or extra == \"docs\"" files = [ {file = "pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723"}, {file = "pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645"}, @@ -2475,7 +2463,7 @@ description = "A small Python package for determining appropriate platform-speci optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(extra == \"lint\" or extra == \"docs\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" +markers = "extra == \"lint\" or extra == \"docs\"" files = [ {file = "platformdirs-4.9.4-py3-none-any.whl", hash = "sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868"}, {file = "platformdirs-4.9.4.tar.gz", hash = "sha256:1ec356301b7dc906d83f371c8f487070e99d3ccf9e501686456394622a01a934"}, @@ -2488,7 +2476,7 @@ description = "plugin and hook calling mechanisms for python" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"test\"" +markers = "extra == \"test\"" files = [ {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, @@ -2505,7 +2493,6 @@ description = "Python client for the Prometheus monitoring system." optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "prometheus_client-0.25.0-py3-none-any.whl", hash = "sha256:d5aec89e349a6ec230805d0df882f3807f74fd6c1a2fa86864e3c2279059fed1"}, {file = "prometheus_client-0.25.0.tar.gz", hash = "sha256:5e373b75c31afb3c86f1a52fa1ad470c9aace18082d39ec0d2f918d11cc9ba28"}, @@ -2523,7 +2510,6 @@ description = "Accelerated property cache" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "propcache-0.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c2d1fa3201efaf55d730400d945b5b3ab6e672e100ba0f9a409d950ab25d7db"}, {file = "propcache-0.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1eb2994229cc8ce7fe9b3db88f5465f5fd8651672840b2e426b88cdb1a30aac8"}, @@ -2656,7 +2642,6 @@ description = "" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "protobuf-6.33.6-cp310-abi3-win32.whl", hash = "sha256:7d29d9b65f8afef196f8334e80d6bc1d5d4adedb449971fefd3723824e6e77d3"}, {file = "protobuf-6.33.6-cp310-abi3-win_amd64.whl", hash = "sha256:0cd27b587afca21b7cfa59a74dcbd48a50f0a6400cfb59391340ad729d91d326"}, @@ -2677,7 +2662,7 @@ description = "Cross-platform lib for process and system monitoring." optional = true python-versions = ">=3.6" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"lint\"" +markers = "extra == \"lint\"" files = [ {file = "psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b"}, {file = "psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea"}, @@ -2703,8 +2688,8 @@ files = [ ] [package.extras] -dev = ["abi3audit", "black", "check-manifest", "colorama", "coverage", "packaging", "psleak", "pylint", "pyperf", "pypinfo", "pyreadline3", "pytest", "pytest-cov", "pytest-instafail", "pytest-xdist", "pywin32", "requests", "rstcheck", "ruff", "setuptools", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "validate-pyproject[all]", "virtualenv", "vulture", "wheel", "wheel", "wmi"] -test = ["psleak", "pytest", "pytest-instafail", "pytest-xdist", "pywin32", "setuptools", "wheel", "wmi"] +dev = ["abi3audit", "black", "check-manifest", "colorama ; os_name == \"nt\"", "coverage", "packaging", "psleak", "pylint", "pyperf", "pypinfo", "pyreadline3 ; os_name == \"nt\"", "pytest", "pytest-cov", "pytest-instafail", "pytest-xdist", "pywin32 ; os_name == \"nt\" and implementation_name != \"pypy\"", "requests", "rstcheck", "ruff", "setuptools", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "validate-pyproject[all]", "virtualenv", "vulture", "wheel", "wheel ; os_name == \"nt\" and implementation_name != \"pypy\"", "wmi ; os_name == \"nt\" and implementation_name != \"pypy\""] +test = ["psleak", "pytest", "pytest-instafail", "pytest-xdist", "pywin32 ; os_name == \"nt\" and implementation_name != \"pypy\"", "setuptools", "wheel ; os_name == \"nt\" and implementation_name != \"pypy\"", "wmi ; os_name == \"nt\" and implementation_name != \"pypy\""] [[package]] name = "psycopg" @@ -2713,7 +2698,7 @@ description = "PostgreSQL database adapter for Python" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"test\"" +markers = "extra == \"test\"" files = [ {file = "psycopg-3.3.4-py3-none-any.whl", hash = "sha256:b6bbc25ccf05c8fad3b061d9db2ef0909a555171b84b07f29458a447253d679a"}, {file = "psycopg-3.3.4.tar.gz", hash = "sha256:e21207764952cff81b6b8bdacad9a3939f2793367fdac2987b3aac36a651b5bc"}, @@ -2725,12 +2710,12 @@ typing-extensions = {version = ">=4.6", markers = "python_version < \"3.13\""} tzdata = {version = "*", markers = "sys_platform == \"win32\""} [package.extras] -binary = ["psycopg-binary (==3.3.4)"] -c = ["psycopg-c (==3.3.4)"] +binary = ["psycopg-binary (==3.3.4) ; implementation_name != \"pypy\""] +c = ["psycopg-c (==3.3.4) ; implementation_name != \"pypy\""] dev = ["ast-comments (>=1.1.2)", "black (>=26.1.0)", "codespell (>=2.2)", "cython-lint (>=0.16)", "dnspython (>=2.1)", "flake8 (>=4.0)", "isort-psycopg", "isort[colors] (>=6.0)", "mypy (>=1.19.0)", "pre-commit (>=4.0.1)", "types-setuptools (>=57.4)", "types-shapely (>=2.0)", "wheel (>=0.37)"] docs = ["Sphinx (>=9.1)", "furo (==2025.12.19)", "sphinx-autobuild (>=2025.8.25)", "sphinx-autodoc-typehints (>=3.10.2)"] pool = ["psycopg-pool"] -test = ["anyio (>=4.0)", "mypy (>=1.19.0)", "pproxy (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0)", "pytest-randomly (>=3.5)"] +test = ["anyio (>=4.0)", "mypy (>=1.19.0) ; implementation_name != \"pypy\"", "pproxy (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0)", "pytest-randomly (>=3.5)"] [[package]] name = "psycopg-pool" @@ -2739,7 +2724,7 @@ description = "Connection Pool for Psycopg" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"test\"" +markers = "extra == \"test\"" files = [ {file = "psycopg_pool-3.3.0-py3-none-any.whl", hash = "sha256:2e44329155c410b5e8666372db44276a8b1ebd8c90f1c3026ebba40d4bc81063"}, {file = "psycopg_pool-3.3.0.tar.gz", hash = "sha256:fa115eb2860bd88fce1717d75611f41490dec6135efb619611142b24da3f6db5"}, @@ -2758,7 +2743,7 @@ description = "C parser in Python" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(extra == \"openai\" or extra == \"claude\") and platform_python_implementation != \"PyPy\" and implementation_name != \"PyPy\" and (python_version <= \"3.13\" or python_version >= \"3.14\")" +markers = "(extra == \"openai\" or extra == \"claude\") and platform_python_implementation != \"PyPy\" and implementation_name != \"PyPy\"" files = [ {file = "pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992"}, {file = "pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29"}, @@ -2771,7 +2756,6 @@ description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba"}, {file = "pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6"}, @@ -2785,7 +2769,7 @@ typing-inspection = ">=0.4.2" [package.extras] email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata"] +timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] [[package]] name = "pydantic-core" @@ -2794,7 +2778,6 @@ description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "pydantic_core-2.46.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a396dcc17e5a0b164dbe026896245a4fa9ff402edca1dff0be3d53a517f74de4"}, {file = "pydantic_core-2.46.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da4b951fe36dc7c3a1ccb4e3cd1747c3542b8c9ceede8fc86cae054e764485f5"}, @@ -2928,7 +2911,6 @@ description = "Settings management using Pydantic" optional = false python-versions = ">=3.10" groups = ["main"] -markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "pydantic_settings-2.14.1-py3-none-any.whl", hash = "sha256:6e3c7edfd8277687cdc598f56e5cff0e9bfff0910a3749deaa8d4401c3a2b9de"}, {file = "pydantic_settings-2.14.1.tar.gz", hash = "sha256:e874d3bec7e787b0c9958277956ed9b4dd5de6a80e162188fdaff7c5e26fd5fa"}, @@ -2953,7 +2935,7 @@ description = "A Python docstring linter that checks arguments, returns, yields, optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" +markers = "extra == \"docs\"" files = [ {file = "pydoclint-0.8.3-py3-none-any.whl", hash = "sha256:5fc9b82d0d515afce0908cb70e8ff695a68b19042785c248c4f227ad66b4a164"}, {file = "pydoclint-0.8.3.tar.gz", hash = "sha256:0c69c0ed92c6f6b5aec2a14371bbd9e0a77980da8e2fa0f708092aa06b3b20b3"}, @@ -2974,7 +2956,7 @@ description = "Pygments is a syntax highlighting package written in Python." optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"test\"" +markers = "extra == \"test\"" files = [ {file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"}, {file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"}, @@ -2990,7 +2972,7 @@ description = "JSON Web Token implementation in Python" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" +markers = "extra == \"openai\" or extra == \"claude\"" files = [ {file = "pyjwt-2.12.1-py3-none-any.whl", hash = "sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c"}, {file = "pyjwt-2.12.1.tar.gz", hash = "sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b"}, @@ -3013,7 +2995,7 @@ description = "Extension pack for Python Markdown." optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" +markers = "extra == \"docs\"" files = [ {file = "pymdown_extensions-10.21.2-py3-none-any.whl", hash = "sha256:5c0fd2a2bea14eb39af8ff284f1066d898ab2187d81b889b75d46d4348c01638"}, {file = "pymdown_extensions-10.21.2.tar.gz", hash = "sha256:c3f55a5b8a1d0edf6699e35dcbea71d978d34ff3fa79f3d807b8a5b3fa90fbdc"}, @@ -3033,7 +3015,7 @@ description = "pytest: simple powerful testing with Python" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"test\"" +markers = "extra == \"test\"" files = [ {file = "pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9"}, {file = "pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c"}, @@ -3058,7 +3040,7 @@ description = "Pytest support for asyncio" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"test\"" +markers = "extra == \"test\"" files = [ {file = "pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5"}, {file = "pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5"}, @@ -3080,7 +3062,7 @@ description = "pytest plugin that allows you to add environment variables." optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"test\"" +markers = "extra == \"test\"" files = [ {file = "pytest_env-1.6.0-py3-none-any.whl", hash = "sha256:1e7f8a62215e5885835daaed694de8657c908505b964ec8097a7ce77b403d9a3"}, {file = "pytest_env-1.6.0.tar.gz", hash = "sha256:ac02d6fba16af54d61e311dd70a3c61024a4e966881ea844affc3c8f0bf207d3"}, @@ -3101,7 +3083,7 @@ description = "Adds the ability to retry flaky tests in CI environments" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"test\"" +markers = "extra == \"test\"" files = [ {file = "pytest_retry-1.7.0-py3-none-any.whl", hash = "sha256:a2dac85b79a4e2375943f1429479c65beb6c69553e7dae6b8332be47a60954f4"}, {file = "pytest_retry-1.7.0.tar.gz", hash = "sha256:f8d52339f01e949df47c11ba9ee8d5b362f5824dff580d3870ec9ae0057df80f"}, @@ -3120,7 +3102,7 @@ description = "pytest xdist plugin for distributed testing, most importantly acr optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"test\"" +markers = "extra == \"test\"" files = [ {file = "pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88"}, {file = "pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1"}, @@ -3142,7 +3124,6 @@ description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" groups = ["main"] -markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -3158,7 +3139,6 @@ description = "Read key-value pairs from a .env file and set them as environment optional = false python-versions = ">=3.10" groups = ["main"] -markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a"}, {file = "python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3"}, @@ -3174,7 +3154,7 @@ description = "A streaming multipart parser for Python" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" +markers = "extra == \"openai\" or extra == \"claude\"" files = [ {file = "python_multipart-0.0.27-py3-none-any.whl", hash = "sha256:6fccfad17a27334bd0193681b369f476eda3409f17381a2d65aa7df3f7275645"}, {file = "python_multipart-0.0.27.tar.gz", hash = "sha256:9870a6a8c5a20a5bf4f07c017bd1489006ff8836cff097b6933355ee2b49b602"}, @@ -3187,7 +3167,7 @@ description = "A Fast, spec compliant Python 3.14+ tokenizer that runs on older optional = true python-versions = ">=3.8" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"lint\"" +markers = "extra == \"lint\"" files = [ {file = "pytokens-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a44ed93ea23415c54f3face3b65ef2b844d96aeb3455b8a69b3df6beab6acc5"}, {file = "pytokens-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:add8bf86b71a5d9fb5b89f023a80b791e04fba57960aa790cc6125f7f1d39dfe"}, @@ -3243,7 +3223,7 @@ description = "Python for Window Extensions" optional = true python-versions = "*" groups = ["main"] -markers = "(extra == \"openai\" or extra == \"claude\") and sys_platform == \"win32\" and (python_version <= \"3.13\" or python_version >= \"3.14\")" +markers = "(extra == \"openai\" or extra == \"claude\") and sys_platform == \"win32\"" files = [ {file = "pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3"}, {file = "pywin32-311-cp310-cp310-win_amd64.whl", hash = "sha256:797c2772017851984b97180b0bebe4b620bb86328e8a884bb626156295a63b3b"}, @@ -3274,7 +3254,7 @@ description = "YAML parser and emitter for Python" optional = true python-versions = ">=3.8" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" +markers = "extra == \"docs\"" files = [ {file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"}, {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"}, @@ -3358,7 +3338,7 @@ description = "A custom YAML tag for referencing environment variables in YAML f optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" +markers = "extra == \"docs\"" files = [ {file = "pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04"}, {file = "pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff"}, @@ -3374,7 +3354,7 @@ description = "JSON Referencing + Python" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" +markers = "extra == \"openai\" or extra == \"claude\"" files = [ {file = "referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231"}, {file = "referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8"}, @@ -3392,7 +3372,7 @@ description = "Python HTTP for Humans." optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(extra == \"otel\" or extra == \"test\" or extra == \"openai\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" +markers = "extra == \"otel\" or extra == \"test\" or extra == \"openai\"" files = [ {file = "requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a"}, {file = "requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517"}, @@ -3415,7 +3395,7 @@ description = "Python bindings to Rust's persistent data structures (rpds)" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" +markers = "extra == \"openai\" or extra == \"claude\"" files = [ {file = "rpds_py-0.30.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:679ae98e00c0e8d68a7fda324e16b90fd5260945b45d3b824c892cec9eea3288"}, {file = "rpds_py-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cc2206b76b4f576934f0ed374b10d7ca5f457858b157ca52064bdfc26b9fc00"}, @@ -3541,7 +3521,7 @@ description = "An extremely fast Python linter and code formatter, written in Ru optional = true python-versions = ">=3.7" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"lint\"" +markers = "extra == \"lint\"" files = [ {file = "ruff-0.15.10-py3-none-linux_armv6l.whl", hash = "sha256:0744e31482f8f7d0d10a11fcbf897af272fefdfcb10f5af907b18c2813ff4d5f"}, {file = "ruff-0.15.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b1e7c16ea0ff5a53b7c2df52d947e685973049be1cdfe2b59a9c43601897b22e"}, @@ -3570,17 +3550,17 @@ description = "An Amazon S3 Transfer Manager" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"test\"" +markers = "extra == \"test\"" files = [ {file = "s3transfer-0.17.0-py3-none-any.whl", hash = "sha256:ce3801712acf4ad3e89fb9990df97b4972e93f4b3b0004d214be5bce12814c20"}, {file = "s3transfer-0.17.0.tar.gz", hash = "sha256:9edeb6d1c3c2f89d6050348548834ad8289610d886e5bf7b7207728bd43ce33a"}, ] [package.dependencies] -botocore = ">=1.37.4,<2.0a.0" +botocore = ">=1.37.4,<2.0a0" [package.extras] -crt = ["botocore[crt] (>=1.37.4,<2.0a.0)"] +crt = ["botocore[crt] (>=1.37.4,<2.0a0)"] [[package]] name = "setuptools" @@ -3589,20 +3569,19 @@ description = "Most extensible Python build backend with support for C/C++ exten optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb"}, {file = "setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.13.0)"] -core = ["importlib_metadata (>=6)", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.13.0) ; sys_platform != \"cygwin\""] +core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.18.*)", "pytest-mypy"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.18.*)", "pytest-mypy"] [[package]] name = "six" @@ -3611,7 +3590,6 @@ description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" groups = ["main"] -markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -3624,7 +3602,7 @@ description = "Sniff out which async library your code is running under" optional = true python-versions = ">=3.7" groups = ["main"] -markers = "(extra == \"docs\" or extra == \"openai\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" +markers = "extra == \"docs\" or extra == \"openai\"" files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -3637,7 +3615,7 @@ description = "A modern CSS selector implementation for Beautiful Soup." optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" +markers = "extra == \"docs\"" files = [ {file = "soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95"}, {file = "soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349"}, @@ -3650,7 +3628,7 @@ description = "SSE plugin for Starlette" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(extra == \"openai\" or extra == \"claude\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" +markers = "extra == \"openai\" or extra == \"claude\"" files = [ {file = "sse_starlette-3.3.4-py3-none-any.whl", hash = "sha256:84bb06e58939a8b38d8341f1bc9792f06c2b53f48c608dd207582b664fc8f3c1"}, {file = "sse_starlette-3.3.4.tar.gz", hash = "sha256:aaf92fc067af8a5427192895ac028e947b484ac01edbc3caf00e7e7137c7bef1"}, @@ -3673,7 +3651,7 @@ description = "The little ASGI library that shines." optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(extra == \"openai\" or extra == \"claude\" or extra == \"docs\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" +markers = "extra == \"openai\" or extra == \"claude\" or extra == \"docs\"" files = [ {file = "starlette-1.0.0-py3-none-any.whl", hash = "sha256:d3ec55e0bb321692d275455ddfd3df75fff145d009685eb40dc91fc66b03d38b"}, {file = "starlette-1.0.0.tar.gz", hash = "sha256:6a4beaf1f81bb472fd19ea9b918b50dc3a77a6f2e190a12954b25e6ed5eea149"}, @@ -3693,7 +3671,6 @@ description = "Retry code until it succeeds" optional = false python-versions = ">=3.10" groups = ["main"] -markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55"}, {file = "tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a"}, @@ -3710,7 +3687,7 @@ description = "A lil' TOML parser" optional = true python-versions = ">=3.8" groups = ["main"] -markers = "(extra == \"lint\" or extra == \"test\" or extra == \"docs\") and python_version < \"3.11\"" +markers = "(extra == \"lint\" or extra == \"test\" or extra == \"docs\") and python_version == \"3.10\"" files = [ {file = "tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30"}, {file = "tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a"}, @@ -3768,7 +3745,7 @@ description = "Fast, Extensible Progress Meter" optional = true python-versions = ">=3.7" groups = ["main"] -markers = "(extra == \"docs\" or extra == \"openai\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" +markers = "extra == \"docs\" or extra == \"openai\"" files = [ {file = "tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf"}, {file = "tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb"}, @@ -3791,7 +3768,7 @@ description = "Typing stubs for beautifulsoup4" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" +markers = "extra == \"docs\"" files = [ {file = "types_beautifulsoup4-4.12.0.20250516-py3-none-any.whl", hash = "sha256:5923399d4a1ba9cc8f0096fe334cc732e130269541d66261bb42ab039c0376ee"}, {file = "types_beautifulsoup4-4.12.0.20250516.tar.gz", hash = "sha256:aa19dd73b33b70d6296adf92da8ab8a0c945c507e6fb7d5db553415cc77b417e"}, @@ -3807,12 +3784,28 @@ description = "Typing stubs for grpcio" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"lint\"" +markers = "extra == \"lint\"" files = [ {file = "types_grpcio-1.0.0.20251009-py3-none-any.whl", hash = "sha256:112ac4312a5b0a273a4c414f7f2c7668f342990d9c6ab0f647391c36331f95ed"}, {file = "types_grpcio-1.0.0.20251009.tar.gz", hash = "sha256:a8f615ea7a47b31f10da028ab5258d4f1611fbd70719ca450fc0ab3fb9c62b63"}, ] +[[package]] +name = "types-grpcio-status" +version = "1.0.0.20260408" +description = "Typing stubs for grpcio-status" +optional = true +python-versions = ">=3.10" +groups = ["main"] +markers = "extra == \"lint\"" +files = [ + {file = "types_grpcio_status-1.0.0.20260408-py3-none-any.whl", hash = "sha256:5d421512b6a9ae5d0f84cde9c251394da5bd77ddc96e122221ac23c8e1ce3acf"}, + {file = "types_grpcio_status-1.0.0.20260408.tar.gz", hash = "sha256:8c8b5a5fd5e661488bb3dfb924fd3562892bae01b9a24e607a26bb0029f3fb71"}, +] + +[package.dependencies] +types-grpcio = "*" + [[package]] name = "types-html5lib" version = "1.1.11.20251117" @@ -3820,7 +3813,7 @@ description = "Typing stubs for html5lib" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" +markers = "extra == \"docs\"" files = [ {file = "types_html5lib-1.1.11.20251117-py3-none-any.whl", hash = "sha256:2a3fc935de788a4d2659f4535002a421e05bea5e172b649d33232e99d4272d08"}, {file = "types_html5lib-1.1.11.20251117.tar.gz", hash = "sha256:1a6a3ac5394aa12bf547fae5d5eff91dceec46b6d07c4367d9b39a37f42f201a"}, @@ -3836,7 +3829,7 @@ description = "Typing stubs for protobuf" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"lint\"" +markers = "extra == \"lint\"" files = [ {file = "types_protobuf-6.32.1.20260221-py3-none-any.whl", hash = "sha256:da7cdd947975964a93c30bfbcc2c6841ee646b318d3816b033adc2c4eb6448e4"}, {file = "types_protobuf-6.32.1.20260221.tar.gz", hash = "sha256:6d5fb060a616bfb076cbb61b4b3c3969f5fc8bec5810f9a2f7e648ee5cbcbf6e"}, @@ -3849,7 +3842,7 @@ description = "Typing stubs for psutil" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"lint\"" +markers = "extra == \"lint\"" files = [ {file = "types_psutil-7.2.2.20260508-py3-none-any.whl", hash = "sha256:b142452e0953f2d07dbdbb98d81f3a629f5906cc2d94bb7e34da0fba55fbab4a"}, {file = "types_psutil-7.2.2.20260508.tar.gz", hash = "sha256:8cfd8339f5e898570f80486423e65d87558d89d0181bf723d20ac5e778fe218e"}, @@ -3862,7 +3855,7 @@ description = "Typing stubs for requests" optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(extra == \"lint\" or extra == \"openai\") and (python_version <= \"3.13\" or python_version >= \"3.14\")" +markers = "extra == \"lint\" or extra == \"openai\"" files = [ {file = "types_requests-2.33.0.20260518-py3-none-any.whl", hash = "sha256:626d697d1adaaff76e2044dc8c5c051d8f21abc157bdfe204a75558076fe0bf0"}, {file = "types_requests-2.33.0.20260518.tar.gz", hash = "sha256:df7bd3bfe0ca8402dfb841e7d9be714bb5578203283d66d7dc4ef69343449a5e"}, @@ -3878,7 +3871,7 @@ description = "Typing stubs for webencodings" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" +markers = "extra == \"docs\"" files = [ {file = "types_webencodings-0.5.0.20251108-py3-none-any.whl", hash = "sha256:e21f81ff750795faffddaffd70a3d8bfff77d006f22c27e393eb7812586249d8"}, {file = "types_webencodings-0.5.0.20251108.tar.gz", hash = "sha256:2378e2ceccced3d41bb5e21387586e7b5305e11519fc6b0659c629f23b2e5de4"}, @@ -3891,7 +3884,6 @@ description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, @@ -3904,7 +3896,6 @@ description = "Runtime typing introspection tools" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, @@ -3920,7 +3911,7 @@ description = "Provider of IANA time zone data" optional = true python-versions = ">=2" groups = ["main"] -markers = "extra == \"test\" and sys_platform == \"win32\" and (python_version <= \"3.13\" or python_version >= \"3.14\")" +markers = "extra == \"test\" and sys_platform == \"win32\"" files = [ {file = "tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1"}, {file = "tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7"}, @@ -3933,17 +3924,16 @@ description = "HTTP library with thread-safe connection pooling, file post, and optional = false python-versions = ">=3.10" groups = ["main"] -markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897"}, {file = "urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c"}, ] [package.extras] -brotli = ["brotli (>=1.2.0)", "brotlicffi (>=1.2.0.0)"] +brotli = ["brotli (>=1.2.0) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=1.2.0.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["backports-zstd (>=1.0.0)"] +zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""] [[package]] name = "uvicorn" @@ -3952,7 +3942,7 @@ description = "The lightning-fast ASGI server." optional = true python-versions = ">=3.10" groups = ["main"] -markers = "(extra == \"openai\" or extra == \"claude\" or extra == \"docs\") and (python_version <= \"3.13\" or python_version >= \"3.14\") and (sys_platform != \"emscripten\" or extra == \"docs\")" +markers = "(extra == \"openai\" or extra == \"claude\") and sys_platform != \"emscripten\" or extra == \"docs\"" files = [ {file = "uvicorn-0.46.0-py3-none-any.whl", hash = "sha256:bbebbcbed972d162afca128605223022bedd345b7bc7855ce66deb31487a9048"}, {file = "uvicorn-0.46.0.tar.gz", hash = "sha256:fb9da0926999cc6cb22dc7cd71a94a632f078e6ae47ff683c5c420750fb7413d"}, @@ -3964,7 +3954,7 @@ h11 = ">=0.8" typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} [package.extras] -standard = ["colorama (>=0.4)", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.15.1)", "watchfiles (>=0.20)", "websockets (>=10.4)"] +standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.15.1) ; sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"", "watchfiles (>=0.20)", "websockets (>=10.4)"] [[package]] name = "watchdog" @@ -3973,7 +3963,7 @@ description = "Filesystem events monitoring" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"docs\"" +markers = "extra == \"docs\"" files = [ {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26"}, {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112"}, @@ -4017,7 +4007,7 @@ description = "Module for decorators, wrappers and monkey patching." optional = true python-versions = ">=3.8" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"otel\"" +markers = "extra == \"otel\"" files = [ {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88bbae4d40d5a46142e70d58bf664a89b6b4befaea7b2ecc14e03cedb8e06c04"}, {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b13af258d6a9ad602d57d889f83b9d5543acd471eee12eb51f5b01f8eb1bc2"}, @@ -4109,7 +4099,6 @@ description = "Yet another URL library" optional = false python-versions = ">=3.10" groups = ["main"] -markers = "python_version <= \"3.13\" or python_version >= \"3.14\"" files = [ {file = "yarl-1.23.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cff6d44cb13d39db2663a22b22305d10855efa0fa8015ddeacc40bc59b9d8107"}, {file = "yarl-1.23.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4c53f8347cd4200f0d70a48ad059cabaf24f5adc6ba08622a23423bc7efa10d"}, @@ -4253,14 +4242,14 @@ description = "Backport of pathlib-compatible object wrapper for zip files" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(python_version <= \"3.13\" or python_version >= \"3.14\") and extra == \"otel\"" +markers = "extra == \"otel\"" files = [ {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] @@ -4270,7 +4259,7 @@ type = ["pytest-mypy"] [extras] claude = ["claude-agent-sdk", "mcp"] docs = ["bs4", "fastapi", "markdownify", "mkdocs", "mkdocstrings", "openai", "pydoclint", "types-beautifulsoup4", "uvicorn"] -lint = ["black", "click", "mypy", "psutil", "ruff", "types-grpcio", "types-protobuf", "types-psutil", "types-requests"] +lint = ["black", "click", "mypy", "psutil", "ruff", "types-grpcio", "types-grpcio-status", "types-protobuf", "types-psutil", "types-requests"] openai = ["openai-agents"] otel = ["opentelemetry-api", "opentelemetry-distro", "opentelemetry-exporter-otlp", "opentelemetry-exporter-otlp-proto-http", "opentelemetry-instrumentation", "opentelemetry-sdk"] test = ["boto3", "botocore", "psycopg", "pytest", "pytest-asyncio", "pytest-env", "pytest-retry", "pytest-xdist", "requests"] @@ -4278,4 +4267,4 @@ test = ["boto3", "botocore", "psycopg", "pytest", "pytest-asyncio", "pytest-env" [metadata] lock-version = "2.1" python-versions = ">=3.10,<4" -content-hash = "03cc0e4d6d768e9565f0e0ad9fe3960a77357cc42683e0b63f40963e830c3b03" +content-hash = "5e3a61fa8c3fb487729adccbfd0e8ed35103c36527cce52848f0ec377c547440" diff --git a/sdks/python/pyproject.toml b/sdks/python/pyproject.toml index 313dbb39d6..ae1d28bee7 100644 --- a/sdks/python/pyproject.toml +++ b/sdks/python/pyproject.toml @@ -48,6 +48,7 @@ lint = [ "types-requests>=2.33.0.20260518", "psutil>=6.0.0", "types-grpcio>=1.0.0", + "types-grpcio-status (>=1.0.0.20260408,<2.0.0.0)", ] test = [ From 0e4cafb9646d82f77d5667e8251a38e9c0704e26 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Tue, 2 Jun 2026 10:33:20 -0400 Subject: [PATCH 29/69] fix: drive by fix for a flaky test caused by event condition race --- sdks/python/examples/events/test_event.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sdks/python/examples/events/test_event.py b/sdks/python/examples/events/test_event.py index bc32671cff..2519d9a8bd 100644 --- a/sdks/python/examples/events/test_event.py +++ b/sdks/python/examples/events/test_event.py @@ -570,6 +570,7 @@ async def test_multi_scope_bug(hatchet: Hatchet, test_run_id: str) -> None: runs = await hatchet.runs.aio_list( triggering_event_external_id=event.event_id, additional_metadata={"test_run_id": test_run_id}, + workflow_ids=[event_workflow.id], ) assert len(runs.rows) == 1 From 9929b37f06926a9d6a521d2d98596fbe976c496d Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Tue, 2 Jun 2026 11:44:09 -0400 Subject: [PATCH 30/69] fix: not found behavior on the get_details method --- sdks/python/hatchet_sdk/clients/admin.py | 5 ++++- sdks/python/hatchet_sdk/clients/rest/tenacity_utils.py | 10 ++++++++-- sdks/python/hatchet_sdk/config.py | 4 ++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/sdks/python/hatchet_sdk/clients/admin.py b/sdks/python/hatchet_sdk/clients/admin.py index fc274f3935..9b68cf2cc3 100644 --- a/sdks/python/hatchet_sdk/clients/admin.py +++ b/sdks/python/hatchet_sdk/clients/admin.py @@ -633,7 +633,10 @@ def get_workflow_run(self, workflow_run_id: str) -> WorkflowRunRef: def get_details(self, external_id: str) -> WorkflowRunDetail: client = self._get_or_create_v1_client() - get_run_payloads = tenacity_retry(client.GetRunDetails, self.config.tenacity) + get_run_payloads = tenacity_retry( + client.GetRunDetails, + self.config.tenacity.model_copy(update={"retry_not_found": True}), + ) response = cast( workflow_protos.GetRunDetailsResponse, diff --git a/sdks/python/hatchet_sdk/clients/rest/tenacity_utils.py b/sdks/python/hatchet_sdk/clients/rest/tenacity_utils.py index 7a543a19ea..cfefcfe72a 100644 --- a/sdks/python/hatchet_sdk/clients/rest/tenacity_utils.py +++ b/sdks/python/hatchet_sdk/clients/rest/tenacity_utils.py @@ -48,14 +48,20 @@ def tenacity_should_retry( # gRPC errors: retry most, except specific permanent failure codes if isinstance(ex, grpc.aio.AioRpcError | grpc.RpcError): - return ex.code() not in [ + non_retryable = [ grpc.StatusCode.UNIMPLEMENTED, - grpc.StatusCode.NOT_FOUND, grpc.StatusCode.INVALID_ARGUMENT, grpc.StatusCode.ALREADY_EXISTS, grpc.StatusCode.UNAUTHENTICATED, grpc.StatusCode.PERMISSION_DENIED, ] + if not config or not config.retry_not_found: + ## don't retry NOT_FOUND by default, + ## but allow it to be configurable so that we can + ## allow this internally, e.g. in `get_details` + non_retryable.append(grpc.StatusCode.NOT_FOUND) + + return ex.code() not in non_retryable # REST transport errors: opt-in retry for configured HTTP methods if isinstance(ex, RestTransportError): diff --git a/sdks/python/hatchet_sdk/config.py b/sdks/python/hatchet_sdk/config.py index e78ab08668..22bcf392d5 100644 --- a/sdks/python/hatchet_sdk/config.py +++ b/sdks/python/hatchet_sdk/config.py @@ -118,6 +118,10 @@ class TenacityConfig(BaseSettings): default=False, description="Enable retries for HTTP 429 Too Many Requests responses. Default: off.", ) + retry_not_found: bool = Field( + default=False, + description="Enable retries for NOT_FOUND gRPC responses. For resources that may not yet exist due to async creation. Default: off.", + ) retry_transport_errors: bool = Field( default=False, description="Enable retries for REST transport errors (timeout, connection, TLS). Default: off.", From e5d54027f2d8d86a449db2aba14c34ee52c40c32 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Tue, 2 Jun 2026 12:20:59 -0400 Subject: [PATCH 31/69] fix: revert python changes --- sdks/python/hatchet_sdk/clients/admin.py | 106 +++++++++-------------- 1 file changed, 39 insertions(+), 67 deletions(-) diff --git a/sdks/python/hatchet_sdk/clients/admin.py b/sdks/python/hatchet_sdk/clients/admin.py index 9b68cf2cc3..3e8c24439d 100644 --- a/sdks/python/hatchet_sdk/clients/admin.py +++ b/sdks/python/hatchet_sdk/clients/admin.py @@ -191,7 +191,7 @@ def _prepare_workflow_request( workflow_name: str, input: str | None, options: TriggerWorkflowOptions, - ) -> workflow_protos.TriggerWorkflowRunRequest: + ) -> trigger_protos.TriggerWorkflowRequest: _options = self.TriggerWorkflowRequest.model_validate(options.model_dump()) desired_worker_labels = None @@ -216,14 +216,15 @@ def _prepare_workflow_request( for key, d in labels_dict.items() } - return workflow_protos.TriggerWorkflowRunRequest( - workflow_name=workflow_name, - input=input.encode("utf-8") if input else None, - additional_metadata=( - _options.additional_metadata.encode("utf-8") - if _options.additional_metadata - else None - ), + return trigger_protos.TriggerWorkflowRequest( + name=workflow_name, + input=input, + parent_id=_options.parent_id, + parent_task_run_external_id=_options.parent_step_run_id, + child_index=_options.child_index, + child_key=_options.child_key, + additional_metadata=_options.additional_metadata, + desired_worker_id=_options.desired_worker_id, priority=_options.priority, desired_worker_labels=desired_worker_labels, ) @@ -402,47 +403,9 @@ def _create_workflow_run_request( ) namespace = options.namespace or self.namespace - workflow_name = self.config.apply_namespace(workflow_name, namespace) - _options = self.TriggerWorkflowRequest.model_validate( - trigger_options.model_dump() - ) - - desired_worker_labels = None - if _options.desired_worker_label: - _warn_if_dict_desired_worker_labels( - _options.desired_worker_label, stacklevel=6 - ) - if isinstance(_options.desired_worker_label, list): - labels_dict = { - d.key: d for d in _options.desired_worker_label if d.key is not None - } - else: - labels_dict = _options.desired_worker_label - desired_worker_labels = { - key: trigger_protos.DesiredWorkerLabels( - str_value=d.value if not isinstance(d.value, int) else None, - int_value=d.value if isinstance(d.value, int) else None, - required=d.required, - weight=d.weight, - comparator=d.comparator, # type: ignore[arg-type] - ) - for key, d in labels_dict.items() - } - - return trigger_protos.TriggerWorkflowRequest( - name=workflow_name, - input=input, - parent_id=_options.parent_id, - parent_task_run_external_id=_options.parent_step_run_id, - child_index=_options.child_index, - child_key=_options.child_key, - additional_metadata=_options.additional_metadata, - desired_worker_id=_options.desired_worker_id, - priority=_options.priority, - desired_worker_labels=desired_worker_labels, - ) + return self._prepare_workflow_request(workflow_name, input, trigger_options) def run_workflow( self, @@ -450,29 +413,40 @@ def run_workflow( input: str | None, options: TriggerWorkflowOptions = TriggerWorkflowOptions(), ) -> WorkflowRunRef: - namespace = options.namespace or self.namespace - namespaced_name = self.config.apply_namespace(workflow_name, namespace) - request = self._prepare_workflow_request(namespaced_name, input, options) - client = self._get_or_create_v1_client() - trigger_workflow = tenacity_retry( - client.TriggerWorkflowRun, self.config.tenacity - ) + request = self._create_workflow_run_request(workflow_name, input, options) + client = self._get_or_create_v0_client() + trigger_workflow = tenacity_retry(client.TriggerWorkflow, self.config.tenacity) try: resp = cast( - workflow_protos.TriggerWorkflowRunResponse, + v0_workflow_protos.TriggerWorkflowResponse, trigger_workflow( request, metadata=create_authorization_header(self.token), ), ) except (grpc.RpcError, grpc.aio.AioRpcError) as e: - if e.code() == grpc.StatusCode.ALREADY_EXISTS: + if ( + e.code() == grpc.StatusCode.ALREADY_EXISTS + and e.details() == "idempotency key collision" + ): + status: list[Any] = rpc_status.from_call(e) # type: ignore[arg-type] + + for detail in status.details: # type: ignore[attr-defined] + if detail.Is(workflow_protos.IdempotencyCollisionError.DESCRIPTOR): + info = workflow_protos.IdempotencyCollisionError() + detail.Unpack(info) + + raise IdempotencyCollisionError( + existing_run_external_id=info.existing_run_external_id + ) from e + elif e.code() == grpc.StatusCode.ALREADY_EXISTS: raise DedupeViolationError(e.details()) from e + raise e return WorkflowRunRef( - workflow_run_id=resp.external_id, + workflow_run_id=resp.workflow_run_id, workflow_run_event_listener=self.workflow_run_event_listener, workflow_run_listener=self.workflow_run_listener, admin_client=self, @@ -484,17 +458,15 @@ async def aio_run_workflow( input: str | None, options: TriggerWorkflowOptions = TriggerWorkflowOptions(), ) -> WorkflowRunRef: - namespace = options.namespace or self.namespace - namespaced_name = self.config.apply_namespace(workflow_name, namespace) - client = self._get_or_create_v1_client() - trigger_workflow = tenacity_retry( - client.TriggerWorkflowRun, self.config.tenacity - ) - request = self._prepare_workflow_request(namespaced_name, input, options) + async with spawn_index_lock: + request = self._create_workflow_run_request(workflow_name, input, options) + + client = self._get_or_create_v0_client() + trigger_workflow = tenacity_retry(client.TriggerWorkflow, self.config.tenacity) try: resp = cast( - workflow_protos.TriggerWorkflowRunResponse, + v0_workflow_protos.TriggerWorkflowResponse, await asyncio.to_thread( trigger_workflow, request, @@ -522,7 +494,7 @@ async def aio_run_workflow( raise e return WorkflowRunRef( - workflow_run_id=resp.external_id, + workflow_run_id=resp.workflow_run_id, workflow_run_event_listener=self.workflow_run_event_listener, workflow_run_listener=self.workflow_run_listener, admin_client=self, From 1c93e1cde9ef13514c005835a897f890cdd6b291 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Tue, 2 Jun 2026 12:41:36 -0400 Subject: [PATCH 32/69] fix: handle collisions in the v1 ingest endpoint :facepalm: --- internal/services/admin/server_v1.go | 110 +++++++++++++++++++-------- 1 file changed, 78 insertions(+), 32 deletions(-) diff --git a/internal/services/admin/server_v1.go b/internal/services/admin/server_v1.go index 6376e9c725..0587f9f8bb 100644 --- a/internal/services/admin/server_v1.go +++ b/internal/services/admin/server_v1.go @@ -76,7 +76,7 @@ func (a *AdminServiceImpl) triggerWorkflowV1(ctx context.Context, req *v1contrac return nil, fmt.Errorf("could not generate external ids: %w", err) } - err = a.ingest( + idempotencyKeyCollisions, err := a.ingest( ctx, tenantId, opt, @@ -86,6 +86,22 @@ func (a *AdminServiceImpl) triggerWorkflowV1(ctx context.Context, req *v1contrac return nil, fmt.Errorf("could not trigger workflow: %w", err) } + for _, collision := range idempotencyKeyCollisions { + if collision.RequestedExternalId == opt.ExternalId { + st, stErr := status.New(codes.AlreadyExists, "idempotency key collision").WithDetails( + &v1contracts.IdempotencyCollisionError{ + ExistingRunExternalId: collision.ExistingExternalId.String(), + }, + ) + + if stErr != nil { + return nil, status.Errorf(codes.Internal, "failed to build idempotency collision error: %v", stErr) + } + + return nil, st.Err() + } + } + additionalMeta := "" if req.AdditionalMetadata != nil { additionalMeta = *req.AdditionalMetadata @@ -141,7 +157,7 @@ func (a *AdminServiceImpl) bulkTriggerWorkflowV1(ctx context.Context, req *contr return nil, fmt.Errorf("could not generate external ids: %w", err) } - err = a.ingest( + _, err = a.ingest( ctx, tenantId, opts..., @@ -232,7 +248,7 @@ func (i *AdminServiceImpl) generateExternalIds(ctx context.Context, tenantId uui return i.repov1.Triggers().PopulateExternalIdsForWorkflow(ctx, tenantId, opts) } -func (i *AdminServiceImpl) ingest(ctx context.Context, tenantId uuid.UUID, opts ...*v1.WorkflowNameTriggerOpts) error { +func (i *AdminServiceImpl) ingest(ctx context.Context, tenantId uuid.UUID, opts ...*v1.WorkflowNameTriggerOpts) ([]v1.IdempotencyCollision, error) { optsToSend := make([]*v1.WorkflowNameTriggerOpts, 0) for _, opt := range opts { @@ -244,7 +260,7 @@ func (i *AdminServiceImpl) ingest(ctx context.Context, tenantId uuid.UUID, opts } if len(optsToSend) == 0 { - return nil + return nil, nil } if i.localScheduler != nil { @@ -254,9 +270,8 @@ func (i *AdminServiceImpl) ingest(ctx context.Context, tenantId uuid.UUID, opts localWorkerIds = i.localDispatcher.GetLocalWorkerIds() } - localAssigned, _, schedulingErr := i.localScheduler.RunOptimisticScheduling(ctx, tenantId, opts, localWorkerIds) + localAssigned, idempotencyKeyCollisions, schedulingErr := i.localScheduler.RunOptimisticScheduling(ctx, tenantId, opts, localWorkerIds) - // if we have a scheduling error, we'll fall back to normal ingestion if schedulingErr != nil { if !errors.Is(schedulingErr, schedulingv1.ErrTenantNotFound) && !errors.Is(schedulingErr, schedulingv1.ErrNoOptimisticSlots) { i.l.Error().Ctx(ctx).Err(schedulingErr).Msg("could not run optimistic scheduling") @@ -286,53 +301,84 @@ func (i *AdminServiceImpl) ingest(ctx context.Context, tenantId uuid.UUID, opts // we return nil because the failed assignments would have been requeued by the local dispatcher, // and we have already written the tasks to the database - return nil + return idempotencyKeyCollisions, nil } - // if there's no scheduling error, we return here because the tasks have been scheduled optimistically if schedulingErr == nil { - return nil + return idempotencyKeyCollisions, nil } } else if i.tw != nil { - _, triggerErr := i.tw.TriggerFromWorkflowNames(ctx, tenantId, optsToSend) + idempotencyKeyCollisions, triggerErr := i.tw.TriggerFromWorkflowNames(ctx, tenantId, optsToSend) - // if we fail to trigger via gRPC, we fall back to normal ingestion if triggerErr != nil && !errors.Is(triggerErr, trigger.ErrNoTriggerSlots) { i.l.Error().Ctx(ctx).Err(triggerErr).Msg("could not trigger workflow runs via gRPC") } else if triggerErr == nil { - return nil + return idempotencyKeyCollisions, nil } } - verifyErr := i.repov1.Triggers().PreflightVerifyWorkflowNameOpts(ctx, tenantId, optsToSend) + if err := i.repov1.Triggers().PopulateWorkflowIdempotencyPresence(ctx, tenantId, optsToSend); err != nil { + return nil, fmt.Errorf("could not populate workflow idempotency presence: %w", err) + } - if verifyErr != nil { - namesNotFound := &v1.ErrNamesNotFound{} + optsWithIdempotencyKeys := make([]*v1.WorkflowNameTriggerOpts, 0, len(optsToSend)) + optsWithoutIdempotencyKeys := make([]*v1.WorkflowNameTriggerOpts, 0, len(optsToSend)) - if errors.As(verifyErr, &namesNotFound) { - return status.Error( - codes.InvalidArgument, - verifyErr.Error(), - ) + for _, opt := range optsToSend { + if opt.HasIdempotencyKey { + optsWithIdempotencyKeys = append(optsWithIdempotencyKeys, opt) + } else { + optsWithoutIdempotencyKeys = append(optsWithoutIdempotencyKeys, opt) } - - return fmt.Errorf("could not verify workflow name opts: %w", verifyErr) } - msg, err := tasktypes.TriggerTaskMessage( - tenantId, - optsToSend..., - ) + var allIdempotencyKeyCollisions []v1.IdempotencyCollision - if err != nil { - return fmt.Errorf("could not create event task: %w", err) + if len(optsWithIdempotencyKeys) > 0 { + tasks, dags, collisions, err := i.repov1.Triggers().TriggerFromWorkflowNames(ctx, tenantId, optsWithIdempotencyKeys) + + if err != nil { + return nil, fmt.Errorf("could not trigger workflows with idempotency keys: %w", err) + } + + if i.tw != nil { + if signalErr := i.tw.SignalCreated(ctx, tenantId, tasks, dags); signalErr != nil { + i.l.Error().Ctx(ctx).Err(signalErr).Msg("failed to signal created tasks and DAGs") + } + } + + allIdempotencyKeyCollisions = append(allIdempotencyKeyCollisions, collisions...) } - err = i.mqv1.SendMessage(ctx, msgqueue.TASK_PROCESSING_QUEUE, msg) + if len(optsWithoutIdempotencyKeys) > 0 { + verifyErr := i.repov1.Triggers().PreflightVerifyWorkflowNameOpts(ctx, tenantId, optsWithoutIdempotencyKeys) - if err != nil { - return fmt.Errorf("could not add event to task queue: %w", err) + if verifyErr != nil { + namesNotFound := &v1.ErrNamesNotFound{} + + if errors.As(verifyErr, &namesNotFound) { + return nil, status.Error( + codes.InvalidArgument, + verifyErr.Error(), + ) + } + + return nil, fmt.Errorf("could not verify workflow name opts: %w", verifyErr) + } + + msg, err := tasktypes.TriggerTaskMessage( + tenantId, + optsWithoutIdempotencyKeys..., + ) + + if err != nil { + return nil, fmt.Errorf("could not create event task: %w", err) + } + + if err = i.mqv1.SendMessage(ctx, msgqueue.TASK_PROCESSING_QUEUE, msg); err != nil { + return nil, fmt.Errorf("could not add event to task queue: %w", err) + } } - return nil + return allIdempotencyKeyCollisions, nil } From 05196fad40e9fdfffd2130233e0bb9bed5f21175 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Tue, 2 Jun 2026 12:51:50 -0400 Subject: [PATCH 33/69] chore: gen --- examples/python/idempotency/trigger.py | 4 ++++ examples/python/idempotency/worker.py | 30 ++++++++++++++++++++++++++ examples/python/worker.py | 2 ++ 3 files changed, 36 insertions(+) create mode 100644 examples/python/idempotency/trigger.py create mode 100644 examples/python/idempotency/worker.py diff --git a/examples/python/idempotency/trigger.py b/examples/python/idempotency/trigger.py new file mode 100644 index 0000000000..9eadfb31b7 --- /dev/null +++ b/examples/python/idempotency/trigger.py @@ -0,0 +1,4 @@ +from examples.idempotency.worker import idempotent_task, IdempotencyInput + +idempotent_task.run(input=IdempotencyInput(id="123"), wait_for_result=False) +idempotent_task.run(input=IdempotencyInput(id="123"), wait_for_result=False) diff --git a/examples/python/idempotency/worker.py b/examples/python/idempotency/worker.py new file mode 100644 index 0000000000..54e69e5a6d --- /dev/null +++ b/examples/python/idempotency/worker.py @@ -0,0 +1,30 @@ +from hatchet_sdk import Context, Hatchet, IdempotencyConfig +from datetime import timedelta +from pydantic import BaseModel + +hatchet = Hatchet() + + +class IdempotencyInput(BaseModel): + id: str + + +@hatchet.task( + idempotency=IdempotencyConfig(key_expression="input.id", ttl=timedelta(minutes=1)), + input_validator=IdempotencyInput, + on_events=["idempotency:example"], +) +async def idempotent_task(input: IdempotencyInput, ctx: Context) -> dict[str, str]: + return {"result": f"Hello, world from task {input.id}"} + + +def main() -> None: + worker = hatchet.worker( + "test-worker", + workflows=[idempotent_task], + ) + worker.start() + + +if __name__ == "__main__": + main() diff --git a/examples/python/worker.py b/examples/python/worker.py index 14cd4dbeaa..3877df3d46 100644 --- a/examples/python/worker.py +++ b/examples/python/worker.py @@ -101,6 +101,7 @@ durable_parent_child_key_bug, child_child_key_bug, ) +from examples.idempotency.worker import idempotent_task from hatchet_sdk import Hatchet hatchet = Hatchet() @@ -195,6 +196,7 @@ def main() -> None: child_child_key_bug, durable_child_key_dedup_replay, durable_spawn_many_dags, + idempotent_task, ], lifespan=lifespan, ) From 4f3103c7a16f8c3c639b6c94afaa297eeb8aa719 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Tue, 2 Jun 2026 12:58:52 -0400 Subject: [PATCH 34/69] chore: add back some comments to shrink diff --- internal/services/admin/server_v1.go | 3 +++ internal/services/admin/v1/server.go | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/internal/services/admin/server_v1.go b/internal/services/admin/server_v1.go index 0587f9f8bb..ecc967014f 100644 --- a/internal/services/admin/server_v1.go +++ b/internal/services/admin/server_v1.go @@ -272,6 +272,7 @@ func (i *AdminServiceImpl) ingest(ctx context.Context, tenantId uuid.UUID, opts localAssigned, idempotencyKeyCollisions, schedulingErr := i.localScheduler.RunOptimisticScheduling(ctx, tenantId, opts, localWorkerIds) + // if we have a scheduling error, we'll fall back to normal ingestion if schedulingErr != nil { if !errors.Is(schedulingErr, schedulingv1.ErrTenantNotFound) && !errors.Is(schedulingErr, schedulingv1.ErrNoOptimisticSlots) { i.l.Error().Ctx(ctx).Err(schedulingErr).Msg("could not run optimistic scheduling") @@ -304,12 +305,14 @@ func (i *AdminServiceImpl) ingest(ctx context.Context, tenantId uuid.UUID, opts return idempotencyKeyCollisions, nil } + // if there's no scheduling error, we return here because the tasks have been scheduled optimistically if schedulingErr == nil { return idempotencyKeyCollisions, nil } } else if i.tw != nil { idempotencyKeyCollisions, triggerErr := i.tw.TriggerFromWorkflowNames(ctx, tenantId, optsToSend) + // if we fail to trigger via gRPC, we fall back to normal ingestion if triggerErr != nil && !errors.Is(triggerErr, trigger.ErrNoTriggerSlots) { i.l.Error().Ctx(ctx).Err(triggerErr).Msg("could not trigger workflow runs via gRPC") } else if triggerErr == nil { diff --git a/internal/services/admin/v1/server.go b/internal/services/admin/v1/server.go index c8f8f91364..66b441a453 100644 --- a/internal/services/admin/v1/server.go +++ b/internal/services/admin/v1/server.go @@ -626,6 +626,7 @@ func (a *AdminServiceImpl) ingest(ctx context.Context, tenantId uuid.UUID, opts localAssigned, idempotencyKeyCollisions, schedulingErr := a.localScheduler.RunOptimisticScheduling(ctx, tenantId, opts, localWorkerIds) + // if we have a scheduling error, we'll fall back to normal ingestion if schedulingErr != nil { if !errors.Is(schedulingErr, schedulingv1.ErrTenantNotFound) && !errors.Is(schedulingErr, schedulingv1.ErrNoOptimisticSlots) { a.l.Error().Ctx(ctx).Err(schedulingErr).Msg("could not run optimistic scheduling") @@ -653,9 +654,12 @@ func (a *AdminServiceImpl) ingest(ctx context.Context, tenantId uuid.UUID, opts a.l.Error().Ctx(ctx).Err(dispatcherErr).Msg("could not handle local assignments") } + // we return nil because the failed assignments would have been requeued by the local dispatcher, + // and we have already written the tasks to the database return idempotencyKeyCollisions, nil } + // if there's no scheduling error, we return here because the tasks have been scheduled optimistically if schedulingErr == nil { return idempotencyKeyCollisions, nil } From 71784e7f91b9cbb0bedb358c4ab84e11f8533820 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Tue, 2 Jun 2026 13:02:40 -0400 Subject: [PATCH 35/69] chore: python version, changelog --- sdks/python/CHANGELOG.md | 6 +++++- sdks/python/pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/sdks/python/CHANGELOG.md b/sdks/python/CHANGELOG.md index 1e94f48910..cabf9d99d6 100644 --- a/sdks/python/CHANGELOG.md +++ b/sdks/python/CHANGELOG.md @@ -5,7 +5,11 @@ All notable changes to Hatchet's Python SDK will be documented in this changelog The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [1.34.0] + +### Added + +- Adds support for defining **idempotency keys** on workflows and standalone tasks, which ensures that they're only run once in a provided time window, based on a CEL expression. ### Fixed diff --git a/sdks/python/pyproject.toml b/sdks/python/pyproject.toml index ae1d28bee7..b608912611 100644 --- a/sdks/python/pyproject.toml +++ b/sdks/python/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "hatchet-sdk" -version = "1.33.6" +version = "1.34.0" description = "This is the official Python SDK for Hatchet, a distributed, fault-tolerant task queue. The SDK allows you to easily integrate Hatchet's task scheduling and workflow orchestration capabilities into your Python applications." readme = "README.md" license = { text = "MIT" } From cdd0f43aaaf27c35b0f183c03253acbcdb40fb79 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Tue, 2 Jun 2026 15:19:00 -0400 Subject: [PATCH 36/69] fix: use the correct tx --- pkg/repository/trigger.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/repository/trigger.go b/pkg/repository/trigger.go index 9cd6f263c7..88f012db10 100644 --- a/pkg/repository/trigger.go +++ b/pkg/repository/trigger.go @@ -710,7 +710,7 @@ func (r *sharedRepository) triggerWorkflows( var idempotencyKeyCollisions []IdempotencyCollision if len(keys) > 0 { - claims, err := r.queries.ClaimIdempotencyKeys(ctx, existingTx.tx, sqlcv1.ClaimIdempotencyKeysParams{ + claims, err := r.queries.ClaimIdempotencyKeys(ctx, preflightTx, sqlcv1.ClaimIdempotencyKeysParams{ Keys: keys, Expiresats: expiresAts, Claimedbyexternalids: claimedByExternalIds, From bfc997b8cddc6f09808ce0eb13c49d9e9fd9c727 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Tue, 2 Jun 2026 15:25:52 -0400 Subject: [PATCH 37/69] fix: couple query bugs --- internal/services/controllers/olap/controller.go | 8 +------- pkg/repository/olap.go | 4 ++-- pkg/repository/sqlcv1/olap.sql | 8 ++++---- pkg/repository/sqlcv1/olap.sql.go | 8 ++++---- pkg/repository/sqlcv1/tasks.sql | 4 +++- pkg/repository/sqlcv1/tasks.sql.go | 4 +++- 6 files changed, 17 insertions(+), 19 deletions(-) diff --git a/internal/services/controllers/olap/controller.go b/internal/services/controllers/olap/controller.go index a071c57f2b..6c8d5aa733 100644 --- a/internal/services/controllers/olap/controller.go +++ b/internal/services/controllers/olap/controller.go @@ -728,18 +728,12 @@ func (tc *OLAPControllerImpl) handleCreateEventTriggers(ctx context.Context, ten for _, msg := range msgs { for _, payload := range msg.Payloads { if payload.MaybeRunId != nil && payload.MaybeRunInsertedAt != nil { - var filterId uuid.UUID - - if payload.FilterId != nil { - filterId = *payload.FilterId - } - bulkCreateTriggersParams = append(bulkCreateTriggersParams, v1.EventTriggersFromExternalId{ RunID: *payload.MaybeRunId, RunInsertedAt: sqlchelpers.TimestamptzFromTime(*payload.MaybeRunInsertedAt), EventExternalId: payload.EventExternalId, EventSeenAt: sqlchelpers.TimestamptzFromTime(payload.EventSeenAt), - FilterId: filterId, + FilterId: payload.FilterId, }) } diff --git a/pkg/repository/olap.go b/pkg/repository/olap.go index 5e24ed47a4..ccfffb278d 100644 --- a/pkg/repository/olap.go +++ b/pkg/repository/olap.go @@ -2526,7 +2526,7 @@ type EventTriggersFromExternalId struct { RunInsertedAt pgtype.Timestamptz `json:"run_inserted_at"` EventExternalId uuid.UUID `json:"event_external_id"` EventSeenAt pgtype.Timestamptz `json:"event_seen_at"` - FilterId uuid.UUID `json:"filter_id"` + FilterId *uuid.UUID `json:"filter_id"` } func (r *OLAPRepositoryImpl) BulkCreateEventsAndTriggers(ctx context.Context, events sqlcv1.BulkCreateEventsOLAPParams, triggers []EventTriggersFromExternalId) error { @@ -2582,7 +2582,7 @@ func (r *OLAPRepositoryImpl) BulkCreateEventsAndTriggers(ctx context.Context, ev RunInsertedAt: trigger.RunInsertedAt, EventID: eventId, EventSeenAt: trigger.EventSeenAt, - FilterID: &trigger.FilterId, + FilterID: trigger.FilterId, }) } diff --git a/pkg/repository/sqlcv1/olap.sql b/pkg/repository/sqlcv1/olap.sql index 69f8a968d1..028876cdc6 100644 --- a/pkg/repository/sqlcv1/olap.sql +++ b/pkg/repository/sqlcv1/olap.sql @@ -1860,8 +1860,8 @@ WHERE ) AND ( sqlc.narg('eventIds')::UUID[] IS NULL OR - EXISTS ( - SELECT 1 + (e.id, e.seen_at) IN ( + SELECT event_id, event_seen_at FROM v1_event_lookup_table_olap elt WHERE elt.tenant_id = @tenantId::UUID AND elt.external_id = ANY(sqlc.narg('eventIds')::UUID[]) ) @@ -1922,8 +1922,8 @@ WITH included_events AS ( ) AND ( sqlc.narg('eventIds')::UUID[] IS NULL OR - EXISTS ( - SELECT 1 + (e.id, e.seen_at) IN ( + SELECT event_id, event_seen_at FROM v1_event_lookup_table_olap elt WHERE elt.tenant_id = @tenantId::UUID AND elt.external_id = ANY(sqlc.narg('eventIds')::UUID[]) ) diff --git a/pkg/repository/sqlcv1/olap.sql.go b/pkg/repository/sqlcv1/olap.sql.go index 9f33f8afc3..3fc6bfe6ab 100644 --- a/pkg/repository/sqlcv1/olap.sql.go +++ b/pkg/repository/sqlcv1/olap.sql.go @@ -243,8 +243,8 @@ WITH included_events AS ( ) AND ( $6::UUID[] IS NULL OR - EXISTS ( - SELECT 1 + (e.id, e.seen_at) IN ( + SELECT event_id, event_seen_at FROM v1_event_lookup_table_olap elt WHERE elt.tenant_id = $1::UUID AND elt.external_id = ANY($6::UUID[]) ) @@ -1334,8 +1334,8 @@ WHERE ) AND ( $6::UUID[] IS NULL OR - EXISTS ( - SELECT 1 + (e.id, e.seen_at) IN ( + SELECT event_id, event_seen_at FROM v1_event_lookup_table_olap elt WHERE elt.tenant_id = $1::UUID AND elt.external_id = ANY($6::UUID[]) ) diff --git a/pkg/repository/sqlcv1/tasks.sql b/pkg/repository/sqlcv1/tasks.sql index dbc4904068..3b4775b0c6 100644 --- a/pkg/repository/sqlcv1/tasks.sql +++ b/pkg/repository/sqlcv1/tasks.sql @@ -1364,7 +1364,9 @@ SELECT run_external_id, event_id, event_seen_at, - filter_id + CASE WHEN filter_id = '00000000-0000-0000-0000-000000000000'::uuid THEN NULL + ELSE filter_id + END AS filter_id FROM input RETURNING diff --git a/pkg/repository/sqlcv1/tasks.sql.go b/pkg/repository/sqlcv1/tasks.sql.go index a31548eba7..487f832c84 100644 --- a/pkg/repository/sqlcv1/tasks.sql.go +++ b/pkg/repository/sqlcv1/tasks.sql.go @@ -187,7 +187,9 @@ SELECT run_external_id, event_id, event_seen_at, - filter_id + CASE WHEN filter_id = '00000000-0000-0000-0000-000000000000'::uuid THEN NULL + ELSE filter_id + END AS filter_id FROM input RETURNING From 7bcaaceef0297777b523c2ef78dce6752522532d Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Tue, 2 Jun 2026 16:02:33 -0400 Subject: [PATCH 38/69] fix: couple more event changes --- api/v1/server/oas/transformers/v1/events.go | 9 +++++-- pkg/repository/olap.go | 26 +++++++++++++++------ 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/api/v1/server/oas/transformers/v1/events.go b/api/v1/server/oas/transformers/v1/events.go index e2255219d9..2146f1dc2f 100644 --- a/api/v1/server/oas/transformers/v1/events.go +++ b/api/v1/server/oas/transformers/v1/events.go @@ -60,7 +60,7 @@ func ToV1Event(event *v1.EventWithPayload) gen.V1Event { triggeredRuns = []gen.V1EventTriggeredRun{} } - return gen.V1Event{ + transformedEvent := gen.V1Event{ AdditionalMetadata: &additionalMetadata, Key: event.EventKey, Metadata: gen.APIResourceMeta{ @@ -77,10 +77,15 @@ func ToV1Event(event *v1.EventWithPayload) gen.V1Event { }, Payload: &payload, SeenAt: &event.EventSeenAt.Time, - Scope: &event.EventScope, TriggeredRuns: &triggeredRuns, TriggeringWebhookName: event.TriggeringWebhookName, } + + if event.EventScope != "" { + transformedEvent.Scope = &event.EventScope + } + + return transformedEvent } func ToV1EventList(events []*v1.EventWithPayload, limit, offset, total int64) gen.V1EventList { diff --git a/pkg/repository/olap.go b/pkg/repository/olap.go index ccfffb278d..f66acfb2b5 100644 --- a/pkg/repository/olap.go +++ b/pkg/repository/olap.go @@ -2685,6 +2685,12 @@ func (r *OLAPRepositoryImpl) GetEventWithPayload(ctx context.Context, externalId failedCount = eventData.FailedCount } + var eventScope *string + + if event.Scope.Valid && event.Scope.String != "" { + eventScope = &event.Scope.String + } + return &EventWithPayload{ ListEventsRow: &ListEventsRow{ TenantID: event.TenantID, @@ -2694,7 +2700,7 @@ func (r *OLAPRepositoryImpl) GetEventWithPayload(ctx context.Context, externalId EventKey: event.Key, EventPayload: payload, EventAdditionalMetadata: event.AdditionalMetadata, - EventScope: event.Scope.String, + EventScope: eventScope, QueuedCount: queuedCount, RunningCount: runningCount, CompletedCount: completedCount, @@ -2715,7 +2721,7 @@ type ListEventsRow struct { EventKey string `json:"event_key"` EventPayload []byte `json:"event_payload"` EventAdditionalMetadata []byte `json:"event_additional_metadata"` - EventScope string `json:"event_scope"` + EventScope *string `json:"event_scope,omitempty,omitzero"` QueuedCount int64 `json:"queued_count"` RunningCount int64 `json:"running_count"` CompletedCount int64 `json:"completed_count"` @@ -2768,7 +2774,7 @@ func (r *OLAPRepositoryImpl) ListEvents(ctx context.Context, opts sqlcv1.ListEve eventExternalIds := make([]uuid.UUID, len(events)) readPayloadOpts := make([]ReadOLAPPayloadOpts, len(events)) - minSeenAt := sqlchelpers.TimestamptzFromTime(time.Now()) + minSeenAt := time.Now().UTC() for i, event := range events { eventExternalIds[i] = event.ExternalID @@ -2777,8 +2783,8 @@ func (r *OLAPRepositoryImpl) ListEvents(ctx context.Context, opts sqlcv1.ListEve InsertedAt: event.SeenAt, } - if event.SeenAt.Time.Before(minSeenAt.Time) { - minSeenAt = event.SeenAt + if event.SeenAt.Time.Before(minSeenAt) { + minSeenAt = event.SeenAt.Time } } @@ -2786,7 +2792,7 @@ func (r *OLAPRepositoryImpl) ListEvents(ctx context.Context, opts sqlcv1.ListEve ctx, opts.Tenantid, eventExternalIds, - minSeenAt, + sqlchelpers.TimestamptzFromTime(minSeenAt), ) if err != nil { @@ -2829,6 +2835,12 @@ func (r *OLAPRepositoryImpl) ListEvents(ctx context.Context, opts sqlcv1.ListEve failedCount = data.FailedCount } + var eventScope *string + + if event.Scope.Valid && event.Scope.String != "" { + eventScope = &event.Scope.String + } + result = append(result, &EventWithPayload{ ListEventsRow: &ListEventsRow{ TenantID: event.TenantID, @@ -2838,7 +2850,7 @@ func (r *OLAPRepositoryImpl) ListEvents(ctx context.Context, opts sqlcv1.ListEve EventKey: event.Key, EventPayload: payload, EventAdditionalMetadata: event.AdditionalMetadata, - EventScope: event.Scope.String, + EventScope: eventScope, QueuedCount: queuedCount, RunningCount: runningCount, CompletedCount: completedCount, From 4781050bbb1896e054a0801b771c50fb1db2ddec Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Tue, 2 Jun 2026 16:02:39 -0400 Subject: [PATCH 39/69] feat: idempotency test with events --- .../examples/idempotency/test_idempotency.py | 55 ++++++++++++++++++- sdks/python/examples/idempotency/worker.py | 3 +- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/sdks/python/examples/idempotency/test_idempotency.py b/sdks/python/examples/idempotency/test_idempotency.py index 537746409b..07b3e789b0 100644 --- a/sdks/python/examples/idempotency/test_idempotency.py +++ b/sdks/python/examples/idempotency/test_idempotency.py @@ -1,6 +1,6 @@ import pytest -from examples.idempotency.worker import idempotent_task, IdempotencyInput +from examples.idempotency.worker import idempotent_task, IdempotencyInput, EVENT_KEY from hatchet_sdk import Hatchet, IdempotencyCollisionError from hatchet_sdk.clients.rest.models.v1_task_summary_list import V1TaskSummaryList @@ -10,7 +10,9 @@ @pytest.mark.asyncio(loop_scope="session") -async def test_idempotency_keys_prevent_duplicate_runs(hatchet: Hatchet) -> None: +async def test_idempotency_keys_prevent_duplicate_runs_direct_trigger( + hatchet: Hatchet, +) -> None: test_run_id = str(uuid4()) ref1 = await idempotent_task.aio_run( input=IdempotencyInput(id=test_run_id), @@ -42,3 +44,52 @@ async def test_idempotency_keys_prevent_duplicate_runs(hatchet: Hatchet) -> None assert runs is not None assert len(runs.rows) == 1 assert runs.rows[0].metadata.id == ref1.workflow_run_id + + +@pytest.mark.asyncio(loop_scope="session") +async def test_idempotency_keys_prevent_duplicate_runs_event_trigger( + hatchet: Hatchet, +) -> None: + test_run_id = str(uuid4()) + e1 = await hatchet.event.aio_push( + event_key=EVENT_KEY, + payload={"id": test_run_id}, + additional_metadata={"test_run_id": test_run_id}, + ) + e2 = await hatchet.event.aio_push( + event_key=EVENT_KEY, + payload={"id": test_run_id}, + additional_metadata={"test_run_id": test_run_id}, + ) + + runs: V1TaskSummaryList | None = None + + for _ in range(15): + runs = await hatchet.runs.aio_list( + since=datetime.now(timezone.utc) - timedelta(minutes=5), + additional_metadata={"test_run_id": test_run_id}, + ) + + if len(runs.rows) == 0: + await asyncio.sleep(1) + continue + + break + + assert runs is not None + assert len(runs.rows) == 1 + + await asyncio.sleep(3) + + details = await hatchet.event.aio_list( + event_ids=[e1.event_id, e2.event_id], + ) + + assert len(details.rows) == 2 + + all_triggered_runs = [ + *(details.rows[0].triggered_runs or []), + *(details.rows[1].triggered_runs or []), + ] + + assert len(all_triggered_runs) == 1 diff --git a/sdks/python/examples/idempotency/worker.py b/sdks/python/examples/idempotency/worker.py index 54e69e5a6d..f1bda619de 100644 --- a/sdks/python/examples/idempotency/worker.py +++ b/sdks/python/examples/idempotency/worker.py @@ -8,11 +8,12 @@ class IdempotencyInput(BaseModel): id: str +EVENT_KEY = "idempotency:example" @hatchet.task( idempotency=IdempotencyConfig(key_expression="input.id", ttl=timedelta(minutes=1)), input_validator=IdempotencyInput, - on_events=["idempotency:example"], + on_events=[EVENT_KEY], ) async def idempotent_task(input: IdempotencyInput, ctx: Context) -> dict[str, str]: return {"result": f"Hello, world from task {input.id}"} From 3228fc74e3b578a4a163f4e58f56aceeb449deb3 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Tue, 2 Jun 2026 16:03:48 -0400 Subject: [PATCH 40/69] fix: type issue --- api/v1/server/oas/transformers/v1/events.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/api/v1/server/oas/transformers/v1/events.go b/api/v1/server/oas/transformers/v1/events.go index 2146f1dc2f..192e800e25 100644 --- a/api/v1/server/oas/transformers/v1/events.go +++ b/api/v1/server/oas/transformers/v1/events.go @@ -79,10 +79,7 @@ func ToV1Event(event *v1.EventWithPayload) gen.V1Event { SeenAt: &event.EventSeenAt.Time, TriggeredRuns: &triggeredRuns, TriggeringWebhookName: event.TriggeringWebhookName, - } - - if event.EventScope != "" { - transformedEvent.Scope = &event.EventScope + Scope: event.EventScope, } return transformedEvent From 8cc85004d545ddee638f4ff683590c23221edbc5 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Tue, 2 Jun 2026 16:05:31 -0400 Subject: [PATCH 41/69] fix: tests, lint --- pkg/repository/olap.go | 7 +------ sdks/python/examples/idempotency/test_idempotency.py | 1 + sdks/python/examples/idempotency/worker.py | 2 ++ 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/pkg/repository/olap.go b/pkg/repository/olap.go index f66acfb2b5..d1d9be43d4 100644 --- a/pkg/repository/olap.go +++ b/pkg/repository/olap.go @@ -2774,7 +2774,6 @@ func (r *OLAPRepositoryImpl) ListEvents(ctx context.Context, opts sqlcv1.ListEve eventExternalIds := make([]uuid.UUID, len(events)) readPayloadOpts := make([]ReadOLAPPayloadOpts, len(events)) - minSeenAt := time.Now().UTC() for i, event := range events { eventExternalIds[i] = event.ExternalID @@ -2782,17 +2781,13 @@ func (r *OLAPRepositoryImpl) ListEvents(ctx context.Context, opts sqlcv1.ListEve ExternalId: event.ExternalID, InsertedAt: event.SeenAt, } - - if event.SeenAt.Time.Before(minSeenAt) { - minSeenAt = event.SeenAt.Time - } } eventExternalIdToData, err := r.PopulateEventData( ctx, opts.Tenantid, eventExternalIds, - sqlchelpers.TimestamptzFromTime(minSeenAt), + opts.Since, ) if err != nil { diff --git a/sdks/python/examples/idempotency/test_idempotency.py b/sdks/python/examples/idempotency/test_idempotency.py index 07b3e789b0..fa7a6856dc 100644 --- a/sdks/python/examples/idempotency/test_idempotency.py +++ b/sdks/python/examples/idempotency/test_idempotency.py @@ -85,6 +85,7 @@ async def test_idempotency_keys_prevent_duplicate_runs_event_trigger( event_ids=[e1.event_id, e2.event_id], ) + assert details.rows assert len(details.rows) == 2 all_triggered_runs = [ diff --git a/sdks/python/examples/idempotency/worker.py b/sdks/python/examples/idempotency/worker.py index f1bda619de..63dbd2f9f1 100644 --- a/sdks/python/examples/idempotency/worker.py +++ b/sdks/python/examples/idempotency/worker.py @@ -8,8 +8,10 @@ class IdempotencyInput(BaseModel): id: str + EVENT_KEY = "idempotency:example" + @hatchet.task( idempotency=IdempotencyConfig(key_expression="input.id", ttl=timedelta(minutes=1)), input_validator=IdempotencyInput, From ed36fc5d0b925ba53ef7a097c3a0f474fca67317 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Tue, 2 Jun 2026 16:07:48 -0400 Subject: [PATCH 42/69] fix: test cleanup --- .../examples/idempotency/test_idempotency.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/sdks/python/examples/idempotency/test_idempotency.py b/sdks/python/examples/idempotency/test_idempotency.py index fa7a6856dc..7c5b6aa812 100644 --- a/sdks/python/examples/idempotency/test_idempotency.py +++ b/sdks/python/examples/idempotency/test_idempotency.py @@ -2,7 +2,7 @@ from examples.idempotency.worker import idempotent_task, IdempotencyInput, EVENT_KEY -from hatchet_sdk import Hatchet, IdempotencyCollisionError +from hatchet_sdk import Hatchet, IdempotencyCollisionError, RunStatus from hatchet_sdk.clients.rest.models.v1_task_summary_list import V1TaskSummaryList from uuid import uuid4 from datetime import timedelta, datetime, timezone @@ -79,8 +79,6 @@ async def test_idempotency_keys_prevent_duplicate_runs_event_trigger( assert runs is not None assert len(runs.rows) == 1 - await asyncio.sleep(3) - details = await hatchet.event.aio_list( event_ids=[e1.event_id, e2.event_id], ) @@ -94,3 +92,14 @@ async def test_idempotency_keys_prevent_duplicate_runs_event_trigger( ] assert len(all_triggered_runs) == 1 + + for _ in range(15): + run_details = await hatchet.runs.aio_get_details( + all_triggered_runs[0].workflow_run_id + ) + + if run_details.status in [RunStatus.QUEUED, RunStatus.RUNNING]: + await asyncio.sleep(1) + continue + + assert run_details.status == RunStatus.COMPLETED From 98823da414c505c4cf971a5bb819df6d42713e6b Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Tue, 2 Jun 2026 16:25:11 -0400 Subject: [PATCH 43/69] fix: handle deduplication in the query --- examples/python/idempotency/worker.py | 2 +- examples/python/worker.py | 3 +- pkg/repository/sqlcv1/idempotency-keys.sql | 15 ++++--- pkg/repository/sqlcv1/idempotency-keys.sql.go | 15 ++++--- .../examples/idempotency/test_idempotency.py | 43 ++++++++++++++++++- sdks/python/examples/idempotency/worker.py | 11 +++++ sdks/python/examples/worker.py | 3 +- 7 files changed, 76 insertions(+), 16 deletions(-) diff --git a/examples/python/idempotency/worker.py b/examples/python/idempotency/worker.py index 54e69e5a6d..cd6e9903f5 100644 --- a/examples/python/idempotency/worker.py +++ b/examples/python/idempotency/worker.py @@ -21,7 +21,7 @@ async def idempotent_task(input: IdempotencyInput, ctx: Context) -> dict[str, st def main() -> None: worker = hatchet.worker( "test-worker", - workflows=[idempotent_task], + workflows=[idempotent_task, idempotent_task], ) worker.start() diff --git a/examples/python/worker.py b/examples/python/worker.py index 3877df3d46..9a666899d6 100644 --- a/examples/python/worker.py +++ b/examples/python/worker.py @@ -101,7 +101,7 @@ durable_parent_child_key_bug, child_child_key_bug, ) -from examples.idempotency.worker import idempotent_task +from examples.idempotency.worker import idempotent_task, idempotent_task_short_window from hatchet_sdk import Hatchet hatchet = Hatchet() @@ -197,6 +197,7 @@ def main() -> None: durable_child_key_dedup_replay, durable_spawn_many_dags, idempotent_task, + idempotent_task_short_window, ], lifespan=lifespan, ) diff --git a/pkg/repository/sqlcv1/idempotency-keys.sql b/pkg/repository/sqlcv1/idempotency-keys.sql index c9a9f6f839..a0cb23b88f 100644 --- a/pkg/repository/sqlcv1/idempotency-keys.sql +++ b/pkg/repository/sqlcv1/idempotency-keys.sql @@ -11,6 +11,13 @@ WITH inputs AS ( UNNEST(@keys::TEXT[]) AS key, UNNEST(@expiresAts::TIMESTAMPTZ[]) AS expires_at, UNNEST(@claimedByExternalIds::UUID[]) AS claimed_by_external_id +), inputs_with_rn AS ( + SELECT *, ROW_NUMBER() OVER (PARTITION BY key ORDER BY expires_at DESC) AS rn + FROM inputs +), deduplicated_potential_claims AS ( + SELECT * + FROM inputs_with_rn + WHERE rn = 1 ), locked_existing_keys AS ( SELECT * FROM v1_idempotency_key @@ -18,13 +25,9 @@ WITH inputs AS ( tenant_id = @tenantId::UUID AND key IN ( SELECT key - FROM inputs + FROM deduplicated_potential_claims ) FOR UPDATE SKIP LOCKED -), already_claimed_keys AS ( - SELECT * - FROM locked_existing_keys - WHERE expires_at > NOW() ), claimable_keys AS ( SELECT * FROM locked_existing_keys @@ -32,7 +35,7 @@ WITH inputs AS ( ), claims AS ( INSERT INTO v1_idempotency_key (key, expires_at, tenant_id, claimed_by_external_id) SELECT key, expires_at, @tenantId::UUID, claimed_by_external_id - FROM inputs + FROM deduplicated_potential_claims ON CONFLICT (tenant_id, key) DO UPDATE SET expires_at = CASE diff --git a/pkg/repository/sqlcv1/idempotency-keys.sql.go b/pkg/repository/sqlcv1/idempotency-keys.sql.go index 6505bd0da5..df5329d16c 100644 --- a/pkg/repository/sqlcv1/idempotency-keys.sql.go +++ b/pkg/repository/sqlcv1/idempotency-keys.sql.go @@ -18,6 +18,13 @@ WITH inputs AS ( UNNEST($1::TEXT[]) AS key, UNNEST($2::TIMESTAMPTZ[]) AS expires_at, UNNEST($3::UUID[]) AS claimed_by_external_id +), inputs_with_rn AS ( + SELECT key, expires_at, claimed_by_external_id, ROW_NUMBER() OVER (PARTITION BY key ORDER BY expires_at DESC) AS rn + FROM inputs +), deduplicated_potential_claims AS ( + SELECT key, expires_at, claimed_by_external_id, rn + FROM inputs_with_rn + WHERE rn = 1 ), locked_existing_keys AS ( SELECT tenant_id, key, expires_at, claimed_by_external_id, inserted_at, updated_at FROM v1_idempotency_key @@ -25,13 +32,9 @@ WITH inputs AS ( tenant_id = $4::UUID AND key IN ( SELECT key - FROM inputs + FROM deduplicated_potential_claims ) FOR UPDATE SKIP LOCKED -), already_claimed_keys AS ( - SELECT tenant_id, key, expires_at, claimed_by_external_id, inserted_at, updated_at - FROM locked_existing_keys - WHERE expires_at > NOW() ), claimable_keys AS ( SELECT tenant_id, key, expires_at, claimed_by_external_id, inserted_at, updated_at FROM locked_existing_keys @@ -39,7 +42,7 @@ WITH inputs AS ( ), claims AS ( INSERT INTO v1_idempotency_key (key, expires_at, tenant_id, claimed_by_external_id) SELECT key, expires_at, $4::UUID, claimed_by_external_id - FROM inputs + FROM deduplicated_potential_claims ON CONFLICT (tenant_id, key) DO UPDATE SET expires_at = CASE diff --git a/sdks/python/examples/idempotency/test_idempotency.py b/sdks/python/examples/idempotency/test_idempotency.py index 7c5b6aa812..7309fca995 100644 --- a/sdks/python/examples/idempotency/test_idempotency.py +++ b/sdks/python/examples/idempotency/test_idempotency.py @@ -1,6 +1,11 @@ import pytest -from examples.idempotency.worker import idempotent_task, IdempotencyInput, EVENT_KEY +from examples.idempotency.worker import ( + idempotent_task, + idempotent_task_short_window, + IdempotencyInput, + EVENT_KEY, +) from hatchet_sdk import Hatchet, IdempotencyCollisionError, RunStatus from hatchet_sdk.clients.rest.models.v1_task_summary_list import V1TaskSummaryList @@ -46,6 +51,42 @@ async def test_idempotency_keys_prevent_duplicate_runs_direct_trigger( assert runs.rows[0].metadata.id == ref1.workflow_run_id +@pytest.mark.asyncio(loop_scope="session") +async def test_idempotency_keys_prevent_duplicate_runs_direct_trigger_short_window( + hatchet: Hatchet, +) -> None: + test_run_id = str(uuid4()) + for i in range(4): + await idempotent_task_short_window.aio_run( + input=IdempotencyInput(id=test_run_id), + wait_for_result=False, + additional_metadata={"test_run_id": test_run_id}, + ) + + ## dynamic sleep, first task should run, second should not, third should, fourth should + await asyncio.sleep(i + 1.5) + + runs: V1TaskSummaryList | None = None + + for _ in range(15): + runs = await hatchet.runs.aio_list( + since=datetime.now(timezone.utc) - timedelta(minutes=5), + additional_metadata={"test_run_id": test_run_id}, + ) + + if len(runs.rows) != 0: + await asyncio.sleep(1) + continue + + break + else: + pytest.fail("Expected to find at least one run, but found none.") + + assert runs + assert runs.rows + assert len(runs.rows) == 3 + + @pytest.mark.asyncio(loop_scope="session") async def test_idempotency_keys_prevent_duplicate_runs_event_trigger( hatchet: Hatchet, diff --git a/sdks/python/examples/idempotency/worker.py b/sdks/python/examples/idempotency/worker.py index 63dbd2f9f1..6f5e8d1bcc 100644 --- a/sdks/python/examples/idempotency/worker.py +++ b/sdks/python/examples/idempotency/worker.py @@ -21,6 +21,17 @@ async def idempotent_task(input: IdempotencyInput, ctx: Context) -> dict[str, st return {"result": f"Hello, world from task {input.id}"} +@hatchet.task( + idempotency=IdempotencyConfig(key_expression="input.id", ttl=timedelta(seconds=2)), + input_validator=IdempotencyInput, + on_events=[EVENT_KEY], +) +async def idempotent_task_short_window( + input: IdempotencyInput, ctx: Context +) -> dict[str, str]: + return {"result": f"Hello, world from task {input.id}"} + + def main() -> None: worker = hatchet.worker( "test-worker", diff --git a/sdks/python/examples/worker.py b/sdks/python/examples/worker.py index 3877df3d46..9a666899d6 100644 --- a/sdks/python/examples/worker.py +++ b/sdks/python/examples/worker.py @@ -101,7 +101,7 @@ durable_parent_child_key_bug, child_child_key_bug, ) -from examples.idempotency.worker import idempotent_task +from examples.idempotency.worker import idempotent_task, idempotent_task_short_window from hatchet_sdk import Hatchet hatchet = Hatchet() @@ -197,6 +197,7 @@ def main() -> None: durable_child_key_dedup_replay, durable_spawn_many_dags, idempotent_task, + idempotent_task_short_window, ], lifespan=lifespan, ) From c36309816c06786d64ba157cd4e4301e163734da Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Tue, 2 Jun 2026 16:29:11 -0400 Subject: [PATCH 44/69] fix: test, gen --- examples/python/idempotency/worker.py | 18 +++++++++++-- .../examples/idempotency/test_idempotency.py | 26 +++++++++++++------ 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/examples/python/idempotency/worker.py b/examples/python/idempotency/worker.py index cd6e9903f5..6f5e8d1bcc 100644 --- a/examples/python/idempotency/worker.py +++ b/examples/python/idempotency/worker.py @@ -9,19 +9,33 @@ class IdempotencyInput(BaseModel): id: str +EVENT_KEY = "idempotency:example" + + @hatchet.task( idempotency=IdempotencyConfig(key_expression="input.id", ttl=timedelta(minutes=1)), input_validator=IdempotencyInput, - on_events=["idempotency:example"], + on_events=[EVENT_KEY], ) async def idempotent_task(input: IdempotencyInput, ctx: Context) -> dict[str, str]: return {"result": f"Hello, world from task {input.id}"} +@hatchet.task( + idempotency=IdempotencyConfig(key_expression="input.id", ttl=timedelta(seconds=2)), + input_validator=IdempotencyInput, + on_events=[EVENT_KEY], +) +async def idempotent_task_short_window( + input: IdempotencyInput, ctx: Context +) -> dict[str, str]: + return {"result": f"Hello, world from task {input.id}"} + + def main() -> None: worker = hatchet.worker( "test-worker", - workflows=[idempotent_task, idempotent_task], + workflows=[idempotent_task], ) worker.start() diff --git a/sdks/python/examples/idempotency/test_idempotency.py b/sdks/python/examples/idempotency/test_idempotency.py index 7309fca995..a795a09922 100644 --- a/sdks/python/examples/idempotency/test_idempotency.py +++ b/sdks/python/examples/idempotency/test_idempotency.py @@ -57,14 +57,25 @@ async def test_idempotency_keys_prevent_duplicate_runs_direct_trigger_short_wind ) -> None: test_run_id = str(uuid4()) for i in range(4): - await idempotent_task_short_window.aio_run( - input=IdempotencyInput(id=test_run_id), - wait_for_result=False, - additional_metadata={"test_run_id": test_run_id}, - ) + if i == 1: + with pytest.raises(IdempotencyCollisionError) as exc_info: + await idempotent_task_short_window.aio_run( + input=IdempotencyInput(id=test_run_id), + wait_for_result=False, + additional_metadata={"test_run_id": test_run_id}, + ) + + assert exc_info.value.existing_run_external_id is not None + else: + await idempotent_task_short_window.aio_run( + input=IdempotencyInput(id=test_run_id), + wait_for_result=False, + additional_metadata={"test_run_id": test_run_id}, + ) ## dynamic sleep, first task should run, second should not, third should, fourth should - await asyncio.sleep(i + 1.5) + if i != 3: + await asyncio.sleep(i + 1.5) runs: V1TaskSummaryList | None = None @@ -74,7 +85,7 @@ async def test_idempotency_keys_prevent_duplicate_runs_direct_trigger_short_wind additional_metadata={"test_run_id": test_run_id}, ) - if len(runs.rows) != 0: + if len(runs.rows) < 3: await asyncio.sleep(1) continue @@ -82,7 +93,6 @@ async def test_idempotency_keys_prevent_duplicate_runs_direct_trigger_short_wind else: pytest.fail("Expected to find at least one run, but found none.") - assert runs assert runs.rows assert len(runs.rows) == 3 From 31c474217d8e2d6c6f29aaa2c7060dbbebd4145a Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Wed, 3 Jun 2026 08:02:44 -0400 Subject: [PATCH 45/69] fix: conflict --- internal/services/ticker/schedule_workflow_v1.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/services/ticker/schedule_workflow_v1.go b/internal/services/ticker/schedule_workflow_v1.go index 3c40702cf9..48a58495c1 100644 --- a/internal/services/ticker/schedule_workflow_v1.go +++ b/internal/services/ticker/schedule_workflow_v1.go @@ -22,7 +22,8 @@ func (t *TickerImpl) RunScheduledWorkflowV1(ctx context.Context, tenantId uuid.U func RunScheduledWorkflow(ctx context.Context, l *zerolog.Logger, mq msgqueue.MessageQueue, repo v1.Repository, tenantId uuid.UUID, opts v1.RunScheduledWorkflowV1Opts) (*uuid.UUID, error) { expiresAt := sqlchelpers.TimestamptzFromTime(opts.TriggerAt.Add(30 * time.Second)) - claimed, err := t.repov1.Idempotency().ClaimKey(ctx, tenantId, opts.ID.String(), expiresAt, opts.ID.String()) + claimed, err := repo.Idempotency().ClaimKey(ctx, tenantId, opts.ID.String(), expiresAt, opts.ID) + if err != nil { return nil, fmt.Errorf("could not claim idempotency key for scheduled workflow: %w", err) } From 59f0fcff5d024b3e508e8f6d59f8934cea5e4069 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Wed, 3 Jun 2026 08:06:54 -0400 Subject: [PATCH 46/69] fix: add prefix to idempotency key claim, clean up a tiny bit --- internal/services/ticker/schedule_workflow_v1.go | 5 +---- pkg/repository/idempotency.go | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/internal/services/ticker/schedule_workflow_v1.go b/internal/services/ticker/schedule_workflow_v1.go index 48a58495c1..2cfda3d528 100644 --- a/internal/services/ticker/schedule_workflow_v1.go +++ b/internal/services/ticker/schedule_workflow_v1.go @@ -11,7 +11,6 @@ import ( "github.com/hatchet-dev/hatchet/internal/msgqueue" tasktypes "github.com/hatchet-dev/hatchet/internal/services/shared/tasktypes/v1" v1 "github.com/hatchet-dev/hatchet/pkg/repository" - "github.com/hatchet-dev/hatchet/pkg/repository/sqlchelpers" ) func (t *TickerImpl) RunScheduledWorkflowV1(ctx context.Context, tenantId uuid.UUID, opts v1.RunScheduledWorkflowV1Opts) error { @@ -20,9 +19,7 @@ func (t *TickerImpl) RunScheduledWorkflowV1(ctx context.Context, tenantId uuid.U } func RunScheduledWorkflow(ctx context.Context, l *zerolog.Logger, mq msgqueue.MessageQueue, repo v1.Repository, tenantId uuid.UUID, opts v1.RunScheduledWorkflowV1Opts) (*uuid.UUID, error) { - expiresAt := sqlchelpers.TimestamptzFromTime(opts.TriggerAt.Add(30 * time.Second)) - - claimed, err := repo.Idempotency().ClaimKey(ctx, tenantId, opts.ID.String(), expiresAt, opts.ID) + claimed, err := repo.Idempotency().ClaimKey(ctx, tenantId, fmt.Sprintf("hatchet_internal_%s", opts.ID.String()), opts.TriggerAt.Add(30*time.Second), opts.ID) if err != nil { return nil, fmt.Errorf("could not claim idempotency key for scheduled workflow: %w", err) diff --git a/pkg/repository/idempotency.go b/pkg/repository/idempotency.go index 40a8d181b0..b0045b295e 100644 --- a/pkg/repository/idempotency.go +++ b/pkg/repository/idempotency.go @@ -2,10 +2,13 @@ package repository import ( "context" + "fmt" + "time" "github.com/google/uuid" "github.com/jackc/pgx/v5/pgtype" + "github.com/hatchet-dev/hatchet/pkg/repository/sqlchelpers" "github.com/hatchet-dev/hatchet/pkg/repository/sqlcv1" ) @@ -20,7 +23,7 @@ type ClaimIdempotencyKeysOpt struct { type IdempotencyRepository interface { EvictExpiredIdempotencyKeys(context context.Context, tenantId uuid.UUID) error - ClaimKey(ctx context.Context, tenantId uuid.UUID, key string, expiresAt pgtype.Timestamptz, claimedByExternalId uuid.UUID) (bool, error) + ClaimKey(ctx context.Context, tenantId uuid.UUID, key string, expiresAt time.Time, claimedByExternalId uuid.UUID) (bool, error) } type idempotencyRepository struct { @@ -37,19 +40,26 @@ func (r *idempotencyRepository) EvictExpiredIdempotencyKeys(context context.Cont return r.queries.CleanUpExpiredIdempotencyKeys(context, r.pool, tenantId) } -func (r *idempotencyRepository) ClaimKey(ctx context.Context, tenantId uuid.UUID, key string, expiresAt pgtype.Timestamptz, claimedByExternalId uuid.UUID) (bool, error) { +func (r *idempotencyRepository) ClaimKey(ctx context.Context, tenantId uuid.UUID, key string, expiresAt time.Time, claimedByExternalId uuid.UUID) (bool, error) { results, err := r.queries.ClaimIdempotencyKeys(ctx, r.pool, sqlcv1.ClaimIdempotencyKeysParams{ Keys: []string{key}, - Expiresats: []pgtype.Timestamptz{expiresAt}, + Expiresats: []pgtype.Timestamptz{sqlchelpers.TimestamptzFromTime(expiresAt)}, Claimedbyexternalids: []uuid.UUID{claimedByExternalId}, Tenantid: tenantId, }) + if err != nil { return false, err } + if len(results) == 0 { return false, nil } + + if len(results) > 1 { + return false, fmt.Errorf("unexpectedly claimed more than one idempotency key, this should never happen") + } + return results[0].WasSuccessfullyClaimed, nil } From 1ddb00b1d1ec4d18edc370c6b74a50d3d57dcce0 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Wed, 3 Jun 2026 08:55:32 -0400 Subject: [PATCH 47/69] fix: migration name, add idempotency key to cel eval failure source enum --- ...260529180517_v1_0_115.sql => 20260529180517_v1_0_116.sql} | 2 ++ pkg/repository/sqlcv1/models.go | 5 +++-- sql/schema/v1-olap.sql | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) rename cmd/hatchet-migrate/migrate/migrations/{20260529180517_v1_0_115.sql => 20260529180517_v1_0_116.sql} (80%) diff --git a/cmd/hatchet-migrate/migrate/migrations/20260529180517_v1_0_115.sql b/cmd/hatchet-migrate/migrate/migrations/20260529180517_v1_0_116.sql similarity index 80% rename from cmd/hatchet-migrate/migrate/migrations/20260529180517_v1_0_115.sql rename to cmd/hatchet-migrate/migrate/migrations/20260529180517_v1_0_116.sql index 8d490abe5c..9ba10b0ec2 100644 --- a/cmd/hatchet-migrate/migrate/migrations/20260529180517_v1_0_115.sql +++ b/cmd/hatchet-migrate/migrate/migrations/20260529180517_v1_0_116.sql @@ -4,6 +4,8 @@ ALTER TABLE "WorkflowVersion" ADD COLUMN "idempotencyKeyExpression" TEXT, ADD COLUMN "idempotencyKeyTtlMs" BIGINT ; + +ALTER TYPE v1_cel_evaluation_failure_source ADD VALUE IF NOT EXISTS 'IDEMPOTENCY_KEY'; -- +goose StatementEnd -- +goose Down diff --git a/pkg/repository/sqlcv1/models.go b/pkg/repository/sqlcv1/models.go index 9b8614bda8..79f7e9e42f 100644 --- a/pkg/repository/sqlcv1/models.go +++ b/pkg/repository/sqlcv1/models.go @@ -905,8 +905,9 @@ func (ns NullTenantResourceLimitAlertType) Value() (driver.Value, error) { type V1CelEvaluationFailureSource string const ( - V1CelEvaluationFailureSourceFILTER V1CelEvaluationFailureSource = "FILTER" - V1CelEvaluationFailureSourceWEBHOOK V1CelEvaluationFailureSource = "WEBHOOK" + V1CelEvaluationFailureSourceFILTER V1CelEvaluationFailureSource = "FILTER" + V1CelEvaluationFailureSourceWEBHOOK V1CelEvaluationFailureSource = "WEBHOOK" + V1CelEvaluationFailureSourceIDEMPOTENCYKEY V1CelEvaluationFailureSource = "IDEMPOTENCY_KEY" ) func (e *V1CelEvaluationFailureSource) Scan(src interface{}) error { diff --git a/sql/schema/v1-olap.sql b/sql/schema/v1-olap.sql index 96786d1e1b..c59e14938c 100644 --- a/sql/schema/v1-olap.sql +++ b/sql/schema/v1-olap.sql @@ -512,7 +512,7 @@ CREATE TABLE v1_event_to_run_olap ( PRIMARY KEY (event_id, event_seen_at, run_id, run_inserted_at) ) PARTITION BY RANGE(event_seen_at); -CREATE TYPE v1_cel_evaluation_failure_source AS ENUM ('FILTER', 'WEBHOOK'); +CREATE TYPE v1_cel_evaluation_failure_source AS ENUM ('FILTER', 'WEBHOOK', 'IDEMPOTENCY_KEY'); CREATE TABLE v1_cel_evaluation_failures_olap ( id BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY, From 70ff89aabe6e37c2429e60c5e3082619a06bc5ea Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Wed, 3 Jun 2026 09:20:17 -0400 Subject: [PATCH 48/69] fix: pr comments --- internal/services/admin/admin.go | 34 ++++----- internal/services/admin/server_v1.go | 68 +++++++----------- internal/services/admin/v1/admin.go | 34 ++++----- internal/services/admin/v1/server.go | 45 ++++-------- .../controllers/task/trigger/trigger.go | 20 +++++- internal/services/dispatcher/v1/server.go | 6 ++ pkg/repository/durable_events.go | 12 ++-- pkg/repository/scheduler_optimistic.go | 2 +- pkg/repository/trigger.go | 69 +++++++++++-------- 9 files changed, 146 insertions(+), 144 deletions(-) diff --git a/internal/services/admin/admin.go b/internal/services/admin/admin.go index dca67f06aa..09a4e92acf 100644 --- a/internal/services/admin/admin.go +++ b/internal/services/admin/admin.go @@ -34,8 +34,9 @@ type AdminServiceImpl struct { localDispatcher *dispatcher.DispatcherImpl l *zerolog.Logger - tw *trigger.TriggerWriter - pubBuffer *msgqueue.MQPubBuffer + tw *trigger.TriggerWriter + pubBuffer *msgqueue.MQPubBuffer + grpcTriggersEnabled bool } type AdminServiceOpt func(*AdminServiceOpts) @@ -139,15 +140,15 @@ func NewAdminService(fs ...AdminServiceOpt) (AdminService, error) { return nil, fmt.Errorf("task queue v1 is required. use WithMessageQueueV1") } - var tw *trigger.TriggerWriter - var pubBuffer *msgqueue.MQPubBuffer + pubBuffer := msgqueue.NewMQPubBuffer(opts.mqv1) + slots := 0 if opts.grpcTriggersEnabled { - pubBuffer = msgqueue.NewMQPubBuffer(opts.mqv1) - - tw = trigger.NewTriggerWriter(opts.mqv1, opts.repov1, opts.l, pubBuffer, opts.grpcTriggerSlots) + slots = opts.grpcTriggerSlots } + tw := trigger.NewTriggerWriter(opts.mqv1, opts.repov1, opts.l, pubBuffer, slots) + var localScheduler *scheduler.Scheduler if opts.optimisticSchedulingEnabled && opts.localScheduler != nil { @@ -155,15 +156,16 @@ func NewAdminService(fs ...AdminServiceOpt) (AdminService, error) { } return &AdminServiceImpl{ - repov1: opts.repov1, - mqv1: opts.mqv1, - v: opts.v, - analytics: opts.analytics, - localScheduler: localScheduler, - localDispatcher: opts.localDispatcher, - l: opts.l, - tw: tw, - pubBuffer: pubBuffer, + repov1: opts.repov1, + mqv1: opts.mqv1, + v: opts.v, + analytics: opts.analytics, + localScheduler: localScheduler, + localDispatcher: opts.localDispatcher, + l: opts.l, + tw: tw, + pubBuffer: pubBuffer, + grpcTriggersEnabled: opts.grpcTriggersEnabled, }, nil } diff --git a/internal/services/admin/server_v1.go b/internal/services/admin/server_v1.go index ecc967014f..c44b7578b7 100644 --- a/internal/services/admin/server_v1.go +++ b/internal/services/admin/server_v1.go @@ -309,7 +309,7 @@ func (i *AdminServiceImpl) ingest(ctx context.Context, tenantId uuid.UUID, opts if schedulingErr == nil { return idempotencyKeyCollisions, nil } - } else if i.tw != nil { + } else if i.grpcTriggersEnabled { idempotencyKeyCollisions, triggerErr := i.tw.TriggerFromWorkflowNames(ctx, tenantId, optsToSend) // if we fail to trigger via gRPC, we fall back to normal ingestion @@ -324,64 +324,44 @@ func (i *AdminServiceImpl) ingest(ctx context.Context, tenantId uuid.UUID, opts return nil, fmt.Errorf("could not populate workflow idempotency presence: %w", err) } - optsWithIdempotencyKeys := make([]*v1.WorkflowNameTriggerOpts, 0, len(optsToSend)) - optsWithoutIdempotencyKeys := make([]*v1.WorkflowNameTriggerOpts, 0, len(optsToSend)) - + hasIdempotencyKeys := false for _, opt := range optsToSend { if opt.HasIdempotencyKey { - optsWithIdempotencyKeys = append(optsWithIdempotencyKeys, opt) - } else { - optsWithoutIdempotencyKeys = append(optsWithoutIdempotencyKeys, opt) + hasIdempotencyKeys = true + break } } - var allIdempotencyKeyCollisions []v1.IdempotencyCollision - - if len(optsWithIdempotencyKeys) > 0 { - tasks, dags, collisions, err := i.repov1.Triggers().TriggerFromWorkflowNames(ctx, tenantId, optsWithIdempotencyKeys) - + // if we have _any_ runs to trigger with idempotency keys, we trigger everything in the batch directly to maintain atomicity + if hasIdempotencyKeys { + idempotencyKeyCollisions, err := i.tw.TriggerFromWorkflowNames(ctx, tenantId, optsToSend) if err != nil { - return nil, fmt.Errorf("could not trigger workflows with idempotency keys: %w", err) + return nil, fmt.Errorf("could not trigger workflows: %w", err) } - - if i.tw != nil { - if signalErr := i.tw.SignalCreated(ctx, tenantId, tasks, dags); signalErr != nil { - i.l.Error().Ctx(ctx).Err(signalErr).Msg("failed to signal created tasks and DAGs") - } - } - - allIdempotencyKeyCollisions = append(allIdempotencyKeyCollisions, collisions...) + return idempotencyKeyCollisions, nil } - if len(optsWithoutIdempotencyKeys) > 0 { - verifyErr := i.repov1.Triggers().PreflightVerifyWorkflowNameOpts(ctx, tenantId, optsWithoutIdempotencyKeys) + verifyErr := i.repov1.Triggers().PreflightVerifyWorkflowNameOpts(ctx, tenantId, optsToSend) - if verifyErr != nil { - namesNotFound := &v1.ErrNamesNotFound{} - - if errors.As(verifyErr, &namesNotFound) { - return nil, status.Error( - codes.InvalidArgument, - verifyErr.Error(), - ) - } + if verifyErr != nil { + namesNotFound := &v1.ErrNamesNotFound{} - return nil, fmt.Errorf("could not verify workflow name opts: %w", verifyErr) + if errors.As(verifyErr, &namesNotFound) { + return nil, status.Error(codes.InvalidArgument, verifyErr.Error()) } - msg, err := tasktypes.TriggerTaskMessage( - tenantId, - optsWithoutIdempotencyKeys..., - ) + return nil, fmt.Errorf("could not verify workflow name opts: %w", verifyErr) + } - if err != nil { - return nil, fmt.Errorf("could not create event task: %w", err) - } + msg, err := tasktypes.TriggerTaskMessage(tenantId, optsToSend...) - if err = i.mqv1.SendMessage(ctx, msgqueue.TASK_PROCESSING_QUEUE, msg); err != nil { - return nil, fmt.Errorf("could not add event to task queue: %w", err) - } + if err != nil { + return nil, fmt.Errorf("could not create event task: %w", err) + } + + if err = i.mqv1.SendMessage(ctx, msgqueue.TASK_PROCESSING_QUEUE, msg); err != nil { + return nil, fmt.Errorf("could not add event to task queue: %w", err) } - return allIdempotencyKeyCollisions, nil + return nil, nil } diff --git a/internal/services/admin/v1/admin.go b/internal/services/admin/v1/admin.go index 3672b98bc9..02671a4a1f 100644 --- a/internal/services/admin/v1/admin.go +++ b/internal/services/admin/v1/admin.go @@ -33,8 +33,9 @@ type AdminServiceImpl struct { localDispatcher *dispatcher.DispatcherImpl l *zerolog.Logger - tw *trigger.TriggerWriter - pubBuffer *msgqueue.MQPubBuffer + tw *trigger.TriggerWriter + pubBuffer *msgqueue.MQPubBuffer + grpcTriggersEnabled bool } type AdminServiceOpt func(*AdminServiceOpts) @@ -140,15 +141,15 @@ func NewAdminService(fs ...AdminServiceOpt) (AdminService, error) { return nil, fmt.Errorf("task queue is required. use WithMessageQueue") } - var tw *trigger.TriggerWriter - var pubBuffer *msgqueue.MQPubBuffer + pubBuffer := msgqueue.NewMQPubBuffer(opts.mq) + slots := 0 if opts.grpcTriggersEnabled { - pubBuffer = msgqueue.NewMQPubBuffer(opts.mq) - - tw = trigger.NewTriggerWriter(opts.mq, opts.repo, opts.l, pubBuffer, opts.grpcTriggerSlots) + slots = opts.grpcTriggerSlots } + tw := trigger.NewTriggerWriter(opts.mq, opts.repo, opts.l, pubBuffer, slots) + var localScheduler *scheduler.Scheduler if opts.optimisticSchedulingEnabled && opts.localScheduler != nil { @@ -156,15 +157,16 @@ func NewAdminService(fs ...AdminServiceOpt) (AdminService, error) { } return &AdminServiceImpl{ - repo: opts.repo, - mq: opts.mq, - v: opts.v, - analytics: opts.analytics, - localScheduler: localScheduler, - localDispatcher: opts.localDispatcher, - l: opts.l, - tw: tw, - pubBuffer: pubBuffer, + repo: opts.repo, + mq: opts.mq, + v: opts.v, + analytics: opts.analytics, + localScheduler: localScheduler, + localDispatcher: opts.localDispatcher, + l: opts.l, + tw: tw, + pubBuffer: pubBuffer, + grpcTriggersEnabled: opts.grpcTriggersEnabled, }, nil } diff --git a/internal/services/admin/v1/server.go b/internal/services/admin/v1/server.go index 66b441a453..3382cf8a30 100644 --- a/internal/services/admin/v1/server.go +++ b/internal/services/admin/v1/server.go @@ -675,51 +675,34 @@ func (a *AdminServiceImpl) ingest(ctx context.Context, tenantId uuid.UUID, opts return nil, fmt.Errorf("could not populate workflow info: %w", err) } - // important: if there's an idempotency key, we can't fall back to the MQ as we'd lose - // any tx-safety we get if we did, so we call `TriggerFromWorkflowNames` directly for those, - // and only fall back to the MQ for opts without idempotency keys - optsWithIdempotencyKeys := make([]*v1.WorkflowNameTriggerOpts, 0, len(optsToSend)) - optsWithoutIdempotencyKeys := make([]*v1.WorkflowNameTriggerOpts, 0, len(optsToSend)) - + hasIdempotencyKeys := false for _, opt := range optsToSend { if opt.HasIdempotencyKey { - optsWithIdempotencyKeys = append(optsWithIdempotencyKeys, opt) - } else { - optsWithoutIdempotencyKeys = append(optsWithoutIdempotencyKeys, opt) + hasIdempotencyKeys = true + break } } - var allIdempotencyKeyCollisions []v1.IdempotencyCollision - - if len(optsWithIdempotencyKeys) > 0 { - tasks, dags, idempotencyKeyCollisions, err := a.repo.Triggers().TriggerFromWorkflowNames(ctx, tenantId, optsWithIdempotencyKeys) - + // if we have _any_ runs to trigger with idempotency keys, we trigger everything in the batch directly to maintain atomicity + if hasIdempotencyKeys { + idempotencyKeyCollisions, err := a.tw.TriggerFromWorkflowNames(ctx, tenantId, optsToSend) if err != nil { return nil, fmt.Errorf("could not trigger workflows: %w", err) } - - if a.tw != nil { - if signalErr := a.tw.SignalCreated(ctx, tenantId, tasks, dags); signalErr != nil { - a.l.Error().Ctx(ctx).Err(signalErr).Msg("failed to signal created tasks and DAGs") - } - } - - allIdempotencyKeyCollisions = append(allIdempotencyKeyCollisions, idempotencyKeyCollisions...) + return idempotencyKeyCollisions, nil } - if len(optsWithoutIdempotencyKeys) > 0 { - msg, err := tasktypes.TriggerTaskMessage(tenantId, optsWithoutIdempotencyKeys...) + msg, err := tasktypes.TriggerTaskMessage(tenantId, optsToSend...) - if err != nil { - return nil, fmt.Errorf("could not create trigger message: %w", err) - } + if err != nil { + return nil, fmt.Errorf("could not create trigger message: %w", err) + } - if err := a.mq.SendMessage(ctx, msgqueue.TASK_PROCESSING_QUEUE, msg); err != nil { - return nil, fmt.Errorf("could not send trigger message: %w", err) - } + if err := a.mq.SendMessage(ctx, msgqueue.TASK_PROCESSING_QUEUE, msg); err != nil { + return nil, fmt.Errorf("could not send trigger message: %w", err) } - return allIdempotencyKeyCollisions, nil + return nil, nil } func (a *AdminServiceImpl) PutWorkflow(ctx context.Context, req *contracts.CreateWorkflowVersionRequest) (*contracts.CreateWorkflowVersionResponse, error) { diff --git a/internal/services/controllers/task/trigger/trigger.go b/internal/services/controllers/task/trigger/trigger.go index 8210310528..81566c163d 100644 --- a/internal/services/controllers/task/trigger/trigger.go +++ b/internal/services/controllers/task/trigger/trigger.go @@ -125,7 +125,7 @@ func (tw *TriggerWriter) TriggerFromWorkflowNames(ctx context.Context, tenantId } } - tasks, dags, idempotencyKeyCollisions, err := tw.repo.Triggers().TriggerFromWorkflowNames(ctx, tenantId, opts) + tasks, dags, idempotencyKeyCollisions, celEvaluationFailures, err := tw.repo.Triggers().TriggerFromWorkflowNames(ctx, tenantId, opts) if err != nil { if errors.Is(err, v1.ErrResourceExhausted) { @@ -137,13 +137,19 @@ func (tw *TriggerWriter) TriggerFromWorkflowNames(ctx context.Context, tenantId return nil, fmt.Errorf("could not trigger workflows from names: %w", err) } - // signaling errors do not result in a failure, since we have already written the tasks to the database, but - // we log the error + // signaling errors do not result in a failure since we have already written the tasks to the database, + // but we log them. // FIXME: we need a mechanism to DLQ these failed signals if err := tw.signaler.SignalCreated(ctx, tenantId, tasks, dags); err != nil { tw.l.Error().Ctx(ctx).Err(err).Msg("failed to signal created tasks and DAGs in TriggerFromWorkflowNames") } + if len(celEvaluationFailures) > 0 { + if err := tw.signaler.SignalCELEvaluationFailures(ctx, tenantId, celEvaluationFailures); err != nil { + tw.l.Error().Ctx(ctx).Err(err).Msg("failed to signal CEL evaluation failures in TriggerFromWorkflowNames") + } + } + return idempotencyKeyCollisions, nil } @@ -154,3 +160,11 @@ func (tw *TriggerWriter) SignalCreated(ctx context.Context, tenantId uuid.UUID, return nil } + +func (tw *TriggerWriter) SignalCELEvaluationFailures(ctx context.Context, tenantId uuid.UUID, failures []v1.CELEvaluationFailure) error { + if err := tw.signaler.SignalCELEvaluationFailures(ctx, tenantId, failures); err != nil { + tw.l.Error().Err(err).Msg("failed to signal CEL evaluation failures in SignalCELEvaluationFailures") + } + + return nil +} diff --git a/internal/services/dispatcher/v1/server.go b/internal/services/dispatcher/v1/server.go index f660470fd2..a9b882e023 100644 --- a/internal/services/dispatcher/v1/server.go +++ b/internal/services/dispatcher/v1/server.go @@ -691,6 +691,12 @@ func (d *DispatcherServiceImpl) handleTriggerRuns( } } + if failures := ingestionResult.TriggerRunsResult.CELEvaluationFailures; len(failures) > 0 { + if sigErr := d.triggerWriter.SignalCELEvaluationFailures(ctx, invocation.tenantId, failures); sigErr != nil { + d.l.Error().Err(sigErr).Msg("failed to signal CEL evaluation failures for durable run trigger") + } + } + err = invocation.send(&contracts.DurableTaskResponse{ Message: &contracts.DurableTaskResponse_TriggerRunsAck{ TriggerRunsAck: ackResp, diff --git a/pkg/repository/durable_events.go b/pkg/repository/durable_events.go index e931dd7ab6..72466bc5e4 100644 --- a/pkg/repository/durable_events.go +++ b/pkg/repository/durable_events.go @@ -83,10 +83,11 @@ type IngestTriggerRunsEntry struct { } type IngestTriggerRunsResult struct { - Entries []*IngestTriggerRunsEntry - CreatedTasks []*V1TaskWithPayload - CreatedDAGs []*DAGWithData - InvocationCount int32 + Entries []*IngestTriggerRunsEntry + CreatedTasks []*V1TaskWithPayload + CreatedDAGs []*DAGWithData + InvocationCount int32 + CELEvaluationFailures []CELEvaluationFailure } type IngestWaitForResult struct { @@ -1242,7 +1243,7 @@ func (r *durableEventsRepository) IngestDurableTaskEvent(ctx context.Context, op } if len(newTriggerOpts) > 0 { - createdTasks, createdDags, _, triggerErr := r.triggerFromWorkflowNames(ctx, optTx, tenantId, newTriggerOpts) + createdTasks, createdDags, _, celFailures, triggerErr := r.triggerFromWorkflowNames(ctx, optTx, tenantId, newTriggerOpts) if triggerErr != nil { return nil, fmt.Errorf("failed to trigger workflows: %w", triggerErr) @@ -1250,6 +1251,7 @@ func (r *durableEventsRepository) IngestDurableTaskEvent(ctx context.Context, op triggerRunsResult.CreatedTasks = createdTasks triggerRunsResult.CreatedDAGs = createdDags + triggerRunsResult.CELEvaluationFailures = celFailures createMatchOpts := make([]CreateMatchOpts, 0, len(createdTasks)+len(createdDags)) diff --git a/pkg/repository/scheduler_optimistic.go b/pkg/repository/scheduler_optimistic.go index 69b00b5fe4..0d201e6e5c 100644 --- a/pkg/repository/scheduler_optimistic.go +++ b/pkg/repository/scheduler_optimistic.go @@ -79,7 +79,7 @@ func (r *optimisticSchedulingRepositoryImpl) TriggerFromNames(ctx context.Contex return nil, nil, nil, nil, fmt.Errorf("failed to prepare trigger from workflow names: %w", err) } - tasks, dags, idempotencyKeyCollisions, err := r.triggerWorkflows(ctx, tx, tenantId, triggerOpts, nil) + tasks, dags, idempotencyKeyCollisions, _, err := r.triggerWorkflows(ctx, tx, tenantId, triggerOpts, nil) if err != nil { return nil, nil, nil, nil, fmt.Errorf("failed to trigger workflows: %w", err) diff --git a/pkg/repository/trigger.go b/pkg/repository/trigger.go index 88f012db10..e6da3ff50d 100644 --- a/pkg/repository/trigger.go +++ b/pkg/repository/trigger.go @@ -137,7 +137,7 @@ type createDAGOpts struct { type TriggerRepository interface { TriggerFromEvents(ctx context.Context, tenantId uuid.UUID, opts []EventTriggerOpts) (*TriggerFromEventsResult, error) - TriggerFromWorkflowNames(ctx context.Context, tenantId uuid.UUID, opts []*WorkflowNameTriggerOpts) ([]*V1TaskWithPayload, []*DAGWithData, []IdempotencyCollision, error) + TriggerFromWorkflowNames(ctx context.Context, tenantId uuid.UUID, opts []*WorkflowNameTriggerOpts) ([]*V1TaskWithPayload, []*DAGWithData, []IdempotencyCollision, []CELEvaluationFailure, error) PopulateExternalIdsForWorkflow(ctx context.Context, tenantId uuid.UUID, opts []*WorkflowNameTriggerOpts) error @@ -312,7 +312,7 @@ func (r *sharedRepository) doTriggerFromEvents( return nil, fmt.Errorf("failed to prepare trigger from events: %w", err) } - tasks, dags, _, err := r.triggerWorkflows(ctx, tx, tenantId, triggerOpts, createCoreEventOpts) + tasks, dags, _, workflowCelFailures, err := r.triggerWorkflows(ctx, tx, tenantId, triggerOpts, createCoreEventOpts) if err != nil { return nil, fmt.Errorf("failed to trigger workflows: %w", err) @@ -324,7 +324,7 @@ func (r *sharedRepository) doTriggerFromEvents( Tasks: tasks, Dags: dags, EventExternalIdToRuns: eventExternalIdToRuns, - CELEvaluationFailures: celEvaluationFailures, + CELEvaluationFailures: append(celEvaluationFailures, workflowCelFailures...), }, nil } @@ -372,36 +372,36 @@ func getEventExternalIdToRuns(opts []EventTriggerOpts, externalIdToEventIdAndFil return eventExternalIdToRuns } -func (s *sharedRepository) triggerFromWorkflowNames(ctx context.Context, tx *OptimisticTx, tenantId uuid.UUID, opts []*WorkflowNameTriggerOpts) ([]*V1TaskWithPayload, []*DAGWithData, []IdempotencyCollision, error) { +func (s *sharedRepository) triggerFromWorkflowNames(ctx context.Context, tx *OptimisticTx, tenantId uuid.UUID, opts []*WorkflowNameTriggerOpts) ([]*V1TaskWithPayload, []*DAGWithData, []IdempotencyCollision, []CELEvaluationFailure, error) { triggerOpts, err := s.prepareTriggerFromWorkflowNames(ctx, tx.tx, tenantId, opts) if err != nil { - return nil, nil, nil, fmt.Errorf("failed to prepare trigger from workflow names: %w", err) + return nil, nil, nil, nil, fmt.Errorf("failed to prepare trigger from workflow names: %w", err) } return s.triggerWorkflows(ctx, tx, tenantId, triggerOpts, nil) } -func (r *TriggerRepositoryImpl) TriggerFromWorkflowNames(ctx context.Context, tenantId uuid.UUID, opts []*WorkflowNameTriggerOpts) ([]*V1TaskWithPayload, []*DAGWithData, []IdempotencyCollision, error) { +func (r *TriggerRepositoryImpl) TriggerFromWorkflowNames(ctx context.Context, tenantId uuid.UUID, opts []*WorkflowNameTriggerOpts) ([]*V1TaskWithPayload, []*DAGWithData, []IdempotencyCollision, []CELEvaluationFailure, error) { tx, err := r.PrepareOptimisticTx(ctx) if err != nil { - return nil, nil, nil, fmt.Errorf("failed to prepare tx: %w", err) + return nil, nil, nil, nil, fmt.Errorf("failed to prepare tx: %w", err) } defer tx.Rollback() - tasks, dags, idempotencyKeyCollisions, err := r.triggerFromWorkflowNames(ctx, tx, tenantId, opts) + tasks, dags, idempotencyKeyCollisions, celEvaluationFailures, err := r.triggerFromWorkflowNames(ctx, tx, tenantId, opts) if err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, err } if err := tx.Commit(ctx); err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, err } - return tasks, dags, idempotencyKeyCollisions, nil + return tasks, dags, idempotencyKeyCollisions, celEvaluationFailures, nil } type ErrNamesNotFound struct { @@ -639,6 +639,8 @@ type createCoreUserEventOpts struct { params sqlcv1.BulkCreateEventsParams } +const internalIdempotencyKeyPrefix = "hatchet_internal_" + func (r *sharedRepository) evalIdempotencyKey(tuple triggerTuple) (string, error) { inputData := make(map[string]any) if len(tuple.input) > 0 { @@ -665,6 +667,10 @@ func (r *sharedRepository) evalIdempotencyKey(tuple triggerTuple) (string, error return "", fmt.Errorf("failed to evaluate idempotency key expression %q: %w", tuple.idempotency.Expression, err) } + if strings.HasPrefix(key, internalIdempotencyKeyPrefix) { + return "", fmt.Errorf("idempotency key %q uses reserved prefix %q", key, internalIdempotencyKeyPrefix) + } + return key, nil } @@ -674,7 +680,7 @@ func (r *sharedRepository) triggerWorkflows( tenantId uuid.UUID, triggerCandidateTuples []triggerTuple, coreEvents *createCoreUserEventOpts, -) ([]*V1TaskWithPayload, []*DAGWithData, []IdempotencyCollision, error) { +) ([]*V1TaskWithPayload, []*DAGWithData, []IdempotencyCollision, []CELEvaluationFailure, error) { var preflightTx sqlcv1.DBTX = r.pool if existingTx != nil { @@ -688,6 +694,8 @@ func (r *sharedRepository) triggerWorkflows( claimedByExternalIds := make([]uuid.UUID, 0, len(triggerCandidateTuples)) externalIdToTuple := make(map[uuid.UUID]triggerTuple) + var celEvaluationFailures []CELEvaluationFailure + for _, tuple := range triggerCandidateTuples { if tuple.idempotency == nil { tuples = append(tuples, tuple) @@ -695,7 +703,12 @@ func (r *sharedRepository) triggerWorkflows( externalIdToTuple[tuple.externalId] = tuple key, err := r.evalIdempotencyKey(tuple) if err != nil { - return nil, nil, nil, err + r.l.Error().Ctx(ctx).Err(err).Msg("failed to evaluate idempotency key, skipping tuple") + celEvaluationFailures = append(celEvaluationFailures, CELEvaluationFailure{ + Source: sqlcv1.V1CelEvaluationFailureSourceIDEMPOTENCYKEY, + ErrorMessage: err.Error(), + }) + continue } keys = append(keys, key) @@ -718,7 +731,7 @@ func (r *sharedRepository) triggerWorkflows( }) if err != nil { - return nil, nil, nil, fmt.Errorf("failed to claim idempotency keys: %w", err) + return nil, nil, nil, nil, fmt.Errorf("failed to claim idempotency keys: %w", err) } idempotencyKeyToLockHolder := make(map[string]uuid.UUID, len(claims)) @@ -768,7 +781,7 @@ func (r *sharedRepository) triggerWorkflows( workflowVersionToSteps, err := r.listStepsByWorkflowVersionIds(ctx, preflightTx, tenantId, workflowVersionIds) if err != nil { - return nil, nil, nil, fmt.Errorf("failed to get workflow versions for engine: %w", err) + return nil, nil, nil, nil, fmt.Errorf("failed to get workflow versions for engine: %w", err) } // group steps by workflow version ids @@ -803,7 +816,7 @@ func (r *sharedRepository) triggerWorkflows( preTask, postTask := r.m.Meter(ctx, sqlcv1.LimitResourceTASKRUN, tenantId, int32(countTasks)) // nolint: gosec if err := preTask(); err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, err } stepsToAdditionalMatches := make(map[uuid.UUID][]*sqlcv1.V1StepMatchCondition) @@ -815,7 +828,7 @@ func (r *sharedRepository) triggerWorkflows( }) if err != nil { - return nil, nil, nil, fmt.Errorf("failed to list step match conditions: %w", err) + return nil, nil, nil, nil, fmt.Errorf("failed to list step match conditions: %w", err) } for _, match := range additionalMatches { @@ -896,7 +909,7 @@ func (r *sharedRepository) triggerWorkflows( tx, commit, rollback, err = sqlchelpers.PrepareTx(ctx, r.pool, r.l) if err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, err } defer rollback() @@ -909,7 +922,7 @@ func (r *sharedRepository) triggerWorkflows( tuplesToSkip, err := r.registerChildWorkflows(ctx, tx, tenantId, tuples, stepsToExternalIds, workflowVersionToSteps) if err != nil { - return nil, nil, nil, fmt.Errorf("failed to register child workflows: %w", err) + return nil, nil, nil, nil, fmt.Errorf("failed to register child workflows: %w", err) } for i, tuple := range tuples { @@ -1055,7 +1068,7 @@ func (r *sharedRepository) triggerWorkflows( ) if err != nil { - return nil, nil, nil, fmt.Errorf("failed to create sleep condition: %w", err) + return nil, nil, nil, nil, fmt.Errorf("failed to create sleep condition: %w", err) } groupConditions = append(groupConditions, *c) @@ -1312,7 +1325,7 @@ func (r *sharedRepository) triggerWorkflows( dags, err := r.createDAGs(ctx, tx, tenantId, dagOpts) if err != nil { - return nil, nil, nil, fmt.Errorf("failed to create DAGs: %w", err) + return nil, nil, nil, nil, fmt.Errorf("failed to create DAGs: %w", err) } // populate taskOpts with inserted DAG data @@ -1337,7 +1350,7 @@ func (r *sharedRepository) triggerWorkflows( tasks, err := r.createTasks(ctx, tx, tenantId, createTaskOpts) if err != nil { - return nil, nil, nil, fmt.Errorf("failed to create tasks: %w", err) + return nil, nil, nil, nil, fmt.Errorf("failed to create tasks: %w", err) } for _, dag := range dags { @@ -1354,7 +1367,7 @@ func (r *sharedRepository) triggerWorkflows( err = r.createEventMatches(ctx, tx, tenantId, createMatchOpts) if err != nil { - return nil, nil, nil, fmt.Errorf("failed to create event matches: %w", err) + return nil, nil, nil, nil, fmt.Errorf("failed to create event matches: %w", err) } storePayloadOpts := make([]StorePayloadOpts, 0, len(tasks)) @@ -1387,7 +1400,7 @@ func (r *sharedRepository) triggerWorkflows( createdEvents, err := r.queries.BulkCreateEvents(ctx, tx, coreEvents.params) if err != nil { - return nil, nil, nil, fmt.Errorf("failed to create core events: %w", err) + return nil, nil, nil, nil, fmt.Errorf("failed to create core events: %w", err) } for _, createdEvent := range createdEvents { @@ -1463,7 +1476,7 @@ func (r *sharedRepository) triggerWorkflows( }) if err != nil { - return nil, nil, nil, fmt.Errorf("failed to create event to runs: %w", err) + return nil, nil, nil, nil, fmt.Errorf("failed to create event to runs: %w", err) } for _, e := range createdEvents { @@ -1487,13 +1500,13 @@ func (r *sharedRepository) triggerWorkflows( err = r.payloadStore.Store(ctx, tx, storePayloadOpts...) if err != nil { - return nil, nil, nil, fmt.Errorf("failed to store payloads: %w", err) + return nil, nil, nil, nil, fmt.Errorf("failed to store payloads: %w", err) } // commit if we started the transaction if existingTx == nil { if err := commit(ctx); err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, err } postTask() @@ -1502,7 +1515,7 @@ func (r *sharedRepository) triggerWorkflows( existingTx.AddPostCommit(postTask) } - return tasks, dags, idempotencyKeyCollisions, nil + return tasks, dags, idempotencyKeyCollisions, celEvaluationFailures, nil } type DAGWithData struct { From 5dcc91382a0e81f026d396a59af3e6a0b835d251 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Wed, 3 Jun 2026 09:34:43 -0400 Subject: [PATCH 49/69] chore: gen --- pkg/repository/durable_events.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/repository/durable_events.go b/pkg/repository/durable_events.go index 72466bc5e4..4161bf2cad 100644 --- a/pkg/repository/durable_events.go +++ b/pkg/repository/durable_events.go @@ -83,10 +83,10 @@ type IngestTriggerRunsEntry struct { } type IngestTriggerRunsResult struct { - Entries []*IngestTriggerRunsEntry - CreatedTasks []*V1TaskWithPayload - CreatedDAGs []*DAGWithData - InvocationCount int32 + Entries []*IngestTriggerRunsEntry + CreatedTasks []*V1TaskWithPayload + CreatedDAGs []*DAGWithData + InvocationCount int32 CELEvaluationFailures []CELEvaluationFailure } From 6667e58bbb607beb55c40019c8ec228d1d1421f5 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Wed, 3 Jun 2026 10:42:54 -0400 Subject: [PATCH 50/69] feat: snippets, initial doc work --- api/v1/server/oas/transformers/v1/events.go | 6 +- examples/python/idempotency/trigger.py | 31 ++++++++++- examples/python/idempotency/worker.py | 9 ++- frontend/docs/pages/v1/_meta.js | 1 + frontend/docs/pages/v1/concurrency.mdx | 2 +- frontend/docs/pages/v1/idempotency.mdx | 61 +++++++++++++++++++++ sdks/python/examples/idempotency/trigger.py | 32 ++++++++++- sdks/python/examples/idempotency/worker.py | 10 +++- 8 files changed, 137 insertions(+), 15 deletions(-) create mode 100644 frontend/docs/pages/v1/idempotency.mdx diff --git a/api/v1/server/oas/transformers/v1/events.go b/api/v1/server/oas/transformers/v1/events.go index 192e800e25..627f8cfdbf 100644 --- a/api/v1/server/oas/transformers/v1/events.go +++ b/api/v1/server/oas/transformers/v1/events.go @@ -60,7 +60,7 @@ func ToV1Event(event *v1.EventWithPayload) gen.V1Event { triggeredRuns = []gen.V1EventTriggeredRun{} } - transformedEvent := gen.V1Event{ + return gen.V1Event{ AdditionalMetadata: &additionalMetadata, Key: event.EventKey, Metadata: gen.APIResourceMeta{ @@ -77,12 +77,10 @@ func ToV1Event(event *v1.EventWithPayload) gen.V1Event { }, Payload: &payload, SeenAt: &event.EventSeenAt.Time, + Scope: event.EventScope, TriggeredRuns: &triggeredRuns, TriggeringWebhookName: event.TriggeringWebhookName, - Scope: event.EventScope, } - - return transformedEvent } func ToV1EventList(events []*v1.EventWithPayload, limit, offset, total int64) gen.V1EventList { diff --git a/examples/python/idempotency/trigger.py b/examples/python/idempotency/trigger.py index 9eadfb31b7..239fdb917f 100644 --- a/examples/python/idempotency/trigger.py +++ b/examples/python/idempotency/trigger.py @@ -1,4 +1,31 @@ +import asyncio + +from hatchet_sdk import IdempotencyCollisionError + from examples.idempotency.worker import idempotent_task, IdempotencyInput -idempotent_task.run(input=IdempotencyInput(id="123"), wait_for_result=False) -idempotent_task.run(input=IdempotencyInput(id="123"), wait_for_result=False) + +async def main() -> None: + # > trigger + ref_1 = await idempotent_task.aio_run( + input=IdempotencyInput(id="123"), + wait_for_result=False, + ) + + try: + ref_2 = await idempotent_task.aio_run( + input=IdempotencyInput(id="123"), + wait_for_result=False, + ) + run_id_2 = ref_2.workflow_run_id + except IdempotencyCollisionError as e: + print( + f"Run with external ID {e.existing_run_external_id} already exists for this idempotency key" + ) + run_id_2 = e.existing_run_external_id + + res_1 = await ref_1.aio_result() + res_2 = await idempotent_task.aio_get_result(run_id_2) + + assert res_1 == res_2 + assert ref_1.workflow_run_id == run_id_2 diff --git a/examples/python/idempotency/worker.py b/examples/python/idempotency/worker.py index 6f5e8d1bcc..f9f965aeb0 100644 --- a/examples/python/idempotency/worker.py +++ b/examples/python/idempotency/worker.py @@ -4,12 +4,13 @@ hatchet = Hatchet() +# > idempotency -class IdempotencyInput(BaseModel): - id: str +EVENT_KEY = "idempotency:example" -EVENT_KEY = "idempotency:example" +class IdempotencyInput(BaseModel): + id: str @hatchet.task( @@ -21,6 +22,8 @@ async def idempotent_task(input: IdempotencyInput, ctx: Context) -> dict[str, st return {"result": f"Hello, world from task {input.id}"} + + @hatchet.task( idempotency=IdempotencyConfig(key_expression="input.id", ttl=timedelta(seconds=2)), input_validator=IdempotencyInput, diff --git a/frontend/docs/pages/v1/_meta.js b/frontend/docs/pages/v1/_meta.js index 429226f98a..8ee3b31325 100644 --- a/frontend/docs/pages/v1/_meta.js +++ b/frontend/docs/pages/v1/_meta.js @@ -39,6 +39,7 @@ export default { concurrency: "Concurrency", "rate-limits": "Rate Limits", priority: "Priority", + idempotency: "Idempotency", "--durable-workflows-section": { title: "Durable Execution", type: "separator", diff --git a/frontend/docs/pages/v1/concurrency.mdx b/frontend/docs/pages/v1/concurrency.mdx index 91a745fe95..8467e52fdc 100644 --- a/frontend/docs/pages/v1/concurrency.mdx +++ b/frontend/docs/pages/v1/concurrency.mdx @@ -5,7 +5,7 @@ import { snippets } from "@/lib/generated/snippets"; # Concurrency Control in Hatchet Tasks -Hatchet provides powerful concurrency control features to help you manage the execution of your tasks. This is particularly useful when you have tasks that may be triggered frequently or have long-running steps, and you want to limit the number of concurrent executions to prevent overloading your system, ensure fairness, or avoid race conditions. Concurrency strategies can be added to both tasks and workflows +Hatchet provides powerful concurrency control features to help you manage the execution of your tasks. This is particularly useful when you have tasks that may be triggered frequently or have long-running steps, and you want to limit the number of concurrent executions to prevent overloading your system, ensure fairness, or avoid race conditions. Concurrency strategies can be added to both tasks and workflows. This page will discuss concurrency **keys** often. The key is the result of diff --git a/frontend/docs/pages/v1/idempotency.mdx b/frontend/docs/pages/v1/idempotency.mdx new file mode 100644 index 0000000000..cf2ba32520 --- /dev/null +++ b/frontend/docs/pages/v1/idempotency.mdx @@ -0,0 +1,61 @@ +import { Callout, Card, Cards, Steps, Tabs } from "nextra/components"; +import UniversalTabs from "@/components/UniversalTabs"; +import { Snippet } from "@/components/code"; +import { snippets } from "@/lib/generated/snippets"; + +# Idempotency + +If you need to prevent more than one run of a task from occurring within a given time window, for instance because of duplicate event sends from a webhook that can trigger duplicate runs in Hatchet, you can achieve this by adding **idempotency** configuration to your workflow or standalone task. + +## Define an Idempotency Key + +Configuring idempotency on a workflow or standalone task requires two parameters to be set: the **expression**, which will be used to create an idempotency key from the input and additional metadata of the run that's going to be triggered, and the **TTL**, which determines how long the key should live for. + + + + + + + {/* TODO: Add snippet */} + + + {/* TODO: Add snippet */} + + + {/* TODO: Add snippet */} + + + + +When idempotency is configured, only one run of a workflow will occur in the time window from when the first trigger comes in until the TTL expires. For instance, if you trigger a workflow at `00:00:00 UTC` (midnight) with a TTL of five minutes, and then you the same workflow is triggered again with the same inputs and metadata at `00:02:00 UTC`, `00:04:00 UTC`, and `00:06:00 UTC`, only the first one (at midnight) and the final one (at `00:06:00 UTC`) will run, and then after the second run occurs, the lock on the key will be held until `00:11:00 UTC` (five minutes after the final run was triggered). + +## Handling Collisions + +When a collision occurs, the engine will reject the workflow run, and, if the run was triggered from an SDK, then the SDK will raise an exception indicating that there was an idempotency collision. This exception will contain the id of the workflow run that already existed that had claimed the key already, so you can retrieve its output if you like. + + + + + + + {/* TODO: Add snippet */} + + + {/* TODO: Add snippet */} + + + {/* TODO: Add snippet */} + + + + +In other cases, such as triggering by events, the idempotency collision will be swallowed, and no additional runs will be created by the event will still be ingested correctly without an error being raised. + diff --git a/sdks/python/examples/idempotency/trigger.py b/sdks/python/examples/idempotency/trigger.py index 9eadfb31b7..aacf8416fd 100644 --- a/sdks/python/examples/idempotency/trigger.py +++ b/sdks/python/examples/idempotency/trigger.py @@ -1,4 +1,32 @@ +import asyncio + +from hatchet_sdk import IdempotencyCollisionError + from examples.idempotency.worker import idempotent_task, IdempotencyInput -idempotent_task.run(input=IdempotencyInput(id="123"), wait_for_result=False) -idempotent_task.run(input=IdempotencyInput(id="123"), wait_for_result=False) + +async def main() -> None: + # > trigger + ref_1 = await idempotent_task.aio_run( + input=IdempotencyInput(id="123"), + wait_for_result=False, + ) + + try: + ref_2 = await idempotent_task.aio_run( + input=IdempotencyInput(id="123"), + wait_for_result=False, + ) + run_id_2 = ref_2.workflow_run_id + except IdempotencyCollisionError as e: + print( + f"Run with external ID {e.existing_run_external_id} already exists for this idempotency key" + ) + run_id_2 = e.existing_run_external_id + + res_1 = await ref_1.aio_result() + res_2 = await idempotent_task.aio_get_result(run_id_2) + + assert res_1 == res_2 + assert ref_1.workflow_run_id == run_id_2 + # !! diff --git a/sdks/python/examples/idempotency/worker.py b/sdks/python/examples/idempotency/worker.py index 6f5e8d1bcc..ab647d85fa 100644 --- a/sdks/python/examples/idempotency/worker.py +++ b/sdks/python/examples/idempotency/worker.py @@ -4,12 +4,13 @@ hatchet = Hatchet() +# > idempotency -class IdempotencyInput(BaseModel): - id: str +EVENT_KEY = "idempotency:example" -EVENT_KEY = "idempotency:example" +class IdempotencyInput(BaseModel): + id: str @hatchet.task( @@ -21,6 +22,9 @@ async def idempotent_task(input: IdempotencyInput, ctx: Context) -> dict[str, st return {"result": f"Hello, world from task {input.id}"} +# !! + + @hatchet.task( idempotency=IdempotencyConfig(key_expression="input.id", ttl=timedelta(seconds=2)), input_validator=IdempotencyInput, From f01f0de29d9c1e30cb599414c8468e8d7a962daa Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Wed, 3 Jun 2026 11:15:11 -0400 Subject: [PATCH 51/69] chore: vibe code other sdks --- examples/go/idempotency/trigger/main.go | 64 ++++++ examples/go/idempotency/worker/main.go | 55 ++++++ examples/ruby/idempotency/trigger.rb | 20 ++ examples/ruby/idempotency/worker.rb | 21 ++ examples/typescript/idempotency/run.ts | 27 +++ examples/typescript/idempotency/workflow.ts | 17 ++ frontend/docs/pages/v1/idempotency.mdx | 19 +- pkg/client/admin.go | 26 +++ pkg/client/create/tasks.go | 12 ++ sdks/go/errors.go | 18 ++ sdks/go/examples/idempotency/trigger/main.go | 65 +++++++ sdks/go/examples/idempotency/worker/main.go | 57 ++++++ sdks/go/internal/declaration.go | 9 + sdks/go/workflow.go | 36 ++++ sdks/ruby/examples/idempotency/trigger.rb | 21 ++ sdks/ruby/examples/idempotency/worker.rb | 22 +++ sdks/ruby/src/.rubocop.yml | 3 - sdks/ruby/src/lib/hatchet-sdk.rb | 8 +- .../src/lib/hatchet/clients/grpc/admin.rb | 109 +++++++++++ .../lib/hatchet/contracts/v1/workflows_pb.rb | 4 +- sdks/ruby/src/lib/hatchet/exceptions.rb | 12 ++ sdks/ruby/src/lib/hatchet/workflow.rb | 13 ++ sdks/typescript/src/protoc/v1/workflows.ts | 183 ++++++++++++++++++ .../errors/idempotency-collision-error.ts | 12 ++ sdks/typescript/src/v1/client/admin.ts | 52 +++++ .../src/v1/client/worker/worker-internal.ts | 6 + sdks/typescript/src/v1/declaration.ts | 7 + .../src/v1/examples/idempotency/run.ts | 28 +++ .../src/v1/examples/idempotency/workflow.ts | 18 ++ sdks/typescript/src/v1/index.ts | 1 + sdks/typescript/src/v1/task.ts | 17 ++ 31 files changed, 945 insertions(+), 17 deletions(-) create mode 100644 examples/go/idempotency/trigger/main.go create mode 100644 examples/go/idempotency/worker/main.go create mode 100644 examples/ruby/idempotency/trigger.rb create mode 100644 examples/ruby/idempotency/worker.rb create mode 100644 examples/typescript/idempotency/run.ts create mode 100644 examples/typescript/idempotency/workflow.ts create mode 100644 sdks/go/examples/idempotency/trigger/main.go create mode 100644 sdks/go/examples/idempotency/worker/main.go create mode 100644 sdks/ruby/examples/idempotency/trigger.rb create mode 100644 sdks/ruby/examples/idempotency/worker.rb create mode 100644 sdks/typescript/src/util/errors/idempotency-collision-error.ts create mode 100644 sdks/typescript/src/v1/examples/idempotency/run.ts create mode 100644 sdks/typescript/src/v1/examples/idempotency/workflow.ts diff --git a/examples/go/idempotency/trigger/main.go b/examples/go/idempotency/trigger/main.go new file mode 100644 index 0000000000..9e83811206 --- /dev/null +++ b/examples/go/idempotency/trigger/main.go @@ -0,0 +1,64 @@ +package main + +import ( + "context" + "fmt" + "log" + "time" + + hatchet "github.com/hatchet-dev/hatchet/sdks/go" +) + +type IdempotencyInput struct { + ID string `json:"id"` +} + +type IdempotencyOutput struct { + Result string `json:"result"` +} + +func main() { + client, err := hatchet.NewClient() + if err != nil { + log.Fatalf("failed to create hatchet client: %v", err) + } + + idempotentTask := client.NewStandaloneTask( + "idempotent-task", + func(ctx hatchet.Context, input IdempotencyInput) (*IdempotencyOutput, error) { + return &IdempotencyOutput{ + Result: fmt.Sprintf("Hello, world from task %s", input.ID), + }, nil + }, + hatchet.WithWorkflowIdempotency(hatchet.IdempotencyConfig{ + Expression: "input.id", + TTL: time.Minute, + }), + ) + + ctx := context.Background() + + // > trigger + ref1, err := idempotentTask.RunNoWait(ctx, IdempotencyInput{ID: "123"}) + if err != nil { + log.Fatalf("unexpected error on first run: %v", err) + } + + ref2, err := idempotentTask.RunNoWait(ctx, IdempotencyInput{ID: "123"}) + + var runID2 string + + if err != nil { + if idempErr, ok := hatchet.IsIdempotencyCollisionError(err); ok { + fmt.Printf("Run %s already exists for this idempotency key\n", idempErr.ExistingRunExternalId) + runID2 = idempErr.ExistingRunExternalId + } else { + log.Fatalf("unexpected error on second run: %v", err) + } + } else { + runID2 = ref2.RunId + } + + fmt.Printf("First run ID: %s\n", ref1.RunId) + fmt.Printf("Second run ID: %s\n", runID2) +} diff --git a/examples/go/idempotency/worker/main.go b/examples/go/idempotency/worker/main.go new file mode 100644 index 0000000000..8689509973 --- /dev/null +++ b/examples/go/idempotency/worker/main.go @@ -0,0 +1,55 @@ +package main + +import ( + "fmt" + "log" + "time" + + "github.com/hatchet-dev/hatchet/pkg/cmdutils" + hatchet "github.com/hatchet-dev/hatchet/sdks/go" +) + +type IdempotencyInput struct { + ID string `json:"id"` +} + +type IdempotencyOutput struct { + Result string `json:"result"` +} + +// > idempotency +func IdempotentTask(client *hatchet.Client) *hatchet.StandaloneTask { + return client.NewStandaloneTask( + "idempotent-task", + func(ctx hatchet.Context, input IdempotencyInput) (*IdempotencyOutput, error) { + return &IdempotencyOutput{ + Result: fmt.Sprintf("Hello, world from task %s", input.ID), + }, nil + }, + hatchet.WithWorkflowIdempotency(hatchet.IdempotencyConfig{ + Expression: "input.id", + TTL: time.Minute, + }), + ) +} + +func main() { + client, err := hatchet.NewClient() + if err != nil { + log.Fatalf("failed to create hatchet client: %v", err) + } + + worker, err := client.NewWorker("idempotency-worker", + hatchet.WithWorkflows(IdempotentTask(client)), + ) + if err != nil { + log.Fatalf("failed to create worker: %v", err) + } + + interruptCtx, cancel := cmdutils.NewInterruptContext() + defer cancel() + + if err := worker.StartBlocking(interruptCtx); err != nil { + log.Fatalf("failed to start worker: %v", err) + } +} diff --git a/examples/ruby/idempotency/trigger.rb b/examples/ruby/idempotency/trigger.rb new file mode 100644 index 0000000000..a5829fce04 --- /dev/null +++ b/examples/ruby/idempotency/trigger.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require 'hatchet-sdk' +require_relative 'worker' + +HATCHET = Hatchet::Client.new(debug: true) unless defined?(HATCHET) + +# > trigger +first_ref = IDEMPOTENT_TASK.run_no_wait({ 'id' => '123' }) + +second_run_id = begin + second_ref = IDEMPOTENT_TASK.run_no_wait({ 'id' => '123' }) + second_ref.workflow_run_id +rescue Hatchet::IdempotencyCollisionError => e + puts "Run #{e.existing_run_external_id} already exists for this idempotency key" + e.existing_run_external_id +end + +puts "First run: #{first_ref.workflow_run_id}" +puts "Second run (or existing): #{second_run_id}" diff --git a/examples/ruby/idempotency/worker.rb b/examples/ruby/idempotency/worker.rb new file mode 100644 index 0000000000..605d1d3db0 --- /dev/null +++ b/examples/ruby/idempotency/worker.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'hatchet-sdk' + +HATCHET = Hatchet::Client.new(debug: true) unless defined?(HATCHET) + +# > idempotency +IDEMPOTENT_TASK = HATCHET.task( + name: 'idempotent-task', + idempotency: { expression: 'input.id', ttl_ms: 60_000 }, + on_events: ['idempotency:example'] +) do |input, _ctx| + { 'result' => "Hello from task #{input['id']}" } +end + +def main + worker = HATCHET.worker('idempotency-worker', workflows: [IDEMPOTENT_TASK]) + worker.start +end + +main if __FILE__ == $PROGRAM_NAME diff --git a/examples/typescript/idempotency/run.ts b/examples/typescript/idempotency/run.ts new file mode 100644 index 0000000000..cd0ca66b19 --- /dev/null +++ b/examples/typescript/idempotency/run.ts @@ -0,0 +1,27 @@ +import { IdempotencyCollisionError } from '@hatchet-dev/typescript-sdk/v1'; +import { idempotentTask } from './workflow'; + +async function main() { + // > trigger + const ref1 = await idempotentTask.runNoWait({ id: '123' }); + + let runId2: string; + try { + const ref2 = await idempotentTask.runNoWait({ id: '123' }); + runId2 = await ref2.getWorkflowRunId(); + } catch (e) { + if (e instanceof IdempotencyCollisionError) { + console.log( + `Run with external ID ${e.existingRunExternalId} already exists for this idempotency key` + ); + runId2 = e.existingRunExternalId; + } else { + throw e; + } + } + + const res1 = await ref1.result(); + console.log(`Result: ${JSON.stringify(res1)}, run ID: ${runId2}`); +} + +main().catch(console.error); diff --git a/examples/typescript/idempotency/workflow.ts b/examples/typescript/idempotency/workflow.ts new file mode 100644 index 0000000000..02d53abd5f --- /dev/null +++ b/examples/typescript/idempotency/workflow.ts @@ -0,0 +1,17 @@ +import { hatchet } from '../hatchet-client'; + +export type IdempotencyInput = { + id: string; +}; + +// > idempotency +export const idempotentTask = hatchet.task({ + name: 'idempotent-task', + idempotency: { + expression: 'input.id', + ttlMs: 60_000, + }, + fn: async (input) => { + return { result: `Hello, world from task ${input.id}` }; + }, +}); diff --git a/frontend/docs/pages/v1/idempotency.mdx b/frontend/docs/pages/v1/idempotency.mdx index cf2ba32520..1854291f32 100644 --- a/frontend/docs/pages/v1/idempotency.mdx +++ b/frontend/docs/pages/v1/idempotency.mdx @@ -11,6 +11,10 @@ If you need to prevent more than one run of a task from occurring within a given Configuring idempotency on a workflow or standalone task requires two parameters to be set: the **expression**, which will be used to create an idempotency key from the input and additional metadata of the run that's going to be triggered, and the **TTL**, which determines how long the key should live for. + + The idempotency key expression must evaluate to a string. + + - {/* TODO: Add snippet */} + - {/* TODO: Add snippet */} + - {/* TODO: Add snippet */} - + @@ -46,16 +49,14 @@ When a collision occurs, the engine will reject the workflow run, and, if the ru /> - {/* TODO: Add snippet */} + - {/* TODO: Add snippet */} + - {/* TODO: Add snippet */} - + In other cases, such as triggering by events, the idempotency collision will be swallowed, and no additional runs will be created by the event will still be ingested correctly without an error being raised. - diff --git a/pkg/client/admin.go b/pkg/client/admin.go index 20c9edcb17..34a9e86630 100644 --- a/pkg/client/admin.go +++ b/pkg/client/admin.go @@ -174,6 +174,14 @@ func (d *DedupeViolationErr) Error() string { return fmt.Sprintf("DedupeViolationErr: %s", d.details) } +type IdempotencyViolationErr struct { + ExistingRunExternalId string +} + +func (e *IdempotencyViolationErr) Error() string { + return fmt.Sprintf("idempotency key collision: existing run %s", e.ExistingRunExternalId) +} + type adminClientImpl struct { client admincontracts.WorkflowServiceClient v1Client v1contracts.AdminServiceClient @@ -421,6 +429,15 @@ func (a *adminClientImpl) RunWorkflow(workflowName string, input interface{}, op if err != nil { if status.Code(err) == codes.AlreadyExists { + if st, ok := status.FromError(err); ok { + for _, detail := range st.Details() { + if idempotencyErr, ok := detail.(*v1contracts.IdempotencyCollisionError); ok { + return nil, &IdempotencyViolationErr{ + ExistingRunExternalId: idempotencyErr.GetExistingRunExternalId(), + } + } + } + } return nil, &DedupeViolationErr{ details: fmt.Sprintf("could not trigger workflow: %s", err.Error()), } @@ -491,6 +508,15 @@ func (a *adminClientImpl) RunChildWorkflow(workflowName string, input interface{ if err != nil { if status.Code(err) == codes.AlreadyExists { + if st, ok := status.FromError(err); ok { + for _, detail := range st.Details() { + if idempotencyErr, ok := detail.(*v1contracts.IdempotencyCollisionError); ok { + return "", &IdempotencyViolationErr{ + ExistingRunExternalId: idempotencyErr.GetExistingRunExternalId(), + } + } + } + } return "", &DedupeViolationErr{ details: fmt.Sprintf("could not trigger child workflow: %s", err.Error()), } diff --git a/pkg/client/create/tasks.go b/pkg/client/create/tasks.go index de6bf05e31..558dbaa850 100644 --- a/pkg/client/create/tasks.go +++ b/pkg/client/create/tasks.go @@ -8,6 +8,15 @@ import ( "github.com/hatchet-dev/hatchet/pkg/client/types" ) +// IdempotencyConfig configures idempotency behavior for a workflow. +type IdempotencyConfig struct { + // Expression is a CEL expression evaluated against the workflow input to produce an idempotency key. + Expression string + + // TTL is the duration during which duplicate runs with the same key are rejected. + TTL time.Duration +} + // TaskDefaults defines default configuration values for tasks within a workflow. type TaskDefaults struct { // (optional) ExecutionTimeout specifies the maximum duration a task can run after starting before being terminated @@ -62,4 +71,7 @@ type WorkflowCreateOpts[I any] struct { DefaultPriority *int32 DefaultFilters []types.DefaultFilter + + // (optional) Idempotency configuration for preventing duplicate runs + Idempotency *IdempotencyConfig } diff --git a/sdks/go/errors.go b/sdks/go/errors.go index 5cc132a463..2d263acf65 100644 --- a/sdks/go/errors.go +++ b/sdks/go/errors.go @@ -51,3 +51,21 @@ func IsEvictionNotSupportedError(err error) (*EvictionNotSupportedError, bool) { } return nil, false } + +// IdempotencyCollisionError is returned when an idempotency key collision occurs. +// It contains the ID of the existing run that claimed the key. +type IdempotencyCollisionError struct { + ExistingRunExternalId string +} + +func (e *IdempotencyCollisionError) Error() string { + return fmt.Sprintf("idempotency key collision: existing run %s already exists", e.ExistingRunExternalId) +} + +// IsIdempotencyCollisionError checks if the error is an IdempotencyCollisionError. +func IsIdempotencyCollisionError(err error) (*IdempotencyCollisionError, bool) { + if e, ok := err.(*IdempotencyCollisionError); ok { + return e, true + } + return nil, false +} diff --git a/sdks/go/examples/idempotency/trigger/main.go b/sdks/go/examples/idempotency/trigger/main.go new file mode 100644 index 0000000000..09ee90a477 --- /dev/null +++ b/sdks/go/examples/idempotency/trigger/main.go @@ -0,0 +1,65 @@ +package main + +import ( + "context" + "fmt" + "log" + "time" + + hatchet "github.com/hatchet-dev/hatchet/sdks/go" +) + +type IdempotencyInput struct { + ID string `json:"id"` +} + +type IdempotencyOutput struct { + Result string `json:"result"` +} + +func main() { + client, err := hatchet.NewClient() + if err != nil { + log.Fatalf("failed to create hatchet client: %v", err) + } + + idempotentTask := client.NewStandaloneTask( + "idempotent-task", + func(ctx hatchet.Context, input IdempotencyInput) (*IdempotencyOutput, error) { + return &IdempotencyOutput{ + Result: fmt.Sprintf("Hello, world from task %s", input.ID), + }, nil + }, + hatchet.WithWorkflowIdempotency(hatchet.IdempotencyConfig{ + Expression: "input.id", + TTL: time.Minute, + }), + ) + + ctx := context.Background() + + // > trigger + ref1, err := idempotentTask.RunNoWait(ctx, IdempotencyInput{ID: "123"}) + if err != nil { + log.Fatalf("unexpected error on first run: %v", err) + } + + ref2, err := idempotentTask.RunNoWait(ctx, IdempotencyInput{ID: "123"}) + + var runID2 string + + if err != nil { + if idempErr, ok := hatchet.IsIdempotencyCollisionError(err); ok { + fmt.Printf("Run %s already exists for this idempotency key\n", idempErr.ExistingRunExternalId) + runID2 = idempErr.ExistingRunExternalId + } else { + log.Fatalf("unexpected error on second run: %v", err) + } + } else { + runID2 = ref2.RunId + } + // !! + + fmt.Printf("First run ID: %s\n", ref1.RunId) + fmt.Printf("Second run ID: %s\n", runID2) +} diff --git a/sdks/go/examples/idempotency/worker/main.go b/sdks/go/examples/idempotency/worker/main.go new file mode 100644 index 0000000000..1cf35e1aeb --- /dev/null +++ b/sdks/go/examples/idempotency/worker/main.go @@ -0,0 +1,57 @@ +package main + +import ( + "fmt" + "log" + "time" + + "github.com/hatchet-dev/hatchet/pkg/cmdutils" + hatchet "github.com/hatchet-dev/hatchet/sdks/go" +) + +type IdempotencyInput struct { + ID string `json:"id"` +} + +type IdempotencyOutput struct { + Result string `json:"result"` +} + +// > idempotency +func IdempotentTask(client *hatchet.Client) *hatchet.StandaloneTask { + return client.NewStandaloneTask( + "idempotent-task", + func(ctx hatchet.Context, input IdempotencyInput) (*IdempotencyOutput, error) { + return &IdempotencyOutput{ + Result: fmt.Sprintf("Hello, world from task %s", input.ID), + }, nil + }, + hatchet.WithWorkflowIdempotency(hatchet.IdempotencyConfig{ + Expression: "input.id", + TTL: time.Minute, + }), + ) +} + +// !! + +func main() { + client, err := hatchet.NewClient() + if err != nil { + log.Fatalf("failed to create hatchet client: %v", err) + } + + worker, err := client.NewWorker("idempotency-worker", + hatchet.WithWorkflows(IdempotentTask(client)), + ) + if err != nil { + log.Fatalf("failed to create worker: %v", err) + } + + interruptCtx, cancel := cmdutils.NewInterruptContext() + defer cancel() + + if err := worker.StartBlocking(interruptCtx); err != nil { + log.Fatalf("failed to start worker: %v", err) + } +} diff --git a/sdks/go/internal/declaration.go b/sdks/go/internal/declaration.go index c007c6b6b4..6264d71890 100644 --- a/sdks/go/internal/declaration.go +++ b/sdks/go/internal/declaration.go @@ -142,6 +142,7 @@ type workflowDeclarationImpl[I any, O any] struct { DefaultPriority *int32 DefaultFilters []types.DefaultFilter + Idempotency *create.IdempotencyConfig } // NewWorkflowDeclaration creates a new workflow declaration with the specified options and client. @@ -197,6 +198,7 @@ func NewWorkflowDeclaration[I any, O any](opts create.WorkflowCreateOpts[I], v0 outputSetters: make(map[string]func(*O, interface{})), DefaultPriority: opts.DefaultPriority, DefaultFilters: opts.DefaultFilters, + Idempotency: opts.Idempotency, } if opts.Version != "" { @@ -642,6 +644,13 @@ func (w *workflowDeclarationImpl[I, O]) Dump() (*contracts.CreateWorkflowVersion req.Sticky = &stickyStrategy } + if w.Idempotency != nil { + req.Idempotency = &contracts.IdempotencyConfig{ + Expression: w.Idempotency.Expression, + TtlMs: w.Idempotency.TTL.Milliseconds(), + } + } + // Create named function objects for regular tasks regularNamedFns := make([]NamedFunction, len(w.tasks)) for i, task := range w.tasks { diff --git a/sdks/go/workflow.go b/sdks/go/workflow.go index 55fc4d872b..c83c592a46 100644 --- a/sdks/go/workflow.go +++ b/sdks/go/workflow.go @@ -148,6 +148,17 @@ func (w *Workflow) GetName() string { // WorkflowOption configures a workflow instance. type WorkflowOption func(*workflowConfig) +// IdempotencyConfig configures idempotency behavior for a workflow or standalone task. +// When set, runs triggered with the same computed key within the TTL window return an +// IdempotencyCollisionError instead of creating a new run. +type IdempotencyConfig struct { + // Expression is a CEL expression evaluated against the workflow input to produce an idempotency key. + Expression string + + // TTL is the duration during which duplicate runs with the same key are rejected. + TTL time.Duration +} + type workflowConfig struct { onCron []string onEvents []string @@ -158,6 +169,7 @@ type workflowConfig struct { defaultPriority *RunPriority stickyStrategy *types.StickyStrategy cronInput *string + idempotency *IdempotencyConfig } // WithWorkflowCron configures the workflow to run on a cron schedule. @@ -235,6 +247,15 @@ func WithWorkflowStickyStrategy(stickyStrategy types.StickyStrategy) WorkflowOpt } } +// WithWorkflowIdempotency configures idempotency for the workflow. +// When set, runs triggered with the same computed key within the TTL window return an +// IdempotencyCollisionError instead of creating a new run. +func WithWorkflowIdempotency(config IdempotencyConfig) WorkflowOption { + return func(c *workflowConfig) { + c.idempotency = &config + } +} + // newWorkflow creates a new workflow definition. func newWorkflow(name string, v0Client v0Client.Client, options ...WorkflowOption) *Workflow { config := &workflowConfig{} @@ -265,6 +286,13 @@ func newWorkflow(name string, v0Client v0Client.Client, options ...WorkflowOptio createOpts.DefaultPriority = &priority } + if config.idempotency != nil { + createOpts.Idempotency = &create.IdempotencyConfig{ + Expression: config.idempotency.Expression, + TTL: config.idempotency.TTL, + } + } + declaration := internal.NewWorkflowDeclaration[any, any](createOpts, v0Client) return &Workflow{ @@ -725,6 +753,14 @@ func (w *Workflow) runWorkflowInternal(ctx context.Context, otelCtx context.Cont } if err != nil { + var idempViolation *v0Client.IdempotencyViolationErr + if errors.As(err, &idempViolation) { + span.SetStatus(codes.Error, err.Error()) + span.RecordError(err) + return nil, &IdempotencyCollisionError{ + ExistingRunExternalId: idempViolation.ExistingRunExternalId, + } + } span.SetStatus(codes.Error, err.Error()) span.RecordError(err) return nil, err diff --git a/sdks/ruby/examples/idempotency/trigger.rb b/sdks/ruby/examples/idempotency/trigger.rb new file mode 100644 index 0000000000..486ed35155 --- /dev/null +++ b/sdks/ruby/examples/idempotency/trigger.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'hatchet-sdk' +require_relative 'worker' + +HATCHET = Hatchet::Client.new(debug: true) unless defined?(HATCHET) + +# > trigger +first_ref = IDEMPOTENT_TASK.run_no_wait({ 'id' => '123' }) + +second_run_id = begin + second_ref = IDEMPOTENT_TASK.run_no_wait({ 'id' => '123' }) + second_ref.workflow_run_id +rescue Hatchet::IdempotencyCollisionError => e + puts "Run #{e.existing_run_external_id} already exists for this idempotency key" + e.existing_run_external_id +end +# !! + +puts "First run: #{first_ref.workflow_run_id}" +puts "Second run (or existing): #{second_run_id}" diff --git a/sdks/ruby/examples/idempotency/worker.rb b/sdks/ruby/examples/idempotency/worker.rb new file mode 100644 index 0000000000..d3716e95be --- /dev/null +++ b/sdks/ruby/examples/idempotency/worker.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'hatchet-sdk' + +HATCHET = Hatchet::Client.new(debug: true) unless defined?(HATCHET) + +# > idempotency +IDEMPOTENT_TASK = HATCHET.task( + name: 'idempotent-task', + idempotency: { expression: 'input.id', ttl_ms: 60_000 }, + on_events: ['idempotency:example'] +) do |input, _ctx| + { 'result' => "Hello from task #{input['id']}" } +end +# !! + +def main + worker = HATCHET.worker('idempotency-worker', workflows: [IDEMPOTENT_TASK]) + worker.start +end + +main if __FILE__ == $PROGRAM_NAME diff --git a/sdks/ruby/src/.rubocop.yml b/sdks/ruby/src/.rubocop.yml index 30d92223e8..0b58d10910 100644 --- a/sdks/ruby/src/.rubocop.yml +++ b/sdks/ruby/src/.rubocop.yml @@ -28,9 +28,6 @@ Style/FrozenStringLiteralComment: Style/Documentation: Enabled: false -# TODO(gregfurman): Requires refactoring classes in lib/hatchet-sdk.rb:73:1 -Style/OneClassPerFile: - Enabled: false Style/TrailingCommaInArguments: EnforcedStyleForMultiline: consistent_comma diff --git a/sdks/ruby/src/lib/hatchet-sdk.rb b/sdks/ruby/src/lib/hatchet-sdk.rb index 3bd264c21a..f1651e5005 100644 --- a/sdks/ruby/src/lib/hatchet-sdk.rb +++ b/sdks/ruby/src/lib/hatchet-sdk.rb @@ -195,7 +195,7 @@ def scheduled # Create a new workflow definition # # @param name [String] Workflow name - # @param opts [Hash] Workflow options + # @param opts [Hash] Workflow options (on_events:, concurrency:, idempotency:, etc.) # @return [Hatchet::Workflow] # # @example @@ -208,17 +208,17 @@ def workflow(name:, **opts) # Create a standalone task (auto-wraps in a single-task workflow) # # @param name [String] Task name - # @param opts [Hash] Task options + # @param opts [Hash] Task options (on_events:, idempotency:, retries:, etc.) # @yield [input, ctx] The task execution block # @return [Hatchet::Task] # # @example # my_task = hatchet.task(name: "my_task") { |input, ctx| { "result" => "done" } } def task(name:, **opts, &block) - # Create a workflow wrapper for standalone tasks wf = Workflow.new(name: name, client: self, on_events: opts.delete(:on_events) || [], - default_filters: opts.delete(:default_filters) || [],) + default_filters: opts.delete(:default_filters) || [], + idempotency: opts.delete(:idempotency),) wf.task(name, **opts, &block) end diff --git a/sdks/ruby/src/lib/hatchet/clients/grpc/admin.rb b/sdks/ruby/src/lib/hatchet/clients/grpc/admin.rb index 4c78f120cc..94aabfffd8 100644 --- a/sdks/ruby/src/lib/hatchet/clients/grpc/admin.rb +++ b/sdks/ruby/src/lib/hatchet/clients/grpc/admin.rb @@ -93,6 +93,10 @@ def trigger_workflow(workflow_name, input: {}, options: {}) response = @v0_stub.trigger_workflow(request, metadata: @config.auth_metadata) response.workflow_run_id rescue ::GRPC::AlreadyExists => e + if e.message.include?("idempotency key collision") + run_id = extract_idempotency_run_id(e) + raise IdempotencyCollisionError, run_id + end raise DedupeViolationError, "Deduplication violation: #{e.message}" rescue ::GRPC::ResourceExhausted => e raise ResourceExhaustedError, e.message @@ -284,6 +288,111 @@ def build_trigger_worker_labels(labels) end end + # Parse the grpc-status-details-bin binary protobuf from a gRPC error to + # extract the existing run ID from a V1::IdempotencyCollisionError detail. + def extract_idempotency_run_id(grpc_error) + status_bin = grpc_error.to_status.metadata&.[]("grpc-status-details-bin") + return "" unless status_bin + + parse_idempotency_run_id(status_bin.b) + rescue StandardError + "" + end + + # Parse the binary google.rpc.Status proto to find an IdempotencyCollisionError + # detail and return the existing_run_external_id field value. + # + # google.rpc.Status layout (proto3): + # field 1 (int32) = code + # field 2 (string) = message + # field 3 (message, repeated) = details <- we want these + # + # Each detail is google.protobuf.Any: + # field 1 (string) = type_url + # field 2 (bytes) = value + def parse_idempotency_run_id(data) + pos = 0 + + while pos < data.bytesize + tag, pos = read_varint(data, pos) + field_num = tag >> 3 + wire_type = tag & 7 + + if field_num == 3 && wire_type == 2 + len, pos = read_varint(data, pos) + any_data = data.byteslice(pos, len) + pos += len + + type_url, value = parse_any_message(any_data) + if type_url&.include?("IdempotencyCollisionError") && value + return ::V1::IdempotencyCollisionError.decode(value).existing_run_external_id + end + else + pos = skip_field(data, pos, wire_type) + end + end + + "" + rescue StandardError + "" + end + + def parse_any_message(data) + type_url = nil + value = nil + pos = 0 + + while pos < data.bytesize + tag, pos = read_varint(data, pos) + field_num = tag >> 3 + wire_type = tag & 7 + + if wire_type == 2 + len, pos = read_varint(data, pos) + field_data = data.byteslice(pos, len) + type_url = field_data if field_num == 1 + value = field_data if field_num == 2 + else + pos = skip_field(data, pos, wire_type) + end + end + + [type_url, value] + end + + def read_varint(data, pos) + result = 0 + shift = 0 + loop do + byte = data.getbyte(pos) + pos += 1 + result |= (byte & 0x7F) << shift + break if byte.nobits?(0x80) + + shift += 7 + end + [result, pos] + end + + def skip_field(data, pos, wire_type) + case wire_type + when 0 + loop do + byte = data.getbyte(pos) + pos += 1 + break if byte.nobits?(0x80) + end + when 1 + pos += 8 + when 2 + len, pos = read_varint(data, pos) + pos += len + when 5 + pos += 4 + end + pos + end + def ensure_connected! return if @v0_stub && @v1_stub diff --git a/sdks/ruby/src/lib/hatchet/contracts/v1/workflows_pb.rb b/sdks/ruby/src/lib/hatchet/contracts/v1/workflows_pb.rb index 628b311eea..af59513de3 100644 --- a/sdks/ruby/src/lib/hatchet/contracts/v1/workflows_pb.rb +++ b/sdks/ruby/src/lib/hatchet/contracts/v1/workflows_pb.rb @@ -9,7 +9,7 @@ require 'v1/shared/trigger_pb' -descriptor_data = "\n\x12v1/workflows.proto\x12\x02v1\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x19v1/shared/condition.proto\x1a\x17v1/shared/trigger.proto\"[\n\x12\x43\x61ncelTasksRequest\x12\x14\n\x0c\x65xternal_ids\x18\x01 \x03(\t\x12$\n\x06\x66ilter\x18\x02 \x01(\x0b\x32\x0f.v1.TasksFilterH\x00\x88\x01\x01\x42\t\n\x07_filter\"[\n\x12ReplayTasksRequest\x12\x14\n\x0c\x65xternal_ids\x18\x01 \x03(\t\x12$\n\x06\x66ilter\x18\x02 \x01(\x0b\x32\x0f.v1.TasksFilterH\x00\x88\x01\x01\x42\t\n\x07_filter\"\xb7\x01\n\x0bTasksFilter\x12\x10\n\x08statuses\x18\x01 \x03(\t\x12)\n\x05since\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12.\n\x05until\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampH\x00\x88\x01\x01\x12\x14\n\x0cworkflow_ids\x18\x04 \x03(\t\x12\x1b\n\x13\x61\x64\x64itional_metadata\x18\x05 \x03(\tB\x08\n\x06_until\".\n\x13\x43\x61ncelTasksResponse\x12\x17\n\x0f\x63\x61ncelled_tasks\x18\x01 \x03(\t\"-\n\x13ReplayTasksResponse\x12\x16\n\x0ereplayed_tasks\x18\x01 \x03(\t\"\xae\x02\n\x19TriggerWorkflowRunRequest\x12\x15\n\rworkflow_name\x18\x01 \x01(\t\x12\r\n\x05input\x18\x02 \x01(\x0c\x12\x1b\n\x13\x61\x64\x64itional_metadata\x18\x03 \x01(\x0c\x12\x15\n\x08priority\x18\x04 \x01(\x05H\x00\x88\x01\x01\x12U\n\x15\x64\x65sired_worker_labels\x18\x05 \x03(\x0b\x32\x36.v1.TriggerWorkflowRunRequest.DesiredWorkerLabelsEntry\x1aS\n\x18\x44\x65siredWorkerLabelsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12&\n\x05value\x18\x02 \x01(\x0b\x32\x17.v1.DesiredWorkerLabels:\x02\x38\x01\x42\x0b\n\t_priority\"1\n\x1aTriggerWorkflowRunResponse\x12\x13\n\x0b\x65xternal_id\x18\x01 \x01(\t\"X\n\x18\x42ranchDurableTaskRequest\x12\x18\n\x10task_external_id\x18\x01 \x01(\t\x12\x0f\n\x07node_id\x18\x02 \x01(\x03\x12\x11\n\tbranch_id\x18\x03 \x01(\x03\"Y\n\x19\x42ranchDurableTaskResponse\x12\x18\n\x10task_external_id\x18\x01 \x01(\t\x12\x0f\n\x07node_id\x18\x02 \x01(\x03\x12\x11\n\tbranch_id\x18\x03 \x01(\x03\"\xac\x04\n\x1c\x43reateWorkflowVersionRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x0f\n\x07version\x18\x03 \x01(\t\x12\x16\n\x0e\x65vent_triggers\x18\x04 \x03(\t\x12\x15\n\rcron_triggers\x18\x05 \x03(\t\x12!\n\x05tasks\x18\x06 \x03(\x0b\x32\x12.v1.CreateTaskOpts\x12$\n\x0b\x63oncurrency\x18\x07 \x01(\x0b\x32\x0f.v1.Concurrency\x12\x17\n\ncron_input\x18\x08 \x01(\tH\x00\x88\x01\x01\x12\x30\n\x0fon_failure_task\x18\t \x01(\x0b\x32\x12.v1.CreateTaskOptsH\x01\x88\x01\x01\x12\'\n\x06sticky\x18\n \x01(\x0e\x32\x12.v1.StickyStrategyH\x02\x88\x01\x01\x12\x1d\n\x10\x64\x65\x66\x61ult_priority\x18\x0b \x01(\x05H\x03\x88\x01\x01\x12(\n\x0f\x63oncurrency_arr\x18\x0c \x03(\x0b\x32\x0f.v1.Concurrency\x12*\n\x0f\x64\x65\x66\x61ult_filters\x18\r \x03(\x0b\x32\x11.v1.DefaultFilter\x12\x1e\n\x11input_json_schema\x18\x0e \x01(\x0cH\x04\x88\x01\x01\x42\r\n\x0b_cron_inputB\x12\n\x10_on_failure_taskB\t\n\x07_stickyB\x13\n\x11_default_priorityB\x14\n\x12_input_json_schema\"T\n\rDefaultFilter\x12\x12\n\nexpression\x18\x01 \x01(\t\x12\r\n\x05scope\x18\x02 \x01(\t\x12\x14\n\x07payload\x18\x03 \x01(\x0cH\x00\x88\x01\x01\x42\n\n\x08_payload\"\x93\x01\n\x0b\x43oncurrency\x12\x12\n\nexpression\x18\x01 \x01(\t\x12\x15\n\x08max_runs\x18\x02 \x01(\x05H\x00\x88\x01\x01\x12\x39\n\x0elimit_strategy\x18\x03 \x01(\x0e\x32\x1c.v1.ConcurrencyLimitStrategyH\x01\x88\x01\x01\x42\x0b\n\t_max_runsB\x11\n\x0f_limit_strategy\"\xb7\x05\n\x0e\x43reateTaskOpts\x12\x13\n\x0breadable_id\x18\x01 \x01(\t\x12\x0e\n\x06\x61\x63tion\x18\x02 \x01(\t\x12\x0f\n\x07timeout\x18\x03 \x01(\t\x12\x0e\n\x06inputs\x18\x04 \x01(\t\x12\x0f\n\x07parents\x18\x05 \x03(\t\x12\x0f\n\x07retries\x18\x06 \x01(\x05\x12,\n\x0brate_limits\x18\x07 \x03(\x0b\x32\x17.v1.CreateTaskRateLimit\x12;\n\rworker_labels\x18\x08 \x03(\x0b\x32$.v1.CreateTaskOpts.WorkerLabelsEntry\x12\x1b\n\x0e\x62\x61\x63koff_factor\x18\t \x01(\x02H\x00\x88\x01\x01\x12 \n\x13\x62\x61\x63koff_max_seconds\x18\n \x01(\x05H\x01\x88\x01\x01\x12$\n\x0b\x63oncurrency\x18\x0b \x03(\x0b\x32\x0f.v1.Concurrency\x12+\n\nconditions\x18\x0c \x01(\x0b\x32\x12.v1.TaskConditionsH\x02\x88\x01\x01\x12\x1d\n\x10schedule_timeout\x18\r \x01(\tH\x03\x88\x01\x01\x12\x12\n\nis_durable\x18\x0e \x01(\x08\x12;\n\rslot_requests\x18\x0f \x03(\x0b\x32$.v1.CreateTaskOpts.SlotRequestsEntry\x1aL\n\x11WorkerLabelsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12&\n\x05value\x18\x02 \x01(\x0b\x32\x17.v1.DesiredWorkerLabels:\x02\x38\x01\x1a\x33\n\x11SlotRequestsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x05:\x02\x38\x01\x42\x11\n\x0f_backoff_factorB\x16\n\x14_backoff_max_secondsB\r\n\x0b_conditionsB\x13\n\x11_schedule_timeout\"\xfd\x01\n\x13\x43reateTaskRateLimit\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x12\n\x05units\x18\x02 \x01(\x05H\x00\x88\x01\x01\x12\x15\n\x08key_expr\x18\x03 \x01(\tH\x01\x88\x01\x01\x12\x17\n\nunits_expr\x18\x04 \x01(\tH\x02\x88\x01\x01\x12\x1e\n\x11limit_values_expr\x18\x05 \x01(\tH\x03\x88\x01\x01\x12,\n\x08\x64uration\x18\x06 \x01(\x0e\x32\x15.v1.RateLimitDurationH\x04\x88\x01\x01\x42\x08\n\x06_unitsB\x0b\n\t_key_exprB\r\n\x0b_units_exprB\x14\n\x12_limit_values_exprB\x0b\n\t_duration\"@\n\x1d\x43reateWorkflowVersionResponse\x12\n\n\x02id\x18\x01 \x01(\t\x12\x13\n\x0bworkflow_id\x18\x02 \x01(\t\"+\n\x14GetRunDetailsRequest\x12\x13\n\x0b\x65xternal_id\x18\x01 \x01(\t\"\xaa\x01\n\rTaskRunDetail\x12\x13\n\x0b\x65xternal_id\x18\x01 \x01(\t\x12\x1d\n\x06status\x18\x02 \x01(\x0e\x32\r.v1.RunStatus\x12\x12\n\x05\x65rror\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x13\n\x06output\x18\x04 \x01(\x0cH\x01\x88\x01\x01\x12\x13\n\x0breadable_id\x18\x05 \x01(\t\x12\x12\n\nis_evicted\x18\x06 \x01(\x08\x42\x08\n\x06_errorB\t\n\x07_output\"\x84\x02\n\x15GetRunDetailsResponse\x12\r\n\x05input\x18\x01 \x01(\x0c\x12\x1d\n\x06status\x18\x02 \x01(\x0e\x32\r.v1.RunStatus\x12:\n\ttask_runs\x18\x03 \x03(\x0b\x32\'.v1.GetRunDetailsResponse.TaskRunsEntry\x12\x0c\n\x04\x64one\x18\x04 \x01(\x08\x12\x1b\n\x13\x61\x64\x64itional_metadata\x18\x05 \x01(\x0c\x12\x12\n\nis_evicted\x18\x06 \x01(\x08\x1a\x42\n\rTaskRunsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12 \n\x05value\x18\x02 \x01(\x0b\x32\x11.v1.TaskRunDetail:\x02\x38\x01*$\n\x0eStickyStrategy\x12\x08\n\x04SOFT\x10\x00\x12\x08\n\x04HARD\x10\x01*]\n\x11RateLimitDuration\x12\n\n\x06SECOND\x10\x00\x12\n\n\x06MINUTE\x10\x01\x12\x08\n\x04HOUR\x10\x02\x12\x07\n\x03\x44\x41Y\x10\x03\x12\x08\n\x04WEEK\x10\x04\x12\t\n\x05MONTH\x10\x05\x12\x08\n\x04YEAR\x10\x06*[\n\tRunStatus\x12\n\n\x06QUEUED\x10\x00\x12\x0b\n\x07RUNNING\x10\x01\x12\r\n\tCOMPLETED\x10\x02\x12\n\n\x06\x46\x41ILED\x10\x03\x12\r\n\tCANCELLED\x10\x04\x12\x0b\n\x07\x45VICTED\x10\x05*\x7f\n\x18\x43oncurrencyLimitStrategy\x12\x16\n\x12\x43\x41NCEL_IN_PROGRESS\x10\x00\x12\x0f\n\x0b\x44ROP_NEWEST\x10\x01\x12\x10\n\x0cQUEUE_NEWEST\x10\x02\x12\x15\n\x11GROUP_ROUND_ROBIN\x10\x03\x12\x11\n\rCANCEL_NEWEST\x10\x04\x32\xcf\x03\n\x0c\x41\x64minService\x12R\n\x0bPutWorkflow\x12 .v1.CreateWorkflowVersionRequest\x1a!.v1.CreateWorkflowVersionResponse\x12>\n\x0b\x43\x61ncelTasks\x12\x16.v1.CancelTasksRequest\x1a\x17.v1.CancelTasksResponse\x12>\n\x0bReplayTasks\x12\x16.v1.ReplayTasksRequest\x1a\x17.v1.ReplayTasksResponse\x12S\n\x12TriggerWorkflowRun\x12\x1d.v1.TriggerWorkflowRunRequest\x1a\x1e.v1.TriggerWorkflowRunResponse\x12\x44\n\rGetRunDetails\x12\x18.v1.GetRunDetailsRequest\x1a\x19.v1.GetRunDetailsResponse\x12P\n\x11\x42ranchDurableTask\x12\x1c.v1.BranchDurableTaskRequest\x1a\x1d.v1.BranchDurableTaskResponseBBZ@github.com/hatchet-dev/hatchet/internal/services/shared/proto/v1b\x06proto3" +descriptor_data = "\n\x12v1/workflows.proto\x12\x02v1\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x19v1/shared/condition.proto\x1a\x17v1/shared/trigger.proto\"[\n\x12\x43\x61ncelTasksRequest\x12\x14\n\x0c\x65xternal_ids\x18\x01 \x03(\t\x12$\n\x06\x66ilter\x18\x02 \x01(\x0b\x32\x0f.v1.TasksFilterH\x00\x88\x01\x01\x42\t\n\x07_filter\"[\n\x12ReplayTasksRequest\x12\x14\n\x0c\x65xternal_ids\x18\x01 \x03(\t\x12$\n\x06\x66ilter\x18\x02 \x01(\x0b\x32\x0f.v1.TasksFilterH\x00\x88\x01\x01\x42\t\n\x07_filter\"\xb7\x01\n\x0bTasksFilter\x12\x10\n\x08statuses\x18\x01 \x03(\t\x12)\n\x05since\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12.\n\x05until\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampH\x00\x88\x01\x01\x12\x14\n\x0cworkflow_ids\x18\x04 \x03(\t\x12\x1b\n\x13\x61\x64\x64itional_metadata\x18\x05 \x03(\tB\x08\n\x06_until\".\n\x13\x43\x61ncelTasksResponse\x12\x17\n\x0f\x63\x61ncelled_tasks\x18\x01 \x03(\t\"-\n\x13ReplayTasksResponse\x12\x16\n\x0ereplayed_tasks\x18\x01 \x03(\t\"\xae\x02\n\x19TriggerWorkflowRunRequest\x12\x15\n\rworkflow_name\x18\x01 \x01(\t\x12\r\n\x05input\x18\x02 \x01(\x0c\x12\x1b\n\x13\x61\x64\x64itional_metadata\x18\x03 \x01(\x0c\x12\x15\n\x08priority\x18\x04 \x01(\x05H\x00\x88\x01\x01\x12U\n\x15\x64\x65sired_worker_labels\x18\x05 \x03(\x0b\x32\x36.v1.TriggerWorkflowRunRequest.DesiredWorkerLabelsEntry\x1aS\n\x18\x44\x65siredWorkerLabelsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12&\n\x05value\x18\x02 \x01(\x0b\x32\x17.v1.DesiredWorkerLabels:\x02\x38\x01\x42\x0b\n\t_priority\"1\n\x1aTriggerWorkflowRunResponse\x12\x13\n\x0b\x65xternal_id\x18\x01 \x01(\t\"X\n\x18\x42ranchDurableTaskRequest\x12\x18\n\x10task_external_id\x18\x01 \x01(\t\x12\x0f\n\x07node_id\x18\x02 \x01(\x03\x12\x11\n\tbranch_id\x18\x03 \x01(\x03\"Y\n\x19\x42ranchDurableTaskResponse\x12\x18\n\x10task_external_id\x18\x01 \x01(\t\x12\x0f\n\x07node_id\x18\x02 \x01(\x03\x12\x11\n\tbranch_id\x18\x03 \x01(\x03\"\xed\x04\n\x1c\x43reateWorkflowVersionRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x0f\n\x07version\x18\x03 \x01(\t\x12\x16\n\x0e\x65vent_triggers\x18\x04 \x03(\t\x12\x15\n\rcron_triggers\x18\x05 \x03(\t\x12!\n\x05tasks\x18\x06 \x03(\x0b\x32\x12.v1.CreateTaskOpts\x12$\n\x0b\x63oncurrency\x18\x07 \x01(\x0b\x32\x0f.v1.Concurrency\x12\x17\n\ncron_input\x18\x08 \x01(\tH\x00\x88\x01\x01\x12\x30\n\x0fon_failure_task\x18\t \x01(\x0b\x32\x12.v1.CreateTaskOptsH\x01\x88\x01\x01\x12\'\n\x06sticky\x18\n \x01(\x0e\x32\x12.v1.StickyStrategyH\x02\x88\x01\x01\x12\x1d\n\x10\x64\x65\x66\x61ult_priority\x18\x0b \x01(\x05H\x03\x88\x01\x01\x12(\n\x0f\x63oncurrency_arr\x18\x0c \x03(\x0b\x32\x0f.v1.Concurrency\x12*\n\x0f\x64\x65\x66\x61ult_filters\x18\r \x03(\x0b\x32\x11.v1.DefaultFilter\x12\x1e\n\x11input_json_schema\x18\x0e \x01(\x0cH\x04\x88\x01\x01\x12/\n\x0bidempotency\x18\x0f \x01(\x0b\x32\x15.v1.IdempotencyConfigH\x05\x88\x01\x01\x42\r\n\x0b_cron_inputB\x12\n\x10_on_failure_taskB\t\n\x07_stickyB\x13\n\x11_default_priorityB\x14\n\x12_input_json_schemaB\x0e\n\x0c_idempotency\"7\n\x11IdempotencyConfig\x12\x12\n\nexpression\x18\x01 \x01(\t\x12\x0e\n\x06ttl_ms\x18\x02 \x01(\x03\"=\n\x19IdempotencyCollisionError\x12 \n\x18\x65xisting_run_external_id\x18\x01 \x01(\t\"T\n\rDefaultFilter\x12\x12\n\nexpression\x18\x01 \x01(\t\x12\r\n\x05scope\x18\x02 \x01(\t\x12\x14\n\x07payload\x18\x03 \x01(\x0cH\x00\x88\x01\x01\x42\n\n\x08_payload\"\x93\x01\n\x0b\x43oncurrency\x12\x12\n\nexpression\x18\x01 \x01(\t\x12\x15\n\x08max_runs\x18\x02 \x01(\x05H\x00\x88\x01\x01\x12\x39\n\x0elimit_strategy\x18\x03 \x01(\x0e\x32\x1c.v1.ConcurrencyLimitStrategyH\x01\x88\x01\x01\x42\x0b\n\t_max_runsB\x11\n\x0f_limit_strategy\"\xb7\x05\n\x0e\x43reateTaskOpts\x12\x13\n\x0breadable_id\x18\x01 \x01(\t\x12\x0e\n\x06\x61\x63tion\x18\x02 \x01(\t\x12\x0f\n\x07timeout\x18\x03 \x01(\t\x12\x0e\n\x06inputs\x18\x04 \x01(\t\x12\x0f\n\x07parents\x18\x05 \x03(\t\x12\x0f\n\x07retries\x18\x06 \x01(\x05\x12,\n\x0brate_limits\x18\x07 \x03(\x0b\x32\x17.v1.CreateTaskRateLimit\x12;\n\rworker_labels\x18\x08 \x03(\x0b\x32$.v1.CreateTaskOpts.WorkerLabelsEntry\x12\x1b\n\x0e\x62\x61\x63koff_factor\x18\t \x01(\x02H\x00\x88\x01\x01\x12 \n\x13\x62\x61\x63koff_max_seconds\x18\n \x01(\x05H\x01\x88\x01\x01\x12$\n\x0b\x63oncurrency\x18\x0b \x03(\x0b\x32\x0f.v1.Concurrency\x12+\n\nconditions\x18\x0c \x01(\x0b\x32\x12.v1.TaskConditionsH\x02\x88\x01\x01\x12\x1d\n\x10schedule_timeout\x18\r \x01(\tH\x03\x88\x01\x01\x12\x12\n\nis_durable\x18\x0e \x01(\x08\x12;\n\rslot_requests\x18\x0f \x03(\x0b\x32$.v1.CreateTaskOpts.SlotRequestsEntry\x1aL\n\x11WorkerLabelsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12&\n\x05value\x18\x02 \x01(\x0b\x32\x17.v1.DesiredWorkerLabels:\x02\x38\x01\x1a\x33\n\x11SlotRequestsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x05:\x02\x38\x01\x42\x11\n\x0f_backoff_factorB\x16\n\x14_backoff_max_secondsB\r\n\x0b_conditionsB\x13\n\x11_schedule_timeout\"\xfd\x01\n\x13\x43reateTaskRateLimit\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x12\n\x05units\x18\x02 \x01(\x05H\x00\x88\x01\x01\x12\x15\n\x08key_expr\x18\x03 \x01(\tH\x01\x88\x01\x01\x12\x17\n\nunits_expr\x18\x04 \x01(\tH\x02\x88\x01\x01\x12\x1e\n\x11limit_values_expr\x18\x05 \x01(\tH\x03\x88\x01\x01\x12,\n\x08\x64uration\x18\x06 \x01(\x0e\x32\x15.v1.RateLimitDurationH\x04\x88\x01\x01\x42\x08\n\x06_unitsB\x0b\n\t_key_exprB\r\n\x0b_units_exprB\x14\n\x12_limit_values_exprB\x0b\n\t_duration\"@\n\x1d\x43reateWorkflowVersionResponse\x12\n\n\x02id\x18\x01 \x01(\t\x12\x13\n\x0bworkflow_id\x18\x02 \x01(\t\"+\n\x14GetRunDetailsRequest\x12\x13\n\x0b\x65xternal_id\x18\x01 \x01(\t\"\xaa\x01\n\rTaskRunDetail\x12\x13\n\x0b\x65xternal_id\x18\x01 \x01(\t\x12\x1d\n\x06status\x18\x02 \x01(\x0e\x32\r.v1.RunStatus\x12\x12\n\x05\x65rror\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x13\n\x06output\x18\x04 \x01(\x0cH\x01\x88\x01\x01\x12\x13\n\x0breadable_id\x18\x05 \x01(\t\x12\x12\n\nis_evicted\x18\x06 \x01(\x08\x42\x08\n\x06_errorB\t\n\x07_output\"\x84\x02\n\x15GetRunDetailsResponse\x12\r\n\x05input\x18\x01 \x01(\x0c\x12\x1d\n\x06status\x18\x02 \x01(\x0e\x32\r.v1.RunStatus\x12:\n\ttask_runs\x18\x03 \x03(\x0b\x32\'.v1.GetRunDetailsResponse.TaskRunsEntry\x12\x0c\n\x04\x64one\x18\x04 \x01(\x08\x12\x1b\n\x13\x61\x64\x64itional_metadata\x18\x05 \x01(\x0c\x12\x12\n\nis_evicted\x18\x06 \x01(\x08\x1a\x42\n\rTaskRunsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12 \n\x05value\x18\x02 \x01(\x0b\x32\x11.v1.TaskRunDetail:\x02\x38\x01*$\n\x0eStickyStrategy\x12\x08\n\x04SOFT\x10\x00\x12\x08\n\x04HARD\x10\x01*]\n\x11RateLimitDuration\x12\n\n\x06SECOND\x10\x00\x12\n\n\x06MINUTE\x10\x01\x12\x08\n\x04HOUR\x10\x02\x12\x07\n\x03\x44\x41Y\x10\x03\x12\x08\n\x04WEEK\x10\x04\x12\t\n\x05MONTH\x10\x05\x12\x08\n\x04YEAR\x10\x06*[\n\tRunStatus\x12\n\n\x06QUEUED\x10\x00\x12\x0b\n\x07RUNNING\x10\x01\x12\r\n\tCOMPLETED\x10\x02\x12\n\n\x06\x46\x41ILED\x10\x03\x12\r\n\tCANCELLED\x10\x04\x12\x0b\n\x07\x45VICTED\x10\x05*\x7f\n\x18\x43oncurrencyLimitStrategy\x12\x16\n\x12\x43\x41NCEL_IN_PROGRESS\x10\x00\x12\x0f\n\x0b\x44ROP_NEWEST\x10\x01\x12\x10\n\x0cQUEUE_NEWEST\x10\x02\x12\x15\n\x11GROUP_ROUND_ROBIN\x10\x03\x12\x11\n\rCANCEL_NEWEST\x10\x04\x32\xcf\x03\n\x0c\x41\x64minService\x12R\n\x0bPutWorkflow\x12 .v1.CreateWorkflowVersionRequest\x1a!.v1.CreateWorkflowVersionResponse\x12>\n\x0b\x43\x61ncelTasks\x12\x16.v1.CancelTasksRequest\x1a\x17.v1.CancelTasksResponse\x12>\n\x0bReplayTasks\x12\x16.v1.ReplayTasksRequest\x1a\x17.v1.ReplayTasksResponse\x12S\n\x12TriggerWorkflowRun\x12\x1d.v1.TriggerWorkflowRunRequest\x1a\x1e.v1.TriggerWorkflowRunResponse\x12\x44\n\rGetRunDetails\x12\x18.v1.GetRunDetailsRequest\x1a\x19.v1.GetRunDetailsResponse\x12P\n\x11\x42ranchDurableTask\x12\x1c.v1.BranchDurableTaskRequest\x1a\x1d.v1.BranchDurableTaskResponseBBZ@github.com/hatchet-dev/hatchet/internal/services/shared/proto/v1b\x06proto3" pool = ::Google::Protobuf::DescriptorPool.generated_pool pool.add_serialized_file(descriptor_data) @@ -25,6 +25,8 @@ module V1 BranchDurableTaskRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("v1.BranchDurableTaskRequest").msgclass BranchDurableTaskResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("v1.BranchDurableTaskResponse").msgclass CreateWorkflowVersionRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("v1.CreateWorkflowVersionRequest").msgclass + IdempotencyConfig = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("v1.IdempotencyConfig").msgclass + IdempotencyCollisionError = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("v1.IdempotencyCollisionError").msgclass DefaultFilter = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("v1.DefaultFilter").msgclass Concurrency = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("v1.Concurrency").msgclass CreateTaskOpts = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("v1.CreateTaskOpts").msgclass diff --git a/sdks/ruby/src/lib/hatchet/exceptions.rb b/sdks/ruby/src/lib/hatchet/exceptions.rb index 0f81e47a29..23ec21ed7a 100644 --- a/sdks/ruby/src/lib/hatchet/exceptions.rb +++ b/sdks/ruby/src/lib/hatchet/exceptions.rb @@ -24,6 +24,18 @@ def initialize(message = "Dedupe violation: a run with this key already exists") end end + # Raised when an idempotency key collision occurs. + # Contains the ID of the existing run that claimed the key. + class IdempotencyCollisionError < Error + # @return [String] The external ID of the existing workflow run + attr_reader :existing_run_external_id + + def initialize(existing_run_external_id) + @existing_run_external_id = existing_run_external_id + super("idempotency key collision: existing run #{existing_run_external_id} already exists") + end + end + # Represents an error from a failed task run class TaskRunError < Error # @return [String] The external ID of the failed task run diff --git a/sdks/ruby/src/lib/hatchet/workflow.rb b/sdks/ruby/src/lib/hatchet/workflow.rb index 5f242d0220..589220d696 100644 --- a/sdks/ruby/src/lib/hatchet/workflow.rb +++ b/sdks/ruby/src/lib/hatchet/workflow.rb @@ -46,6 +46,9 @@ class Workflow # @return [Task, nil] The on_success task attr_reader :on_success + # @return [Hash, nil] Idempotency config with :expression and :ttl_ms keys + attr_reader :idempotency + # @return [String, nil] The workflow ID writer (set after registration) attr_writer :id @@ -57,6 +60,7 @@ class Workflow # @param task_defaults [Hash, nil] Default task settings # @param default_filters [Array] Default filters # @param sticky [Symbol, nil] Sticky strategy + # @param idempotency [Hash, nil] Idempotency config with :expression and :ttl_ms keys # @param client [Hatchet::Client, nil] The client def initialize( name:, @@ -67,6 +71,7 @@ def initialize( task_defaults: nil, default_filters: [], sticky: nil, + idempotency: nil, client: nil ) @name = name @@ -78,6 +83,7 @@ def initialize( @task_defaults = task_defaults @default_filters = default_filters @sticky = sticky + @idempotency = idempotency @client = client @on_failure = nil @on_success = nil @@ -208,6 +214,13 @@ def to_proto(config) args[:default_priority] = @default_priority if @default_priority args[:default_filters] = filter_protos unless filter_protos.empty? + if @idempotency + args[:idempotency] = ::V1::IdempotencyConfig.new( + expression: @idempotency[:expression], + ttl_ms: @idempotency[:ttl_ms], + ) + end + ::V1::CreateWorkflowVersionRequest.new(**args) end diff --git a/sdks/typescript/src/protoc/v1/workflows.ts b/sdks/typescript/src/protoc/v1/workflows.ts index 08d40c0f7b..2b4c235b86 100644 --- a/sdks/typescript/src/protoc/v1/workflows.ts +++ b/sdks/typescript/src/protoc/v1/workflows.ts @@ -312,6 +312,20 @@ export interface CreateWorkflowVersionRequest { defaultFilters: DefaultFilter[]; /** (optional) the JSON schema for the workflow input */ inputJsonSchema?: Uint8Array | undefined; + /** (optional) idempotency configuration for the workflow */ + idempotency?: IdempotencyConfig | undefined; +} + +export interface IdempotencyConfig { + /** a CEL expression for determining the idempotency key for workflow runs */ + expression: string; + /** time-to-live for idempotency keys in milliseconds */ + ttlMs: number; +} + +export interface IdempotencyCollisionError { + /** the external ID of the existing workflow run that caused the collision */ + existingRunExternalId: string; } export interface DefaultFilter { @@ -1448,6 +1462,7 @@ function createBaseCreateWorkflowVersionRequest(): CreateWorkflowVersionRequest concurrencyArr: [], defaultFilters: [], inputJsonSchema: undefined, + idempotency: undefined, }; } @@ -1498,6 +1513,9 @@ export const CreateWorkflowVersionRequest: MessageFns Concurrency.fromPartial(e)) || []; message.defaultFilters = object.defaultFilters?.map((e) => DefaultFilter.fromPartial(e)) || []; message.inputJsonSchema = object.inputJsonSchema ?? undefined; + message.idempotency = + object.idempotency !== undefined && object.idempotency !== null + ? IdempotencyConfig.fromPartial(object.idempotency) + : undefined; + return message; + }, +}; + +function createBaseIdempotencyConfig(): IdempotencyConfig { + return { expression: '', ttlMs: 0 }; +} + +export const IdempotencyConfig: MessageFns = { + encode(message: IdempotencyConfig, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.expression !== '') { + writer.uint32(10).string(message.expression); + } + if (message.ttlMs !== 0) { + writer.uint32(16).int64(message.ttlMs); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): IdempotencyConfig { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseIdempotencyConfig(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.expression = reader.string(); + continue; + } + case 2: { + if (tag !== 16) { + break; + } + + message.ttlMs = longToNumber(reader.int64()); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): IdempotencyConfig { + return { + expression: isSet(object.expression) ? globalThis.String(object.expression) : '', + ttlMs: isSet(object.ttlMs) + ? globalThis.Number(object.ttlMs) + : isSet(object.ttl_ms) + ? globalThis.Number(object.ttl_ms) + : 0, + }; + }, + + toJSON(message: IdempotencyConfig): unknown { + const obj: any = {}; + if (message.expression !== '') { + obj.expression = message.expression; + } + if (message.ttlMs !== 0) { + obj.ttlMs = Math.round(message.ttlMs); + } + return obj; + }, + + create(base?: DeepPartial): IdempotencyConfig { + return IdempotencyConfig.fromPartial(base ?? {}); + }, + fromPartial(object: DeepPartial): IdempotencyConfig { + const message = createBaseIdempotencyConfig(); + message.expression = object.expression ?? ''; + message.ttlMs = object.ttlMs ?? 0; + return message; + }, +}; + +function createBaseIdempotencyCollisionError(): IdempotencyCollisionError { + return { existingRunExternalId: '' }; +} + +export const IdempotencyCollisionError: MessageFns = { + encode( + message: IdempotencyCollisionError, + writer: BinaryWriter = new BinaryWriter() + ): BinaryWriter { + if (message.existingRunExternalId !== '') { + writer.uint32(10).string(message.existingRunExternalId); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): IdempotencyCollisionError { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseIdempotencyCollisionError(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.existingRunExternalId = reader.string(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): IdempotencyCollisionError { + return { + existingRunExternalId: isSet(object.existingRunExternalId) + ? globalThis.String(object.existingRunExternalId) + : isSet(object.existing_run_external_id) + ? globalThis.String(object.existing_run_external_id) + : '', + }; + }, + + toJSON(message: IdempotencyCollisionError): unknown { + const obj: any = {}; + if (message.existingRunExternalId !== '') { + obj.existingRunExternalId = message.existingRunExternalId; + } + return obj; + }, + + create(base?: DeepPartial): IdempotencyCollisionError { + return IdempotencyCollisionError.fromPartial(base ?? {}); + }, + fromPartial(object: DeepPartial): IdempotencyCollisionError { + const message = createBaseIdempotencyCollisionError(); + message.existingRunExternalId = object.existingRunExternalId ?? ''; return message; }, }; diff --git a/sdks/typescript/src/util/errors/idempotency-collision-error.ts b/sdks/typescript/src/util/errors/idempotency-collision-error.ts new file mode 100644 index 0000000000..f64d0a2b0c --- /dev/null +++ b/sdks/typescript/src/util/errors/idempotency-collision-error.ts @@ -0,0 +1,12 @@ +import HatchetError from './hatchet-error'; + +export class IdempotencyCollisionError extends HatchetError { + existingRunExternalId: string; + + constructor(existingRunExternalId: string) { + super(`idempotency key collision: existing run ${existingRunExternalId} already exists`); + this.name = 'IdempotencyCollisionError'; + this.existingRunExternalId = existingRunExternalId; + Object.setPrototypeOf(this, new.target.prototype); + } +} diff --git a/sdks/typescript/src/v1/client/admin.ts b/sdks/typescript/src/v1/client/admin.ts index 87540053cc..94cc3ee6e6 100644 --- a/sdks/typescript/src/v1/client/admin.ts +++ b/sdks/typescript/src/v1/client/admin.ts @@ -1,6 +1,9 @@ import HatchetError from '@util/errors/hatchet-error'; +import { IdempotencyCollisionError } from '@util/errors/idempotency-collision-error'; import { ClientConfig } from '@clients/hatchet-client/client-config'; import WorkflowRunRef from '@hatchet/util/workflow-run-ref'; +import { BinaryReader } from '@bufbuild/protobuf/wire'; +import { IdempotencyCollisionError as IdempotencyCollisionErrorProto } from '@hatchet/protoc/v1/workflows'; import { Priority, RateLimitDuration, RunsClient, WorkerLabelComparator } from '@hatchet/v1'; import { createGrpcClient } from '@hatchet/util/grpc-helpers'; @@ -22,6 +25,52 @@ import { batch } from '@hatchet/util/batch'; import { applyNamespace } from '@hatchet/util/apply-namespace'; import { DesiredWorkerLabels } from '@hatchet-dev/typescript-sdk/protoc/v1/shared/trigger'; +function extractExistingRunIdFromGrpcError(e: any): string { + try { + const detailsBin = e.metadata?.get?.('grpc-status-details-bin'); + const binData = Array.isArray(detailsBin) ? detailsBin[0] : detailsBin; + if (!binData) return ''; + + const statusBytes = binData instanceof Buffer ? binData : Buffer.from(binData); + const reader = new BinaryReader(statusBytes); + + while (reader.pos < reader.len) { + const tag = reader.uint32(); + const fieldNumber = tag >>> 3; + const wireType = tag & 7; + + if (fieldNumber === 3 && wireType === 2) { + // google.rpc.Status.details: repeated google.protobuf.Any + const anyBytes = reader.bytes(); + const anyReader = new BinaryReader(anyBytes); + let typeUrl = ''; + let value: Uint8Array | undefined; + + while (anyReader.pos < anyReader.len) { + const anyTag = anyReader.uint32(); + const anyField = anyTag >>> 3; + if (anyField === 1) { + typeUrl = anyReader.string(); + } else if (anyField === 2) { + value = anyReader.bytes(); + } else { + anyReader.skip(anyTag & 7); + } + } + + if (typeUrl.includes('IdempotencyCollisionError') && value) { + return IdempotencyCollisionErrorProto.decode(value).existingRunExternalId ?? ''; + } + } else { + reader.skip(wireType); + } + } + } catch { + // ignore decoding errors + } + return ''; +} + type DesiredWorkerLabelOpt = { value: string | number; required?: boolean; @@ -180,6 +229,9 @@ export class AdminClient { await ref.getWorkflowRunId(); return ref; } catch (e: any) { + if (e.code === 6) { + throw new IdempotencyCollisionError(extractExistingRunIdFromGrpcError(e)); + } throw new HatchetError(e.message); } } diff --git a/sdks/typescript/src/v1/client/worker/worker-internal.ts b/sdks/typescript/src/v1/client/worker/worker-internal.ts index ffa409fc51..61d5ad83b9 100644 --- a/sdks/typescript/src/v1/client/worker/worker-internal.ts +++ b/sdks/typescript/src/v1/client/worker/worker-internal.ts @@ -421,6 +421,12 @@ export class InternalWorker { expression: f.expression, payload: f.payload ? new TextEncoder().encode(JSON.stringify(f.payload)) : undefined, })) ?? [], + idempotency: workflow.idempotency + ? { + expression: workflow.idempotency.expression, + ttlMs: workflow.idempotency.ttlMs, + } + : undefined, }); this.registeredWorkflowPromises.push(registeredWorkflow); await registeredWorkflow; diff --git a/sdks/typescript/src/v1/declaration.ts b/sdks/typescript/src/v1/declaration.ts index 49ad94baae..3ab88d70c1 100644 --- a/sdks/typescript/src/v1/declaration.ts +++ b/sdks/typescript/src/v1/declaration.ts @@ -25,6 +25,7 @@ import { Concurrency, DurableTaskFn, WorkerLabelComparator, + IdempotencyConfig, } from './task'; import { Duration } from './client/duration'; import { MetricsClient } from './client/features/metrics'; @@ -208,6 +209,12 @@ export type CreateBaseWorkflowOpts = { * can be used on the dashboard for autocomplete. */ inputValidator?: z.ZodType; + + /** + * (optional) idempotency configuration for the workflow. + * Prevents more than one run from occurring for a given key within the TTL window. + */ + idempotency?: IdempotencyConfig; }; export type CreateTaskWorkflowOpts< diff --git a/sdks/typescript/src/v1/examples/idempotency/run.ts b/sdks/typescript/src/v1/examples/idempotency/run.ts new file mode 100644 index 0000000000..bc2372ac16 --- /dev/null +++ b/sdks/typescript/src/v1/examples/idempotency/run.ts @@ -0,0 +1,28 @@ +import { IdempotencyCollisionError } from '@hatchet/v1'; +import { idempotentTask } from './workflow'; + +async function main() { + // > trigger + const ref1 = await idempotentTask.runNoWait({ id: '123' }); + + let runId2: string; + try { + const ref2 = await idempotentTask.runNoWait({ id: '123' }); + runId2 = await ref2.getWorkflowRunId(); + } catch (e) { + if (e instanceof IdempotencyCollisionError) { + console.log( + `Run with external ID ${e.existingRunExternalId} already exists for this idempotency key` + ); + runId2 = e.existingRunExternalId; + } else { + throw e; + } + } + + const res1 = await ref1.result(); + console.log(`Result: ${JSON.stringify(res1)}, run ID: ${runId2}`); + // !! +} + +main().catch(console.error); diff --git a/sdks/typescript/src/v1/examples/idempotency/workflow.ts b/sdks/typescript/src/v1/examples/idempotency/workflow.ts new file mode 100644 index 0000000000..495867ebe5 --- /dev/null +++ b/sdks/typescript/src/v1/examples/idempotency/workflow.ts @@ -0,0 +1,18 @@ +import { hatchet } from '../hatchet-client'; + +export type IdempotencyInput = { + id: string; +}; + +// > idempotency +export const idempotentTask = hatchet.task({ + name: 'idempotent-task', + idempotency: { + expression: 'input.id', + ttlMs: 60_000, + }, + fn: async (input) => { + return { result: `Hello, world from task ${input.id}` }; + }, +}); +// !! diff --git a/sdks/typescript/src/v1/index.ts b/sdks/typescript/src/v1/index.ts index bc351e43c9..2ac9f70e46 100644 --- a/sdks/typescript/src/v1/index.ts +++ b/sdks/typescript/src/v1/index.ts @@ -11,6 +11,7 @@ export * from './slot-types'; export * from '../legacy/legacy-transformer'; export { NonDeterminismError } from '../util/errors/non-determinism-error'; export { EvictionNotSupportedError } from '../util/errors/eviction-not-supported-error'; +export { IdempotencyCollisionError } from '../util/errors/idempotency-collision-error'; export { EvictionPolicy, DEFAULT_DURABLE_TASK_EVICTION_POLICY, diff --git a/sdks/typescript/src/v1/task.ts b/sdks/typescript/src/v1/task.ts index 86c7695ebc..4592fe31a6 100644 --- a/sdks/typescript/src/v1/task.ts +++ b/sdks/typescript/src/v1/task.ts @@ -42,6 +42,23 @@ export type Concurrency = { */ export type TaskConcurrency = Concurrency; +/** + * Configuration for idempotency on a workflow or standalone task. + * Prevents more than one run from occurring within a given time window. + */ +export type IdempotencyConfig = { + /** + * CEL expression to create an idempotency key from input and metadata. + * @example "input.id" // use the 'id' field from input as the key + */ + expression: string; + + /** + * How long the idempotency key should live (in milliseconds). + */ + ttlMs: number; +}; + export class NonRetryableError extends Error { constructor(message?: string) { super(message); From 0bdfc1c2490f776a4e055c81c2efa6413d282f0e Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Wed, 3 Jun 2026 11:28:19 -0400 Subject: [PATCH 52/69] chore: vendor the status proto for js --- frontend/docs/pages/v1/idempotency.mdx | 12 +- hack/proto/vendor/google/rpc/status.proto | 49 ++++ sdks/typescript/generate-protoc.sh | 6 +- .../src/protoc/google/protobuf/any.ts | 258 ++++++++++++++++++ .../src/protoc/google/rpc/status.ts | 159 +++++++++++ sdks/typescript/src/v1/client/admin.ts | 38 +-- 6 files changed, 478 insertions(+), 44 deletions(-) create mode 100644 hack/proto/vendor/google/rpc/status.proto create mode 100644 sdks/typescript/src/protoc/google/protobuf/any.ts create mode 100644 sdks/typescript/src/protoc/google/rpc/status.ts diff --git a/frontend/docs/pages/v1/idempotency.mdx b/frontend/docs/pages/v1/idempotency.mdx index 1854291f32..ed7d4aab57 100644 --- a/frontend/docs/pages/v1/idempotency.mdx +++ b/frontend/docs/pages/v1/idempotency.mdx @@ -17,11 +17,7 @@ Configuring idempotency on a workflow or standalone task requires two parameters - + @@ -42,11 +38,7 @@ When a collision occurs, the engine will reject the workflow run, and, if the ru - + diff --git a/hack/proto/vendor/google/rpc/status.proto b/hack/proto/vendor/google/rpc/status.proto new file mode 100644 index 0000000000..dc14c9438c --- /dev/null +++ b/hack/proto/vendor/google/rpc/status.proto @@ -0,0 +1,49 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.rpc; + +import "google/protobuf/any.proto"; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/rpc/status;status"; +option java_multiple_files = true; +option java_outer_classname = "StatusProto"; +option java_package = "com.google.rpc"; +option objc_class_prefix = "RPC"; + +// The `Status` type defines a logical error model that is suitable for +// different programming environments, including REST APIs and RPC APIs. It is +// used by [gRPC](https://github.com/grpc). Each `Status` message contains +// three pieces of data: error code, error message, and error details. +// +// You can find out more about this error model and how to work with it in the +// [API Design Guide](https://cloud.google.com/apis/design/errors). +message Status { + // The status code, which should be an enum value of + // [google.rpc.Code][google.rpc.Code]. + int32 code = 1; + + // A developer-facing error message, which should be in English. Any + // user-facing error message should be localized and sent in the + // [google.rpc.Status.details][google.rpc.Status.details] field, or localized + // by the client. + string message = 2; + + // A list of messages that carry the error details. There is a common set of + // message types for APIs to use. + repeated google.protobuf.Any details = 3; +} diff --git a/sdks/typescript/generate-protoc.sh b/sdks/typescript/generate-protoc.sh index 39c8ea155f..05243db431 100755 --- a/sdks/typescript/generate-protoc.sh +++ b/sdks/typescript/generate-protoc.sh @@ -4,8 +4,10 @@ OUT_DIR="./src/protoc" if [ -d "./hatchet" ]; then IN_DIR="./hatchet/api-contracts" + VENDOR_DIR="./hatchet/hack/proto/vendor" else IN_DIR="../../api-contracts" + VENDOR_DIR="../../hack/proto/vendor" fi # Generate code @@ -14,6 +16,8 @@ fi --ts_proto_out=$OUT_DIR \ --ts_proto_opt=outputServices=nice-grpc,outputServices=generic-definitions,useExactTypes=false \ --proto_path=$IN_DIR \ - $IN_DIR/**/*.proto + --proto_path=$VENDOR_DIR \ + $IN_DIR/**/*.proto \ + $VENDOR_DIR/google/rpc/status.proto pnpm lint:fix diff --git a/sdks/typescript/src/protoc/google/protobuf/any.ts b/sdks/typescript/src/protoc/google/protobuf/any.ts new file mode 100644 index 0000000000..3bbab777c9 --- /dev/null +++ b/sdks/typescript/src/protoc/google/protobuf/any.ts @@ -0,0 +1,258 @@ +// Code generated by protoc-gen-ts_proto. DO NOT EDIT. +// versions: +// protoc-gen-ts_proto v2.11.6 +// protoc v3.19.1 +// source: google/protobuf/any.proto + +/* eslint-disable */ +import { BinaryReader, BinaryWriter } from '@bufbuild/protobuf/wire'; + +export const protobufPackage = 'google.protobuf'; + +/** + * `Any` contains an arbitrary serialized protocol buffer message along with a + * URL that describes the type of the serialized message. + * + * Protobuf library provides support to pack/unpack Any values in the form + * of utility functions or additional generated methods of the Any type. + * + * Example 1: Pack and unpack a message in C++. + * + * Foo foo = ...; + * Any any; + * any.PackFrom(foo); + * ... + * if (any.UnpackTo(&foo)) { + * ... + * } + * + * Example 2: Pack and unpack a message in Java. + * + * Foo foo = ...; + * Any any = Any.pack(foo); + * ... + * if (any.is(Foo.class)) { + * foo = any.unpack(Foo.class); + * } + * + * Example 3: Pack and unpack a message in Python. + * + * foo = Foo(...) + * any = Any() + * any.Pack(foo) + * ... + * if any.Is(Foo.DESCRIPTOR): + * any.Unpack(foo) + * ... + * + * Example 4: Pack and unpack a message in Go + * + * foo := &pb.Foo{...} + * any, err := anypb.New(foo) + * if err != nil { + * ... + * } + * ... + * foo := &pb.Foo{} + * if err := any.UnmarshalTo(foo); err != nil { + * ... + * } + * + * The pack methods provided by protobuf library will by default use + * 'type.googleapis.com/full.type.name' as the type URL and the unpack + * methods only use the fully qualified type name after the last '/' + * in the type URL, for example "foo.bar.com/x/y.z" will yield type + * name "y.z". + * + * JSON + * ==== + * The JSON representation of an `Any` value uses the regular + * representation of the deserialized, embedded message, with an + * additional field `@type` which contains the type URL. Example: + * + * package google.profile; + * message Person { + * string first_name = 1; + * string last_name = 2; + * } + * + * { + * "@type": "type.googleapis.com/google.profile.Person", + * "firstName": , + * "lastName": + * } + * + * If the embedded message type is well-known and has a custom JSON + * representation, that representation will be embedded adding a field + * `value` which holds the custom JSON in addition to the `@type` + * field. Example (for message [google.protobuf.Duration][]): + * + * { + * "@type": "type.googleapis.com/google.protobuf.Duration", + * "value": "1.212s" + * } + */ +export interface Any { + /** + * A URL/resource name that uniquely identifies the type of the serialized + * protocol buffer message. This string must contain at least + * one "/" character. The last segment of the URL's path must represent + * the fully qualified name of the type (as in + * `path/google.protobuf.Duration`). The name should be in a canonical form + * (e.g., leading "." is not accepted). + * + * In practice, teams usually precompile into the binary all types that they + * expect it to use in the context of Any. However, for URLs which use the + * scheme `http`, `https`, or no scheme, one can optionally set up a type + * server that maps type URLs to message definitions as follows: + * + * * If no scheme is provided, `https` is assumed. + * * An HTTP GET on the URL must yield a [google.protobuf.Type][] + * value in binary format, or produce an error. + * * Applications are allowed to cache lookup results based on the + * URL, or have them precompiled into a binary to avoid any + * lookup. Therefore, binary compatibility needs to be preserved + * on changes to types. (Use versioned type names to manage + * breaking changes.) + * + * Note: this functionality is not currently available in the official + * protobuf release, and it is not used for type URLs beginning with + * type.googleapis.com. + * + * Schemes other than `http`, `https` (or the empty scheme) might be + * used with implementation specific semantics. + */ + typeUrl: string; + /** Must be a valid serialized protocol buffer of the above specified type. */ + value: Uint8Array; +} + +function createBaseAny(): Any { + return { typeUrl: '', value: new Uint8Array(0) }; +} + +export const Any: MessageFns = { + encode(message: Any, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.typeUrl !== '') { + writer.uint32(10).string(message.typeUrl); + } + if (message.value.length !== 0) { + writer.uint32(18).bytes(message.value); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): Any { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseAny(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.typeUrl = reader.string(); + continue; + } + case 2: { + if (tag !== 18) { + break; + } + + message.value = reader.bytes(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): Any { + return { + typeUrl: isSet(object.typeUrl) + ? globalThis.String(object.typeUrl) + : isSet(object.type_url) + ? globalThis.String(object.type_url) + : '', + value: isSet(object.value) ? bytesFromBase64(object.value) : new Uint8Array(0), + }; + }, + + toJSON(message: Any): unknown { + const obj: any = {}; + if (message.typeUrl !== '') { + obj.typeUrl = message.typeUrl; + } + if (message.value.length !== 0) { + obj.value = base64FromBytes(message.value); + } + return obj; + }, + + create(base?: DeepPartial): Any { + return Any.fromPartial(base ?? {}); + }, + fromPartial(object: DeepPartial): Any { + const message = createBaseAny(); + message.typeUrl = object.typeUrl ?? ''; + message.value = object.value ?? new Uint8Array(0); + return message; + }, +}; + +function bytesFromBase64(b64: string): Uint8Array { + if ((globalThis as any).Buffer) { + return Uint8Array.from((globalThis as any).Buffer.from(b64, 'base64')); + } else { + const bin = globalThis.atob(b64); + const arr = new Uint8Array(bin.length); + for (let i = 0; i < bin.length; ++i) { + arr[i] = bin.charCodeAt(i); + } + return arr; + } +} + +function base64FromBytes(arr: Uint8Array): string { + if ((globalThis as any).Buffer) { + return (globalThis as any).Buffer.from(arr).toString('base64'); + } else { + const bin: string[] = []; + arr.forEach((byte) => { + bin.push(globalThis.String.fromCharCode(byte)); + }); + return globalThis.btoa(bin.join('')); + } +} + +type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; + +export type DeepPartial = T extends Builtin + ? T + : T extends globalThis.Array + ? globalThis.Array> + : T extends ReadonlyArray + ? ReadonlyArray> + : T extends {} + ? { [K in keyof T]?: DeepPartial } + : Partial; + +function isSet(value: any): boolean { + return value !== null && value !== undefined; +} + +export interface MessageFns { + encode(message: T, writer?: BinaryWriter): BinaryWriter; + decode(input: BinaryReader | Uint8Array, length?: number): T; + fromJSON(object: any): T; + toJSON(message: T): unknown; + create(base?: DeepPartial): T; + fromPartial(object: DeepPartial): T; +} diff --git a/sdks/typescript/src/protoc/google/rpc/status.ts b/sdks/typescript/src/protoc/google/rpc/status.ts new file mode 100644 index 0000000000..85f43dede6 --- /dev/null +++ b/sdks/typescript/src/protoc/google/rpc/status.ts @@ -0,0 +1,159 @@ +// Code generated by protoc-gen-ts_proto. DO NOT EDIT. +// versions: +// protoc-gen-ts_proto v2.11.6 +// protoc v3.19.1 +// source: google/rpc/status.proto + +/* eslint-disable */ +import { BinaryReader, BinaryWriter } from '@bufbuild/protobuf/wire'; +import { Any } from '../protobuf/any'; + +export const protobufPackage = 'google.rpc'; + +/** + * The `Status` type defines a logical error model that is suitable for + * different programming environments, including REST APIs and RPC APIs. It is + * used by [gRPC](https://github.com/grpc). Each `Status` message contains + * three pieces of data: error code, error message, and error details. + * + * You can find out more about this error model and how to work with it in the + * [API Design Guide](https://cloud.google.com/apis/design/errors). + */ +export interface Status { + /** + * The status code, which should be an enum value of + * [google.rpc.Code][google.rpc.Code]. + */ + code: number; + /** + * A developer-facing error message, which should be in English. Any + * user-facing error message should be localized and sent in the + * [google.rpc.Status.details][google.rpc.Status.details] field, or localized + * by the client. + */ + message: string; + /** + * A list of messages that carry the error details. There is a common set of + * message types for APIs to use. + */ + details: Any[]; +} + +function createBaseStatus(): Status { + return { code: 0, message: '', details: [] }; +} + +export const Status: MessageFns = { + encode(message: Status, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.code !== 0) { + writer.uint32(8).int32(message.code); + } + if (message.message !== '') { + writer.uint32(18).string(message.message); + } + for (const v of message.details) { + Any.encode(v!, writer.uint32(26).fork()).join(); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): Status { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseStatus(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.code = reader.int32(); + continue; + } + case 2: { + if (tag !== 18) { + break; + } + + message.message = reader.string(); + continue; + } + case 3: { + if (tag !== 26) { + break; + } + + message.details.push(Any.decode(reader, reader.uint32())); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): Status { + return { + code: isSet(object.code) ? globalThis.Number(object.code) : 0, + message: isSet(object.message) ? globalThis.String(object.message) : '', + details: globalThis.Array.isArray(object?.details) + ? object.details.map((e: any) => Any.fromJSON(e)) + : [], + }; + }, + + toJSON(message: Status): unknown { + const obj: any = {}; + if (message.code !== 0) { + obj.code = Math.round(message.code); + } + if (message.message !== '') { + obj.message = message.message; + } + if (message.details?.length) { + obj.details = message.details.map((e) => Any.toJSON(e)); + } + return obj; + }, + + create(base?: DeepPartial): Status { + return Status.fromPartial(base ?? {}); + }, + fromPartial(object: DeepPartial): Status { + const message = createBaseStatus(); + message.code = object.code ?? 0; + message.message = object.message ?? ''; + message.details = object.details?.map((e) => Any.fromPartial(e)) || []; + return message; + }, +}; + +type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; + +export type DeepPartial = T extends Builtin + ? T + : T extends globalThis.Array + ? globalThis.Array> + : T extends ReadonlyArray + ? ReadonlyArray> + : T extends {} + ? { [K in keyof T]?: DeepPartial } + : Partial; + +function isSet(value: any): boolean { + return value !== null && value !== undefined; +} + +export interface MessageFns { + encode(message: T, writer?: BinaryWriter): BinaryWriter; + decode(input: BinaryReader | Uint8Array, length?: number): T; + fromJSON(object: any): T; + toJSON(message: T): unknown; + create(base?: DeepPartial): T; + fromPartial(object: DeepPartial): T; +} diff --git a/sdks/typescript/src/v1/client/admin.ts b/sdks/typescript/src/v1/client/admin.ts index 94cc3ee6e6..b8a2892c88 100644 --- a/sdks/typescript/src/v1/client/admin.ts +++ b/sdks/typescript/src/v1/client/admin.ts @@ -2,7 +2,7 @@ import HatchetError from '@util/errors/hatchet-error'; import { IdempotencyCollisionError } from '@util/errors/idempotency-collision-error'; import { ClientConfig } from '@clients/hatchet-client/client-config'; import WorkflowRunRef from '@hatchet/util/workflow-run-ref'; -import { BinaryReader } from '@bufbuild/protobuf/wire'; +import { Status } from '@hatchet/protoc/google/rpc/status'; import { IdempotencyCollisionError as IdempotencyCollisionErrorProto } from '@hatchet/protoc/v1/workflows'; import { Priority, RateLimitDuration, RunsClient, WorkerLabelComparator } from '@hatchet/v1'; @@ -31,38 +31,10 @@ function extractExistingRunIdFromGrpcError(e: any): string { const binData = Array.isArray(detailsBin) ? detailsBin[0] : detailsBin; if (!binData) return ''; - const statusBytes = binData instanceof Buffer ? binData : Buffer.from(binData); - const reader = new BinaryReader(statusBytes); - - while (reader.pos < reader.len) { - const tag = reader.uint32(); - const fieldNumber = tag >>> 3; - const wireType = tag & 7; - - if (fieldNumber === 3 && wireType === 2) { - // google.rpc.Status.details: repeated google.protobuf.Any - const anyBytes = reader.bytes(); - const anyReader = new BinaryReader(anyBytes); - let typeUrl = ''; - let value: Uint8Array | undefined; - - while (anyReader.pos < anyReader.len) { - const anyTag = anyReader.uint32(); - const anyField = anyTag >>> 3; - if (anyField === 1) { - typeUrl = anyReader.string(); - } else if (anyField === 2) { - value = anyReader.bytes(); - } else { - anyReader.skip(anyTag & 7); - } - } - - if (typeUrl.includes('IdempotencyCollisionError') && value) { - return IdempotencyCollisionErrorProto.decode(value).existingRunExternalId ?? ''; - } - } else { - reader.skip(wireType); + const status = Status.decode(binData instanceof Buffer ? binData : Buffer.from(binData)); + for (const detail of status.details) { + if (detail.typeUrl.includes('IdempotencyCollisionError')) { + return IdempotencyCollisionErrorProto.decode(detail.value).existingRunExternalId ?? ''; } } } catch { From de07a7840c8ae16ec43c06dd920392aa82e075e4 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Wed, 3 Jun 2026 11:32:21 -0400 Subject: [PATCH 53/69] fix: go docs --- examples/go/idempotency/trigger.go | 59 +++++++++++++++++++ examples/go/idempotency/worker.go | 32 ++++++++++ .../{trigger/main.go => trigger.go} | 37 +++++------- .../idempotency/{worker/main.go => worker.go} | 23 -------- 4 files changed, 107 insertions(+), 44 deletions(-) create mode 100644 examples/go/idempotency/trigger.go create mode 100644 examples/go/idempotency/worker.go rename sdks/go/examples/idempotency/{trigger/main.go => trigger.go} (65%) rename sdks/go/examples/idempotency/{worker/main.go => worker.go} (55%) diff --git a/examples/go/idempotency/trigger.go b/examples/go/idempotency/trigger.go new file mode 100644 index 0000000000..9140a21a7d --- /dev/null +++ b/examples/go/idempotency/trigger.go @@ -0,0 +1,59 @@ +package main + +import ( + "context" + "fmt" + "log" + + "github.com/hatchet-dev/hatchet/pkg/cmdutils" + hatchet "github.com/hatchet-dev/hatchet/sdks/go" +) + +func main() { + client, err := hatchet.NewClient() + if err != nil { + log.Fatalf("failed to create hatchet client: %v", err) + } + + idempotentTask := IdempotentTask(client) + + worker, err := client.NewWorker("idempotency-worker", hatchet.WithWorkflows(idempotentTask)) + if err != nil { + log.Fatalf("failed to create worker: %v", err) + } + + interruptCtx, cancel := cmdutils.NewInterruptContext() + defer cancel() + + go func() { + if err := worker.StartBlocking(interruptCtx); err != nil { + log.Fatalf("failed to start worker: %v", err) + } + }() + + ctx := context.Background() + + // > trigger + ref1, err := idempotentTask.RunNoWait(ctx, IdempotencyInput{ID: "123"}) + if err != nil { + log.Fatalf("unexpected error on first run: %v", err) + } + + ref2, err := idempotentTask.RunNoWait(ctx, IdempotencyInput{ID: "123"}) + + var runID2 string + + if err != nil { + if idempErr, ok := hatchet.IsIdempotencyCollisionError(err); ok { + fmt.Printf("Run %s already exists for this idempotency key\n", idempErr.ExistingRunExternalId) + runID2 = idempErr.ExistingRunExternalId + } else { + log.Fatalf("unexpected error on second run: %v", err) + } + } else { + runID2 = ref2.RunId + } + + fmt.Printf("First run ID: %s\n", ref1.RunId) + fmt.Printf("Second run ID: %s\n", runID2) +} diff --git a/examples/go/idempotency/worker.go b/examples/go/idempotency/worker.go new file mode 100644 index 0000000000..4d8f4d7c61 --- /dev/null +++ b/examples/go/idempotency/worker.go @@ -0,0 +1,32 @@ +package main + +import ( + "fmt" + "time" + + hatchet "github.com/hatchet-dev/hatchet/sdks/go" +) + +type IdempotencyInput struct { + ID string `json:"id"` +} + +type IdempotencyOutput struct { + Result string `json:"result"` +} + +// > idempotency +func IdempotentTask(client *hatchet.Client) *hatchet.StandaloneTask { + return client.NewStandaloneTask( + "idempotent-task", + func(ctx hatchet.Context, input IdempotencyInput) (*IdempotencyOutput, error) { + return &IdempotencyOutput{ + Result: fmt.Sprintf("Hello, world from task %s", input.ID), + }, nil + }, + hatchet.WithWorkflowIdempotency(hatchet.IdempotencyConfig{ + Expression: "input.id", + TTL: time.Minute, + }), + ) +} diff --git a/sdks/go/examples/idempotency/trigger/main.go b/sdks/go/examples/idempotency/trigger.go similarity index 65% rename from sdks/go/examples/idempotency/trigger/main.go rename to sdks/go/examples/idempotency/trigger.go index 09ee90a477..2afe30fa42 100644 --- a/sdks/go/examples/idempotency/trigger/main.go +++ b/sdks/go/examples/idempotency/trigger.go @@ -4,37 +4,32 @@ import ( "context" "fmt" "log" - "time" + "github.com/hatchet-dev/hatchet/pkg/cmdutils" hatchet "github.com/hatchet-dev/hatchet/sdks/go" ) -type IdempotencyInput struct { - ID string `json:"id"` -} - -type IdempotencyOutput struct { - Result string `json:"result"` -} - func main() { client, err := hatchet.NewClient() if err != nil { log.Fatalf("failed to create hatchet client: %v", err) } - idempotentTask := client.NewStandaloneTask( - "idempotent-task", - func(ctx hatchet.Context, input IdempotencyInput) (*IdempotencyOutput, error) { - return &IdempotencyOutput{ - Result: fmt.Sprintf("Hello, world from task %s", input.ID), - }, nil - }, - hatchet.WithWorkflowIdempotency(hatchet.IdempotencyConfig{ - Expression: "input.id", - TTL: time.Minute, - }), - ) + idempotentTask := IdempotentTask(client) + + worker, err := client.NewWorker("idempotency-worker", hatchet.WithWorkflows(idempotentTask)) + if err != nil { + log.Fatalf("failed to create worker: %v", err) + } + + interruptCtx, cancel := cmdutils.NewInterruptContext() + defer cancel() + + go func() { + if err := worker.StartBlocking(interruptCtx); err != nil { + log.Fatalf("failed to start worker: %v", err) + } + }() ctx := context.Background() diff --git a/sdks/go/examples/idempotency/worker/main.go b/sdks/go/examples/idempotency/worker.go similarity index 55% rename from sdks/go/examples/idempotency/worker/main.go rename to sdks/go/examples/idempotency/worker.go index 1cf35e1aeb..030e6af6c0 100644 --- a/sdks/go/examples/idempotency/worker/main.go +++ b/sdks/go/examples/idempotency/worker.go @@ -2,10 +2,8 @@ package main import ( "fmt" - "log" "time" - "github.com/hatchet-dev/hatchet/pkg/cmdutils" hatchet "github.com/hatchet-dev/hatchet/sdks/go" ) @@ -34,24 +32,3 @@ func IdempotentTask(client *hatchet.Client) *hatchet.StandaloneTask { } // !! - -func main() { - client, err := hatchet.NewClient() - if err != nil { - log.Fatalf("failed to create hatchet client: %v", err) - } - - worker, err := client.NewWorker("idempotency-worker", - hatchet.WithWorkflows(IdempotentTask(client)), - ) - if err != nil { - log.Fatalf("failed to create worker: %v", err) - } - - interruptCtx, cancel := cmdutils.NewInterruptContext() - defer cancel() - - if err := worker.StartBlocking(interruptCtx); err != nil { - log.Fatalf("failed to start worker: %v", err) - } -} From 8ec5334811622ec6b47b779c91d9b4714f6b891b Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Wed, 3 Jun 2026 11:38:14 -0400 Subject: [PATCH 54/69] fix: ruby simplification --- sdks/ruby/src/Gemfile.lock | 1 + sdks/ruby/src/hatchet-sdk.gemspec | 1 + .../src/lib/hatchet/clients/grpc/admin.rb | 112 ++---------------- sdks/typescript/src/v1/client/admin.ts | 20 ++-- 4 files changed, 24 insertions(+), 110 deletions(-) diff --git a/sdks/ruby/src/Gemfile.lock b/sdks/ruby/src/Gemfile.lock index 481595e45e..d78a9b4cfb 100644 --- a/sdks/ruby/src/Gemfile.lock +++ b/sdks/ruby/src/Gemfile.lock @@ -6,6 +6,7 @@ PATH faraday (~> 2.0) faraday-multipart google-protobuf (~> 4.0) + googleapis-common-protos-types (~> 1.0) grpc (~> 1.60) json (~> 2.0) marcel diff --git a/sdks/ruby/src/hatchet-sdk.gemspec b/sdks/ruby/src/hatchet-sdk.gemspec index 09692d52e2..46246efb89 100644 --- a/sdks/ruby/src/hatchet-sdk.gemspec +++ b/sdks/ruby/src/hatchet-sdk.gemspec @@ -58,6 +58,7 @@ Gem::Specification.new do |spec| # Runtime dependencies for gRPC spec.add_dependency "concurrent-ruby", ">= 1.1" spec.add_dependency "google-protobuf", "~> 4.0" + spec.add_dependency "googleapis-common-protos-types", "~> 1.0" spec.add_dependency "grpc", "~> 1.60" # Development dependencies diff --git a/sdks/ruby/src/lib/hatchet/clients/grpc/admin.rb b/sdks/ruby/src/lib/hatchet/clients/grpc/admin.rb index 94aabfffd8..f2b5fcdd39 100644 --- a/sdks/ruby/src/lib/hatchet/clients/grpc/admin.rb +++ b/sdks/ruby/src/lib/hatchet/clients/grpc/admin.rb @@ -2,6 +2,7 @@ require "json" require "google/protobuf/timestamp_pb" +require "google/rpc/status_pb" module Hatchet module Clients @@ -93,11 +94,8 @@ def trigger_workflow(workflow_name, input: {}, options: {}) response = @v0_stub.trigger_workflow(request, metadata: @config.auth_metadata) response.workflow_run_id rescue ::GRPC::AlreadyExists => e - if e.message.include?("idempotency key collision") - run_id = extract_idempotency_run_id(e) - raise IdempotencyCollisionError, run_id - end - raise DedupeViolationError, "Deduplication violation: #{e.message}" + run_id = extract_idempotency_run_id(e) + raise(run_id ? IdempotencyCollisionError.new(run_id) : DedupeViolationError, "Deduplication violation: #{e.message}") rescue ::GRPC::ResourceExhausted => e raise ResourceExhaustedError, e.message rescue ::GRPC::BadStatus => e @@ -288,109 +286,19 @@ def build_trigger_worker_labels(labels) end end - # Parse the grpc-status-details-bin binary protobuf from a gRPC error to - # extract the existing run ID from a V1::IdempotencyCollisionError detail. def extract_idempotency_run_id(grpc_error) status_bin = grpc_error.to_status.metadata&.[]("grpc-status-details-bin") - return "" unless status_bin + return nil unless status_bin - parse_idempotency_run_id(status_bin.b) - rescue StandardError - "" - end + rpc_status = Google::Rpc::Status.decode(status_bin.b) + rpc_status.details.each do |any| + next unless any.type_url.include?("IdempotencyCollisionError") - # Parse the binary google.rpc.Status proto to find an IdempotencyCollisionError - # detail and return the existing_run_external_id field value. - # - # google.rpc.Status layout (proto3): - # field 1 (int32) = code - # field 2 (string) = message - # field 3 (message, repeated) = details <- we want these - # - # Each detail is google.protobuf.Any: - # field 1 (string) = type_url - # field 2 (bytes) = value - def parse_idempotency_run_id(data) - pos = 0 - - while pos < data.bytesize - tag, pos = read_varint(data, pos) - field_num = tag >> 3 - wire_type = tag & 7 - - if field_num == 3 && wire_type == 2 - len, pos = read_varint(data, pos) - any_data = data.byteslice(pos, len) - pos += len - - type_url, value = parse_any_message(any_data) - if type_url&.include?("IdempotencyCollisionError") && value - return ::V1::IdempotencyCollisionError.decode(value).existing_run_external_id - end - else - pos = skip_field(data, pos, wire_type) - end + return ::V1::IdempotencyCollisionError.decode(any.value).existing_run_external_id end - - "" + nil rescue StandardError - "" - end - - def parse_any_message(data) - type_url = nil - value = nil - pos = 0 - - while pos < data.bytesize - tag, pos = read_varint(data, pos) - field_num = tag >> 3 - wire_type = tag & 7 - - if wire_type == 2 - len, pos = read_varint(data, pos) - field_data = data.byteslice(pos, len) - type_url = field_data if field_num == 1 - value = field_data if field_num == 2 - else - pos = skip_field(data, pos, wire_type) - end - end - - [type_url, value] - end - - def read_varint(data, pos) - result = 0 - shift = 0 - loop do - byte = data.getbyte(pos) - pos += 1 - result |= (byte & 0x7F) << shift - break if byte.nobits?(0x80) - - shift += 7 - end - [result, pos] - end - - def skip_field(data, pos, wire_type) - case wire_type - when 0 - loop do - byte = data.getbyte(pos) - pos += 1 - break if byte.nobits?(0x80) - end - when 1 - pos += 8 - when 2 - len, pos = read_varint(data, pos) - pos += len - when 5 - pos += 4 - end - pos + nil end def ensure_connected! diff --git a/sdks/typescript/src/v1/client/admin.ts b/sdks/typescript/src/v1/client/admin.ts index b8a2892c88..4068eb62f6 100644 --- a/sdks/typescript/src/v1/client/admin.ts +++ b/sdks/typescript/src/v1/client/admin.ts @@ -2,7 +2,8 @@ import HatchetError from '@util/errors/hatchet-error'; import { IdempotencyCollisionError } from '@util/errors/idempotency-collision-error'; import { ClientConfig } from '@clients/hatchet-client/client-config'; import WorkflowRunRef from '@hatchet/util/workflow-run-ref'; -import { Status } from '@hatchet/protoc/google/rpc/status'; +import { ServiceError, status as GrpcStatus } from '@grpc/grpc-js'; +import { Status as RpcStatus } from '@hatchet/protoc/google/rpc/status'; import { IdempotencyCollisionError as IdempotencyCollisionErrorProto } from '@hatchet/protoc/v1/workflows'; import { Priority, RateLimitDuration, RunsClient, WorkerLabelComparator } from '@hatchet/v1'; @@ -25,13 +26,16 @@ import { batch } from '@hatchet/util/batch'; import { applyNamespace } from '@hatchet/util/apply-namespace'; import { DesiredWorkerLabels } from '@hatchet-dev/typescript-sdk/protoc/v1/shared/trigger'; -function extractExistingRunIdFromGrpcError(e: any): string { +function isGrpcServiceError(e: unknown): e is ServiceError { + return e instanceof Error && 'code' in e && 'metadata' in e; +} + +function extractExistingRunIdFromGrpcError(e: ServiceError): string { try { - const detailsBin = e.metadata?.get?.('grpc-status-details-bin'); - const binData = Array.isArray(detailsBin) ? detailsBin[0] : detailsBin; + const [binData] = e.metadata.get('grpc-status-details-bin'); if (!binData) return ''; - const status = Status.decode(binData instanceof Buffer ? binData : Buffer.from(binData)); + const status = RpcStatus.decode(binData instanceof Buffer ? binData : Buffer.from(binData)); for (const detail of status.details) { if (detail.typeUrl.includes('IdempotencyCollisionError')) { return IdempotencyCollisionErrorProto.decode(detail.value).existingRunExternalId ?? ''; @@ -200,11 +204,11 @@ export class AdminClient { ); await ref.getWorkflowRunId(); return ref; - } catch (e: any) { - if (e.code === 6) { + } catch (e: unknown) { + if (isGrpcServiceError(e) && e.code === GrpcStatus.ALREADY_EXISTS) { throw new IdempotencyCollisionError(extractExistingRunIdFromGrpcError(e)); } - throw new HatchetError(e.message); + throw new HatchetError(e instanceof Error ? e.message : String(e)); } } From a9ae5486109cbf2066adf53d59a135e85b7c8bc5 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Wed, 3 Jun 2026 11:41:52 -0400 Subject: [PATCH 55/69] chore: changelogs --- sdks/ruby/src/CHANGELOG.md | 6 ++++++ sdks/ruby/src/lib/hatchet/version.rb | 2 +- sdks/typescript/CHANGELOG.md | 6 ++++++ sdks/typescript/package.json | 2 +- 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/sdks/ruby/src/CHANGELOG.md b/sdks/ruby/src/CHANGELOG.md index 99af52c0b1..f96698f657 100644 --- a/sdks/ruby/src/CHANGELOG.md +++ b/sdks/ruby/src/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to Hatchet's Ruby SDK will be documented in this changelog. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.4.0] - 2026-06-03 + +### Added + +- Adds support for defining **idempotency keys** on workflows and standalone tasks via an `idempotency` option, which ensures that they're only run once in a provided time window, based on a CEL expression. Triggers that collide with an existing run raise an `IdempotencyCollisionError` containing the existing run's ID. + ## [0.3.0] - 2026-04-28 ### Added diff --git a/sdks/ruby/src/lib/hatchet/version.rb b/sdks/ruby/src/lib/hatchet/version.rb index 57c98949e2..c09c6fb6e2 100644 --- a/sdks/ruby/src/lib/hatchet/version.rb +++ b/sdks/ruby/src/lib/hatchet/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Hatchet - VERSION = "0.3.0" + VERSION = "0.4.0" end diff --git a/sdks/typescript/CHANGELOG.md b/sdks/typescript/CHANGELOG.md index c8b618f004..d7574ba670 100644 --- a/sdks/typescript/CHANGELOG.md +++ b/sdks/typescript/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to Hatchet's TypeScript SDK will be documented in this chang The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.24.0] - 2026-06-03 + +### Added + +- Adds support for defining **idempotency keys** on workflows and standalone tasks via an `idempotency` option, which ensures that they're only run once in a provided time window, based on a CEL expression. Triggers that collide with an existing run throw an `IdempotencyCollisionError` containing the existing run's ID. + ## [1.23.0] - 2026-05-27 ### Added diff --git a/sdks/typescript/package.json b/sdks/typescript/package.json index 3a2e29db9b..ce053a79c0 100644 --- a/sdks/typescript/package.json +++ b/sdks/typescript/package.json @@ -1,6 +1,6 @@ { "name": "@hatchet-dev/typescript-sdk", - "version": "1.23.0", + "version": "1.24.0", "description": "Background task orchestration & visibility for developers", "types": "dist/index.d.ts", "files": [ From cf744a9c11d352ca3a79111742c9b111d9f031fa Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Wed, 3 Jun 2026 11:47:59 -0400 Subject: [PATCH 56/69] fix: ruby ci --- sdks/ruby/src/Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdks/ruby/src/Gemfile.lock b/sdks/ruby/src/Gemfile.lock index d78a9b4cfb..a34f0168e7 100644 --- a/sdks/ruby/src/Gemfile.lock +++ b/sdks/ruby/src/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - hatchet-sdk (0.3.0) + hatchet-sdk (0.4.0) concurrent-ruby (>= 1.1) faraday (~> 2.0) faraday-multipart From 4196bae67b1cf4260fc5be7c2192958d077d95cb Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Wed, 3 Jun 2026 15:43:06 -0400 Subject: [PATCH 57/69] fix: more test fixes --- sdks/go/e2e/idempotency_test.go | 288 ++++++++++++++++++ sdks/ruby/examples/Gemfile.lock | 3 +- .../idempotency/test_idempotency_spec.rb | 133 ++++++++ sdks/ruby/examples/idempotency/worker.rb | 14 +- sdks/typescript/src/v1/examples/e2e-worker.ts | 3 + .../examples/idempotency/idempotency.e2e.ts | 146 +++++++++ .../src/v1/examples/idempotency/workflow.ts | 16 +- 7 files changed, 598 insertions(+), 5 deletions(-) create mode 100644 sdks/go/e2e/idempotency_test.go create mode 100644 sdks/ruby/examples/idempotency/test_idempotency_spec.rb create mode 100644 sdks/typescript/src/v1/examples/idempotency/idempotency.e2e.ts diff --git a/sdks/go/e2e/idempotency_test.go b/sdks/go/e2e/idempotency_test.go new file mode 100644 index 0000000000..95e0457520 --- /dev/null +++ b/sdks/go/e2e/idempotency_test.go @@ -0,0 +1,288 @@ +//go:build e2e + +package e2e + +import ( + "context" + "fmt" + "sync" + "testing" + "time" + + "github.com/google/uuid" + legacyclient "github.com/hatchet-dev/hatchet/pkg/client" + "github.com/hatchet-dev/hatchet/pkg/client/rest" + hatchet "github.com/hatchet-dev/hatchet/sdks/go" + "github.com/stretchr/testify/require" +) + +const ( + idempotencyEventKey = "go-e2e:idempotency-example" + idempotentTaskName = "go-e2e-idempotent-task" + idempotentShortWindowName = "go-e2e-idempotent-task-short-window" +) + +type idempotencyInput struct { + ID string `json:"id"` +} + +var ( + idempotencySetupOnce sync.Once + idempotencySetupErr error + idempotencyLegacyClient legacyclient.Client + idempotentTask *hatchet.StandaloneTask + idempotentTaskShortWindow *hatchet.StandaloneTask +) + +func setupIdempotencyWorker(t *testing.T) (legacyclient.Client, *hatchet.StandaloneTask, *hatchet.StandaloneTask) { + t.Helper() + + idempotencySetupOnce.Do(func() { + idempotencyLegacyClient, idempotencySetupErr = legacyclient.New() + if idempotencySetupErr != nil { + return + } + + idempotentTask = sharedClient.NewStandaloneTask( + idempotentTaskName, + func(ctx hatchet.Context, input idempotencyInput) (map[string]string, error) { + return map[string]string{"result": fmt.Sprintf("Hello, world from task %s", input.ID)}, nil + }, + hatchet.WithWorkflowIdempotency(hatchet.IdempotencyConfig{ + Expression: "input.id", + TTL: time.Minute, + }), + hatchet.WithWorkflowEvents(idempotencyEventKey), + ) + + idempotentTaskShortWindow = sharedClient.NewStandaloneTask( + idempotentShortWindowName, + func(ctx hatchet.Context, input idempotencyInput) (map[string]string, error) { + return map[string]string{"result": fmt.Sprintf("Hello, world from task %s", input.ID)}, nil + }, + hatchet.WithWorkflowIdempotency(hatchet.IdempotencyConfig{ + Expression: "input.id", + TTL: 2 * time.Second, + }), + ) + + worker, err := sharedClient.NewWorker( + "e2e-idempotency-worker", + hatchet.WithWorkflows(idempotentTask, idempotentTaskShortWindow), + ) + if err != nil { + idempotencySetupErr = err + return + } + + _, idempotencySetupErr = worker.Start() + if idempotencySetupErr != nil { + return + } + + ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) + defer cancel() + + pollUntil(t, ctx, func() (bool, error) { + if _, err := sharedClient.Workflows().Get(ctx, idempotentTask.GetName()); err != nil { + return false, nil + } + if _, err := sharedClient.Workflows().Get(ctx, idempotentTaskShortWindow.GetName()); err != nil { + return false, nil + } + + return true, nil + }) + }) + + require.NoError(t, idempotencySetupErr) + + return idempotencyLegacyClient, idempotentTask, idempotentTaskShortWindow +} + +func listRunsByTestRunID(ctx context.Context, testRunID string) (*rest.V1TaskSummaryList, error) { + limit := int64(20) + metadata := []string{"test_run_id:" + testRunID} + + return sharedClient.Runs().List(ctx, rest.V1WorkflowRunListParams{ + Since: time.Now().Add(-5 * time.Minute), + Limit: &limit, + AdditionalMetadata: &metadata, + OnlyTasks: false, + }) +} + +func listEventsByTestRunID(ctx context.Context, legacy legacyclient.Client, testRunID string) (*rest.V1EventList, error) { + limit := int64(10) + metadata := []string{"test_run_id:" + testRunID} + + resp, err := legacy.API().V1EventListWithResponse( + ctx, + uuid.MustParse(legacy.TenantId()), + &rest.V1EventListParams{ + Since: &[]time.Time{time.Now().Add(-5 * time.Minute)}[0], + Limit: &limit, + AdditionalMetadata: &metadata, + }, + ) + if err != nil { + return nil, err + } + if resp.JSON200 == nil { + return nil, fmt.Errorf("unexpected event list status %d", resp.StatusCode()) + } + + return resp.JSON200, nil +} + +func TestIdempotencyDirectTrigger(t *testing.T) { + _, idempotentTask, _ := setupIdempotencyWorker(t) + ctx := newTestContext(t) + testRunID := uniqueID() + + ref1, err := idempotentTask.RunNoWait( + ctx, + idempotencyInput{ID: testRunID}, + hatchet.WithRunMetadata(map[string]string{"test_run_id": testRunID}), + ) + require.NoError(t, err) + + _, err = idempotentTask.RunNoWait(ctx, idempotencyInput{ID: testRunID}) + idempErr, ok := hatchet.IsIdempotencyCollisionError(err) + require.True(t, ok) + require.Equal(t, ref1.RunId, idempErr.ExistingRunExternalId) + + var runs *rest.V1TaskSummaryList + pollUntil(t, ctx, func() (bool, error) { + var pollErr error + runs, pollErr = listRunsByTestRunID(ctx, testRunID) + if pollErr != nil { + return false, pollErr + } + return len(runs.Rows) == 1, nil + }) + + require.NotNil(t, runs) + require.Len(t, runs.Rows, 1) + require.Equal(t, ref1.RunId, runs.Rows[0].Metadata.Id) +} + +func TestIdempotencyShortWindow(t *testing.T) { + _, _, idempotentTaskShortWindow := setupIdempotencyWorker(t) + ctx := newTestContext(t) + testRunID := uniqueID() + + for i := 0; i < 4; i++ { + if i == 1 { + _, err := idempotentTaskShortWindow.RunNoWait( + ctx, + idempotencyInput{ID: testRunID}, + hatchet.WithRunMetadata(map[string]string{"test_run_id": testRunID}), + ) + idempErr, ok := hatchet.IsIdempotencyCollisionError(err) + require.True(t, ok) + require.NotEmpty(t, idempErr.ExistingRunExternalId) + } else { + _, err := idempotentTaskShortWindow.RunNoWait( + ctx, + idempotencyInput{ID: testRunID}, + hatchet.WithRunMetadata(map[string]string{"test_run_id": testRunID}), + ) + require.NoError(t, err) + } + + if i != 3 { + time.Sleep(time.Duration(float64(i)*float64(time.Second) + 1.5*float64(time.Second))) + } + } + + var runs *rest.V1TaskSummaryList + pollUntil(t, ctx, func() (bool, error) { + var pollErr error + runs, pollErr = listRunsByTestRunID(ctx, testRunID) + if pollErr != nil { + return false, pollErr + } + return len(runs.Rows) >= 3, nil + }) + + require.NotNil(t, runs) + require.Len(t, runs.Rows, 3) +} + +func TestIdempotencyEventTrigger(t *testing.T) { + legacy, _, _ := setupIdempotencyWorker(t) + ctx := newTestContext(t) + testRunID := uniqueID() + + err := sharedClient.Events().Push( + ctx, + idempotencyEventKey, + idempotencyInput{ID: testRunID}, + legacyclient.WithEventMetadata(map[string]string{"test_run_id": testRunID}), + ) + require.NoError(t, err) + err = sharedClient.Events().Push( + ctx, + idempotencyEventKey, + idempotencyInput{ID: testRunID}, + legacyclient.WithEventMetadata(map[string]string{"test_run_id": testRunID}), + ) + require.NoError(t, err) + + var runs *rest.V1TaskSummaryList + pollUntil(t, ctx, func() (bool, error) { + var pollErr error + runs, pollErr = listRunsByTestRunID(ctx, testRunID) + if pollErr != nil { + return false, pollErr + } + return len(runs.Rows) == 1, nil + }) + + require.NotNil(t, runs) + require.Len(t, runs.Rows, 1) + + var events *rest.V1EventList + pollUntil(t, ctx, func() (bool, error) { + var pollErr error + events, pollErr = listEventsByTestRunID(ctx, legacy, testRunID) + if pollErr != nil { + return false, pollErr + } + return events.Rows != nil && len(*events.Rows) == 2, nil + }) + + require.NotNil(t, events) + require.Len(t, *events.Rows, 2) + + triggeredRunIDs := make(map[string]struct{}) + for _, event := range *events.Rows { + for _, triggeredRun := range derefTriggeredRuns(event.TriggeredRuns) { + triggeredRunIDs[triggeredRun.WorkflowRunId.String()] = struct{}{} + } + } + + require.Len(t, triggeredRunIDs, 1) + + var triggeredRunID string + for runID := range triggeredRunIDs { + triggeredRunID = runID + } + + pollUntil(t, ctx, func() (bool, error) { + status, pollErr := sharedClient.Runs().GetStatus(ctx, triggeredRunID) + if pollErr != nil { + return false, pollErr + } + return *status == rest.V1TaskStatusCOMPLETED, nil + }) +} + +func derefTriggeredRuns(triggeredRuns *[]rest.V1EventTriggeredRun) []rest.V1EventTriggeredRun { + if triggeredRuns == nil { + return nil + } + + return *triggeredRuns +} diff --git a/sdks/ruby/examples/Gemfile.lock b/sdks/ruby/examples/Gemfile.lock index 7ba97e6dd7..99ec4871bf 100644 --- a/sdks/ruby/examples/Gemfile.lock +++ b/sdks/ruby/examples/Gemfile.lock @@ -1,11 +1,12 @@ PATH remote: ../src specs: - hatchet-sdk (0.3.0) + hatchet-sdk (0.4.0) concurrent-ruby (>= 1.1) faraday (~> 2.0) faraday-multipart google-protobuf (~> 4.0) + googleapis-common-protos-types (~> 1.0) grpc (~> 1.60) json (~> 2.0) marcel diff --git a/sdks/ruby/examples/idempotency/test_idempotency_spec.rb b/sdks/ruby/examples/idempotency/test_idempotency_spec.rb new file mode 100644 index 0000000000..3dcc772aa9 --- /dev/null +++ b/sdks/ruby/examples/idempotency/test_idempotency_spec.rb @@ -0,0 +1,133 @@ +# frozen_string_literal: true + +require "securerandom" +require_relative "../spec_helper" +require_relative "../worker_fixture" +require_relative "worker" + +RSpec.describe "Idempotency" do + around do |example| + HatchetWorkerFixture.with_worker( + ["bundle", "exec", "ruby", File.expand_path("worker.rb", __dir__)], + healthcheck_port: 8005 + ) do + example.run + end + end + + def wait_for_runs(test_run_id, min_runs:) + runs = nil + + 15.times do + runs = hatchet.runs.list( + since: Time.now - (5 * 60), + additional_metadata: { "test_run_id" => test_run_id }, + limit: 20 + ) + + break if runs.rows.length >= min_runs + + sleep 1 + end + + runs + end + + it "prevents duplicate direct triggers" do + test_run_id = SecureRandom.uuid + ref1 = IDEMPOTENT_TASK.run_no_wait( + { "id" => test_run_id }, + options: Hatchet::TriggerWorkflowOptions.new( + additional_metadata: { "test_run_id" => test_run_id } + ) + ) + + collision = nil + + expect do + IDEMPOTENT_TASK.run_no_wait({ "id" => test_run_id }) + end.to raise_error(Hatchet::IdempotencyCollisionError) { |error| collision = error } + + expect(collision&.existing_run_external_id).to eq(ref1.workflow_run_id) + + runs = wait_for_runs(test_run_id, min_runs: 1) + + expect(runs).not_to be_nil + expect(runs.rows.length).to eq(1) + expect(runs.rows[0].metadata.id).to eq(ref1.workflow_run_id) + end + + it "allows reruns after the short idempotency window expires" do + test_run_id = SecureRandom.uuid + + 4.times do |i| + if i == 1 + expect do + IDEMPOTENT_TASK_SHORT_WINDOW.run_no_wait( + { "id" => test_run_id }, + options: Hatchet::TriggerWorkflowOptions.new( + additional_metadata: { "test_run_id" => test_run_id } + ) + ) + end.to raise_error(Hatchet::IdempotencyCollisionError) { |error| expect(error.existing_run_external_id).not_to be_nil } + else + IDEMPOTENT_TASK_SHORT_WINDOW.run_no_wait( + { "id" => test_run_id }, + options: Hatchet::TriggerWorkflowOptions.new( + additional_metadata: { "test_run_id" => test_run_id } + ) + ) + end + + sleep(i + 1.5) unless i == 3 + end + + runs = wait_for_runs(test_run_id, min_runs: 3) + + expect(runs.rows.length).to eq(3) + end + + it "deduplicates event-triggered runs" do + test_run_id = SecureRandom.uuid + e1 = hatchet.events.push( + EVENT_KEY, + { "id" => test_run_id }, + additional_metadata: { "test_run_id" => test_run_id } + ) + e2 = hatchet.events.push( + EVENT_KEY, + { "id" => test_run_id }, + additional_metadata: { "test_run_id" => test_run_id } + ) + + runs = wait_for_runs(test_run_id, min_runs: 1) + + expect(runs).not_to be_nil + expect(runs.rows.length).to eq(1) + + details = nil + 15.times do + details = hatchet.events.list(event_ids: [e1.event_id, e2.event_id]) + break if details.rows.length == 2 + + sleep 1 + end + + expect(details).not_to be_nil + expect(details.rows.length).to eq(2) + + all_triggered_runs = details.rows.flat_map { |row| row.triggered_runs || [] } + + expect(all_triggered_runs.length).to eq(1) + + run = nil + 15.times do + run = hatchet.runs.get(all_triggered_runs[0].workflow_run_id) + break unless %w[QUEUED RUNNING].include?(run.status) + + sleep 1 + end + + expect(run.status).to eq("COMPLETED") + end +end \ No newline at end of file diff --git a/sdks/ruby/examples/idempotency/worker.rb b/sdks/ruby/examples/idempotency/worker.rb index d3716e95be..6c07372b9d 100644 --- a/sdks/ruby/examples/idempotency/worker.rb +++ b/sdks/ruby/examples/idempotency/worker.rb @@ -3,19 +3,27 @@ require 'hatchet-sdk' HATCHET = Hatchet::Client.new(debug: true) unless defined?(HATCHET) +EVENT_KEY = 'ruby-e2e:idempotency-example' # > idempotency IDEMPOTENT_TASK = HATCHET.task( - name: 'idempotent-task', + name: 'ruby-e2e-idempotent-task', idempotency: { expression: 'input.id', ttl_ms: 60_000 }, - on_events: ['idempotency:example'] + on_events: [EVENT_KEY] +) do |input, _ctx| + { 'result' => "Hello from task #{input['id']}" } +end + +IDEMPOTENT_TASK_SHORT_WINDOW = HATCHET.task( + name: 'ruby-e2e-idempotent-task-short-window', + idempotency: { expression: 'input.id', ttl_ms: 2_000 } ) do |input, _ctx| { 'result' => "Hello from task #{input['id']}" } end # !! def main - worker = HATCHET.worker('idempotency-worker', workflows: [IDEMPOTENT_TASK]) + worker = HATCHET.worker('idempotency-worker', workflows: [IDEMPOTENT_TASK, IDEMPOTENT_TASK_SHORT_WINDOW]) worker.start end diff --git a/sdks/typescript/src/v1/examples/e2e-worker.ts b/sdks/typescript/src/v1/examples/e2e-worker.ts index 6325185365..bfa7e27201 100644 --- a/sdks/typescript/src/v1/examples/e2e-worker.ts +++ b/sdks/typescript/src/v1/examples/e2e-worker.ts @@ -45,6 +45,7 @@ import { durableSleep } from './durable_sleep/workflow'; import { createLoggingWorkflow } from './logger/workflow'; import { nonRetryableWorkflow } from './non_retryable/workflow'; import { failureWorkflow } from './on_failure/workflow'; +import { idempotentTask, idempotentTaskShortWindow } from './idempotency/workflow'; import { lower } from './on_event/workflow'; import { returnExceptionsTask } from './return_exceptions/workflow'; import { runDetailTestWorkflow } from './run_details/workflow'; @@ -108,6 +109,8 @@ const workflows = [ createLoggingWorkflow(hatchet), nonRetryableWorkflow, failureWorkflow, + idempotentTask, + idempotentTaskShortWindow, lower, returnExceptionsTask, runDetailTestWorkflow, diff --git a/sdks/typescript/src/v1/examples/idempotency/idempotency.e2e.ts b/sdks/typescript/src/v1/examples/idempotency/idempotency.e2e.ts new file mode 100644 index 0000000000..63653bcb64 --- /dev/null +++ b/sdks/typescript/src/v1/examples/idempotency/idempotency.e2e.ts @@ -0,0 +1,146 @@ +import sleep from '@hatchet/util/sleep'; +import { randomUUID } from 'crypto'; +import { V1TaskStatus } from '@hatchet/clients/rest/generated/data-contracts'; +import { makeE2EClient, poll, startWorker, stopWorker } from '../__e2e__/harness'; +import { EVENT_KEY, idempotentTask, idempotentTaskShortWindow } from './workflow'; + +describe('idempotency-e2e', () => { + const hatchet = makeE2EClient(); + let worker: Awaited> | undefined; + + beforeAll(async () => { + worker = await startWorker({ + client: hatchet, + name: 'idempotency-e2e-worker', + workflows: [idempotentTask, idempotentTaskShortWindow], + }); + }); + + afterAll(async () => { + await stopWorker(worker); + }); + + async function waitForRuns(testRunId: string, minRuns: number) { + return poll( + async () => + hatchet.runs.list({ + additionalMetadata: { test_run_id: testRunId }, + onlyTasks: false, + limit: 20, + }), + { + timeoutMs: 30_000, + intervalMs: 200, + label: `runs for ${testRunId}`, + shouldStop: (response) => (response.rows || []).length >= minRuns, + } + ); + } + + it('prevents duplicate direct triggers', async () => { + const testRunId = randomUUID(); + const ref1 = await idempotentTask.runNoWait( + { id: testRunId }, + { + additionalMetadata: { + test_run_id: testRunId, + }, + } + ); + + let collided = false; + + try { + await idempotentTask.runNoWait({ id: testRunId }); + } catch { + collided = true; + } + + expect(collided).toBe(true); + + const runs = await waitForRuns(testRunId, 1); + + expect(runs.rows).toHaveLength(1); + expect(runs.rows?.[0]?.metadata.id).toBe(await ref1.getWorkflowRunId()); + }, 60_000); + + it('allows reruns after the short idempotency window expires', async () => { + const testRunId = randomUUID(); + + for (let i = 0; i < 4; i += 1) { + try { + await idempotentTaskShortWindow.runNoWait( + { id: testRunId }, + { + additionalMetadata: { + test_run_id: testRunId, + }, + } + ); + } catch {} + + if (i !== 3) { + await sleep((i + 1.5) * 1000); + } + } + + const runs = await waitForRuns(testRunId, 3); + + expect(runs.rows).toHaveLength(3); + }, 60_000); + + it('deduplicates event-triggered runs', async () => { + const testRunId = randomUUID(); + const e1 = await hatchet.events.push( + EVENT_KEY, + { id: testRunId }, + { + additionalMetadata: { + test_run_id: testRunId, + }, + } + ); + const e2 = await hatchet.events.push( + EVENT_KEY, + { id: testRunId }, + { + additionalMetadata: { + test_run_id: testRunId, + }, + } + ); + + const runs = await waitForRuns(testRunId, 1); + + expect(runs.rows).toHaveLength(1); + + const details = await poll( + async () => hatchet.events.list({ eventIds: [e1.eventId, e2.eventId] }), + { + timeoutMs: 30_000, + intervalMs: 200, + label: 'idempotency event details', + shouldStop: (response) => (response.rows || []).length === 2, + } + ); + + const allTriggeredRuns = (details.rows || []).flatMap((row) => row.triggeredRuns || []); + + expect(allTriggeredRuns).toHaveLength(1); + + const runDetails = await poll( + async () => hatchet.runs.get(allTriggeredRuns[0].workflowRunId), + { + timeoutMs: 30_000, + intervalMs: 200, + label: 'idempotent event-triggered run completion', + shouldStop: (response) => { + const status = response.run?.status; + return status !== V1TaskStatus.QUEUED && status !== V1TaskStatus.RUNNING; + }, + } + ); + + expect(runDetails.run?.status).toBe(V1TaskStatus.COMPLETED); + }, 60_000); +}); \ No newline at end of file diff --git a/sdks/typescript/src/v1/examples/idempotency/workflow.ts b/sdks/typescript/src/v1/examples/idempotency/workflow.ts index 495867ebe5..81b052abee 100644 --- a/sdks/typescript/src/v1/examples/idempotency/workflow.ts +++ b/sdks/typescript/src/v1/examples/idempotency/workflow.ts @@ -1,18 +1,32 @@ import { hatchet } from '../hatchet-client'; +export const EVENT_KEY = 'ts-e2e:idempotency-example'; + export type IdempotencyInput = { id: string; }; // > idempotency export const idempotentTask = hatchet.task({ - name: 'idempotent-task', + name: 'ts-e2e-idempotent-task', idempotency: { expression: 'input.id', ttlMs: 60_000, }, + onEvents: [EVENT_KEY], fn: async (input) => { return { result: `Hello, world from task ${input.id}` }; }, }); // !! + +export const idempotentTaskShortWindow = hatchet.task({ + name: 'ts-e2e-idempotent-task-short-window', + idempotency: { + expression: 'input.id', + ttlMs: 2_000, + }, + fn: async (input) => { + return { result: `Hello, world from task ${input.id}` }; + }, +}); From 342ab2511906cf6b9819645b839c9be3adce23e6 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Wed, 3 Jun 2026 15:43:51 -0400 Subject: [PATCH 58/69] chore: gen --- examples/ruby/idempotency/worker.rb | 14 +++++++++++--- examples/typescript/e2e-worker.ts | 3 +++ examples/typescript/idempotency/workflow.ts | 16 +++++++++++++++- sdks/go/e2e/idempotency_test.go | 6 +++--- .../idempotency/test_idempotency_spec.rb | 2 +- .../v1/examples/idempotency/idempotency.e2e.ts | 2 +- 6 files changed, 34 insertions(+), 9 deletions(-) diff --git a/examples/ruby/idempotency/worker.rb b/examples/ruby/idempotency/worker.rb index 605d1d3db0..4a990c3035 100644 --- a/examples/ruby/idempotency/worker.rb +++ b/examples/ruby/idempotency/worker.rb @@ -3,18 +3,26 @@ require 'hatchet-sdk' HATCHET = Hatchet::Client.new(debug: true) unless defined?(HATCHET) +EVENT_KEY = 'ruby-e2e:idempotency-example' # > idempotency IDEMPOTENT_TASK = HATCHET.task( - name: 'idempotent-task', + name: 'ruby-e2e-idempotent-task', idempotency: { expression: 'input.id', ttl_ms: 60_000 }, - on_events: ['idempotency:example'] + on_events: [EVENT_KEY] +) do |input, _ctx| + { 'result' => "Hello from task #{input['id']}" } +end + +IDEMPOTENT_TASK_SHORT_WINDOW = HATCHET.task( + name: 'ruby-e2e-idempotent-task-short-window', + idempotency: { expression: 'input.id', ttl_ms: 2_000 } ) do |input, _ctx| { 'result' => "Hello from task #{input['id']}" } end def main - worker = HATCHET.worker('idempotency-worker', workflows: [IDEMPOTENT_TASK]) + worker = HATCHET.worker('idempotency-worker', workflows: [IDEMPOTENT_TASK, IDEMPOTENT_TASK_SHORT_WINDOW]) worker.start end diff --git a/examples/typescript/e2e-worker.ts b/examples/typescript/e2e-worker.ts index 6325185365..bfa7e27201 100644 --- a/examples/typescript/e2e-worker.ts +++ b/examples/typescript/e2e-worker.ts @@ -45,6 +45,7 @@ import { durableSleep } from './durable_sleep/workflow'; import { createLoggingWorkflow } from './logger/workflow'; import { nonRetryableWorkflow } from './non_retryable/workflow'; import { failureWorkflow } from './on_failure/workflow'; +import { idempotentTask, idempotentTaskShortWindow } from './idempotency/workflow'; import { lower } from './on_event/workflow'; import { returnExceptionsTask } from './return_exceptions/workflow'; import { runDetailTestWorkflow } from './run_details/workflow'; @@ -108,6 +109,8 @@ const workflows = [ createLoggingWorkflow(hatchet), nonRetryableWorkflow, failureWorkflow, + idempotentTask, + idempotentTaskShortWindow, lower, returnExceptionsTask, runDetailTestWorkflow, diff --git a/examples/typescript/idempotency/workflow.ts b/examples/typescript/idempotency/workflow.ts index 02d53abd5f..2d13870d64 100644 --- a/examples/typescript/idempotency/workflow.ts +++ b/examples/typescript/idempotency/workflow.ts @@ -1,16 +1,30 @@ import { hatchet } from '../hatchet-client'; +export const EVENT_KEY = 'ts-e2e:idempotency-example'; + export type IdempotencyInput = { id: string; }; // > idempotency export const idempotentTask = hatchet.task({ - name: 'idempotent-task', + name: 'ts-e2e-idempotent-task', idempotency: { expression: 'input.id', ttlMs: 60_000, }, + onEvents: [EVENT_KEY], + fn: async (input) => { + return { result: `Hello, world from task ${input.id}` }; + }, +}); + +export const idempotentTaskShortWindow = hatchet.task({ + name: 'ts-e2e-idempotent-task-short-window', + idempotency: { + expression: 'input.id', + ttlMs: 2_000, + }, fn: async (input) => { return { result: `Hello, world from task ${input.id}` }; }, diff --git a/sdks/go/e2e/idempotency_test.go b/sdks/go/e2e/idempotency_test.go index 95e0457520..8cfa5e48b7 100644 --- a/sdks/go/e2e/idempotency_test.go +++ b/sdks/go/e2e/idempotency_test.go @@ -17,9 +17,9 @@ import ( ) const ( - idempotencyEventKey = "go-e2e:idempotency-example" - idempotentTaskName = "go-e2e-idempotent-task" - idempotentShortWindowName = "go-e2e-idempotent-task-short-window" + idempotencyEventKey = "go-e2e:idempotency-example" + idempotentTaskName = "go-e2e-idempotent-task" + idempotentShortWindowName = "go-e2e-idempotent-task-short-window" ) type idempotencyInput struct { diff --git a/sdks/ruby/examples/idempotency/test_idempotency_spec.rb b/sdks/ruby/examples/idempotency/test_idempotency_spec.rb index 3dcc772aa9..32a1ed6d99 100644 --- a/sdks/ruby/examples/idempotency/test_idempotency_spec.rb +++ b/sdks/ruby/examples/idempotency/test_idempotency_spec.rb @@ -130,4 +130,4 @@ def wait_for_runs(test_run_id, min_runs:) expect(run.status).to eq("COMPLETED") end -end \ No newline at end of file +end diff --git a/sdks/typescript/src/v1/examples/idempotency/idempotency.e2e.ts b/sdks/typescript/src/v1/examples/idempotency/idempotency.e2e.ts index 63653bcb64..c11f28c118 100644 --- a/sdks/typescript/src/v1/examples/idempotency/idempotency.e2e.ts +++ b/sdks/typescript/src/v1/examples/idempotency/idempotency.e2e.ts @@ -143,4 +143,4 @@ describe('idempotency-e2e', () => { expect(runDetails.run?.status).toBe(V1TaskStatus.COMPLETED); }, 60_000); -}); \ No newline at end of file +}); From 6c46e848c75cd3e8961cac075968a5ff72a2bc38 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Wed, 3 Jun 2026 15:44:28 -0400 Subject: [PATCH 59/69] chore: gen again --- .../examples/idempotency/idempotency.e2e.ts | 40 ++++++++++++++----- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/sdks/typescript/src/v1/examples/idempotency/idempotency.e2e.ts b/sdks/typescript/src/v1/examples/idempotency/idempotency.e2e.ts index c11f28c118..3c3ce718c1 100644 --- a/sdks/typescript/src/v1/examples/idempotency/idempotency.e2e.ts +++ b/sdks/typescript/src/v1/examples/idempotency/idempotency.e2e.ts @@ -1,6 +1,7 @@ import sleep from '@hatchet/util/sleep'; import { randomUUID } from 'crypto'; import { V1TaskStatus } from '@hatchet/clients/rest/generated/data-contracts'; +import { IdempotencyCollisionError } from '@hatchet/v1'; import { makeE2EClient, poll, startWorker, stopWorker } from '../__e2e__/harness'; import { EVENT_KEY, idempotentTask, idempotentTaskShortWindow } from './workflow'; @@ -48,15 +49,20 @@ describe('idempotency-e2e', () => { } ); - let collided = false; + let collisionError: IdempotencyCollisionError | undefined; try { await idempotentTask.runNoWait({ id: testRunId }); - } catch { - collided = true; + } catch (e) { + if (e instanceof IdempotencyCollisionError) { + collisionError = e; + } else { + throw e; + } } - expect(collided).toBe(true); + expect(collisionError).toBeInstanceOf(IdempotencyCollisionError); + expect(collisionError?.existingRunExternalId).toBe(await ref1.getWorkflowRunId()); const runs = await waitForRuns(testRunId, 1); @@ -68,16 +74,28 @@ describe('idempotency-e2e', () => { const testRunId = randomUUID(); for (let i = 0; i < 4; i += 1) { - try { + if (i === 1) { + let collisionError: IdempotencyCollisionError | undefined; + try { + await idempotentTaskShortWindow.runNoWait( + { id: testRunId }, + { additionalMetadata: { test_run_id: testRunId } } + ); + } catch (e) { + if (e instanceof IdempotencyCollisionError) { + collisionError = e; + } else { + throw e; + } + } + expect(collisionError).toBeInstanceOf(IdempotencyCollisionError); + expect(collisionError?.existingRunExternalId).toBeTruthy(); + } else { await idempotentTaskShortWindow.runNoWait( { id: testRunId }, - { - additionalMetadata: { - test_run_id: testRunId, - }, - } + { additionalMetadata: { test_run_id: testRunId } } ); - } catch {} + } if (i !== 3) { await sleep((i + 1.5) * 1000); From 02df6addd2cd8e2afe38e0fa1459deadc481a75f Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Wed, 3 Jun 2026 16:54:11 -0400 Subject: [PATCH 60/69] fix: tests --- sdks/typescript/src/util/retrier.ts | 6 +++- sdks/typescript/src/v1/client/admin.ts | 39 ++++++++++++++++++++++++-- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/sdks/typescript/src/util/retrier.ts b/sdks/typescript/src/util/retrier.ts index 0164ee6bca..f2b9b49f81 100644 --- a/sdks/typescript/src/util/retrier.ts +++ b/sdks/typescript/src/util/retrier.ts @@ -9,7 +9,8 @@ export async function retrier( fn: () => Promise, logger: Logger, retries: number = DEFAULT_RETRY_COUNT, - interval: number = DEFAULT_RETRY_INTERVAL + interval: number = DEFAULT_RETRY_INTERVAL, + shouldRetry: (e: unknown) => boolean = () => true ) { let lastError: Error | undefined; @@ -17,6 +18,9 @@ export async function retrier( try { return await fn(); } catch (e: unknown) { + if (!shouldRetry(e)) { + throw e; + } lastError = e instanceof Error ? e : new Error(String(e)); logger.error(`Error: ${lastError.message}`); diff --git a/sdks/typescript/src/v1/client/admin.ts b/sdks/typescript/src/v1/client/admin.ts index 4068eb62f6..315a1db3b7 100644 --- a/sdks/typescript/src/v1/client/admin.ts +++ b/sdks/typescript/src/v1/client/admin.ts @@ -5,6 +5,7 @@ import WorkflowRunRef from '@hatchet/util/workflow-run-ref'; import { ServiceError, status as GrpcStatus } from '@grpc/grpc-js'; import { Status as RpcStatus } from '@hatchet/protoc/google/rpc/status'; import { IdempotencyCollisionError as IdempotencyCollisionErrorProto } from '@hatchet/protoc/v1/workflows'; +import { ClientError, Metadata as NiceGrpcMetadata, Status as NiceGrpcStatus } from 'nice-grpc-common'; import { Priority, RateLimitDuration, RunsClient, WorkerLabelComparator } from '@hatchet/v1'; import { createGrpcClient } from '@hatchet/util/grpc-helpers'; @@ -47,6 +48,27 @@ function extractExistingRunIdFromGrpcError(e: ServiceError): string { return ''; } +function isNiceGrpcAlreadyExists(e: unknown): e is ClientError { + return e instanceof ClientError && e.code === NiceGrpcStatus.ALREADY_EXISTS; +} + +function extractRunIdFromNiceGrpcMetadata(metadata: NiceGrpcMetadata | undefined): string { + if (!metadata) return ''; + try { + const binData = metadata.get('grpc-status-details-bin'); + if (!binData) return ''; + const status = RpcStatus.decode(binData); + for (const detail of status.details) { + if (detail.typeUrl.includes('IdempotencyCollisionError')) { + return IdempotencyCollisionErrorProto.decode(detail.value).existingRunExternalId ?? ''; + } + } + } catch { + // ignore decoding errors + } + return ''; +} + type DesiredWorkerLabelOpt = { value: string | number; required?: boolean; @@ -160,6 +182,8 @@ export class AdminClient { _standaloneTaskName?: string | undefined; } ) { + let trailerMetadata: NiceGrpcMetadata | undefined; + try { const computedName = applyNamespace(workflowName, this.config.namespace).toLowerCase(); @@ -188,8 +212,16 @@ export class AdminClient { }; const resp = await retrier( - async () => this.workflowsGrpc.triggerWorkflow(request), - this.logger + async () => + this.workflowsGrpc.triggerWorkflow(request, { + onTrailer: (trailer) => { + trailerMetadata = trailer; + }, + }), + this.logger, + undefined, + undefined, + (e) => !isNiceGrpcAlreadyExists(e) ); const id = resp.workflowRunId; @@ -208,6 +240,9 @@ export class AdminClient { if (isGrpcServiceError(e) && e.code === GrpcStatus.ALREADY_EXISTS) { throw new IdempotencyCollisionError(extractExistingRunIdFromGrpcError(e)); } + if (isNiceGrpcAlreadyExists(e)) { + throw new IdempotencyCollisionError(extractRunIdFromNiceGrpcMetadata(trailerMetadata)); + } throw new HatchetError(e instanceof Error ? e.message : String(e)); } } From be351c28221c3f0cc5bc3daee2448441cd4ab2a7 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Wed, 3 Jun 2026 17:09:09 -0400 Subject: [PATCH 61/69] chore: gen --- sdks/typescript/src/v1/client/admin.ts | 6 +++++- .../examples/idempotency/idempotency.e2e.ts | 21 ++++++++----------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/sdks/typescript/src/v1/client/admin.ts b/sdks/typescript/src/v1/client/admin.ts index 315a1db3b7..bf8342b674 100644 --- a/sdks/typescript/src/v1/client/admin.ts +++ b/sdks/typescript/src/v1/client/admin.ts @@ -5,7 +5,11 @@ import WorkflowRunRef from '@hatchet/util/workflow-run-ref'; import { ServiceError, status as GrpcStatus } from '@grpc/grpc-js'; import { Status as RpcStatus } from '@hatchet/protoc/google/rpc/status'; import { IdempotencyCollisionError as IdempotencyCollisionErrorProto } from '@hatchet/protoc/v1/workflows'; -import { ClientError, Metadata as NiceGrpcMetadata, Status as NiceGrpcStatus } from 'nice-grpc-common'; +import { + ClientError, + Metadata as NiceGrpcMetadata, + Status as NiceGrpcStatus, +} from 'nice-grpc-common'; import { Priority, RateLimitDuration, RunsClient, WorkerLabelComparator } from '@hatchet/v1'; import { createGrpcClient } from '@hatchet/util/grpc-helpers'; diff --git a/sdks/typescript/src/v1/examples/idempotency/idempotency.e2e.ts b/sdks/typescript/src/v1/examples/idempotency/idempotency.e2e.ts index 3c3ce718c1..a74af10428 100644 --- a/sdks/typescript/src/v1/examples/idempotency/idempotency.e2e.ts +++ b/sdks/typescript/src/v1/examples/idempotency/idempotency.e2e.ts @@ -146,18 +146,15 @@ describe('idempotency-e2e', () => { expect(allTriggeredRuns).toHaveLength(1); - const runDetails = await poll( - async () => hatchet.runs.get(allTriggeredRuns[0].workflowRunId), - { - timeoutMs: 30_000, - intervalMs: 200, - label: 'idempotent event-triggered run completion', - shouldStop: (response) => { - const status = response.run?.status; - return status !== V1TaskStatus.QUEUED && status !== V1TaskStatus.RUNNING; - }, - } - ); + const runDetails = await poll(async () => hatchet.runs.get(allTriggeredRuns[0].workflowRunId), { + timeoutMs: 30_000, + intervalMs: 200, + label: 'idempotent event-triggered run completion', + shouldStop: (response) => { + const status = response.run?.status; + return status !== V1TaskStatus.QUEUED && status !== V1TaskStatus.RUNNING; + }, + }); expect(runDetails.run?.status).toBe(V1TaskStatus.COMPLETED); }, 60_000); From c9b5e1d943401b31ee0a3299954246c8044730d4 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Thu, 4 Jun 2026 09:36:10 -0400 Subject: [PATCH 62/69] chore: changelogs --- .../pages/reference/changelog/platform.mdx | 765 +++++++----------- .../docs/pages/reference/changelog/python.mdx | 10 + .../docs/pages/reference/changelog/ruby.mdx | 6 + .../pages/reference/changelog/typescript.mdx | 6 + 4 files changed, 336 insertions(+), 451 deletions(-) diff --git a/frontend/docs/pages/reference/changelog/platform.mdx b/frontend/docs/pages/reference/changelog/platform.mdx index daa0ff5de0..18d50f997e 100644 --- a/frontend/docs/pages/reference/changelog/platform.mdx +++ b/frontend/docs/pages/reference/changelog/platform.mdx @@ -3,175 +3,165 @@ ## v0.88.0 - 2026-05-30 ### Fixed - - Use leases to protect concurrent partition creation (un-revert) by @juliusgeo in [#4051](https://github.com/hatchet-dev/hatchet/pull/4051) + ## v0.87.12 - 2026-05-30 ### Fixed - - Re-enable external id dupe check, add flag to enable dynamic window size by @mrkaye97 in [#4050](https://github.com/hatchet-dev/hatchet/pull/4050) + ## v0.87.11 - 2026-05-30 ### Fixed - - Remove external id check for now, it's too expensive by @mrkaye97 in [#4048](https://github.com/hatchet-dev/hatchet/pull/4048) + ## v0.87.10 - 2026-05-30 ### Fixed - - Lengthen timeout for external id dupe check by @mrkaye97 in [#4047](https://github.com/hatchet-dev/hatchet/pull/4047) + ## v0.87.9 - 2026-05-29 ### Fixed - - Migration order for partition leases by @juliusgeo in [#4046](https://github.com/hatchet-dev/hatchet/pull/4046) + ## v0.87.8 - 2026-05-29 ### Fixed - - Use leases to protect concurrent partition creation by @juliusgeo in [#4044](https://github.com/hatchet-dev/hatchet/pull/4044) + ## v0.87.7 - 2026-05-29 ### Added - - Support named shard deployment targets by @igor-kupczynski in [#4042](https://github.com/hatchet-dev/hatchet/pull/4042) ### Fixed - - Some more payload offload improvements by @mrkaye97 in [#4041](https://github.com/hatchet-dev/hatchet/pull/4041) - Sleep bug for anything over 59s by @darren-west in [#4012](https://github.com/hatchet-dev/hatchet/pull/4012) - Update quickstart template SDK versions by @BloggerBust in [#4025](https://github.com/hatchet-dev/hatchet/pull/4025) + ## v0.87.6 - 2026-05-28 ### Added - - Index file-based payload offloads by @mrkaye97 in [#3979](https://github.com/hatchet-dev/hatchet/pull/3979) + ## v0.87.3 - 2026-05-28 ### Fixed - - Go use durable listener for async results by @grutt in [#4019](https://github.com/hatchet-dev/hatchet/pull/4019) + ## v0.87.1 - 2026-05-26 ### Added - - Move billing to control plane by @grutt in [#3982](https://github.com/hatchet-dev/hatchet/pull/3982) + ## v0.86.32 - 2026-05-26 ### Added - - Show structured log fields in log viewer by @MicroYui in [#3972](https://github.com/hatchet-dev/hatchet/pull/3972) ### Changed - - Use shared Dialog for plan upgrade modal by @jishnundth in [#3887](https://github.com/hatchet-dev/hatchet/pull/3887) ### Removed - - Remove backoff overflow test by @juliusgeo in [#3990](https://github.com/hatchet-dev/hatchet/pull/3990) ### Fixed - - Hide github tab if managed workers are disabled by @mrkaye97 in [#3988](https://github.com/hatchet-dev/hatchet/pull/3988) + ## v0.86.29 - 2026-05-22 ### Fixed - - Migration number by @mrkaye97 in [#3987](https://github.com/hatchet-dev/hatchet/pull/3987) + ## v0.86.28 - 2026-05-22 ### Fixed - - A couple more migration performance issues by @mrkaye97 in [#3942](https://github.com/hatchet-dev/hatchet/pull/3942) + ## v0.86.27 - 2026-05-22 ### Added - - Make Hatchet Lite version configurable by @Ujjwal-Singh-20 in [#3925](https://github.com/hatchet-dev/hatchet/pull/3925) ### Removed - - Revert "chore: move requests to control plane" by @grutt ### Fixed - - Payload external id duplicates from durable child spawning by @mrkaye97 in [#3984](https://github.com/hatchet-dev/hatchet/pull/3984) + ## v0.86.26 - 2026-05-21 ### Fixed - - Increase limit default to 10k by @mrkaye97 in [#3978](https://github.com/hatchet-dev/hatchet/pull/3978) + ## v0.86.25 - 2026-05-20 ### Fixed - - Only run worker id action getter query when needed by @mrkaye97 in [#3976](https://github.com/hatchet-dev/hatchet/pull/3976) + ## v0.86.24 - 2026-05-20 ### Changed - - Make SSO Configuration UI more clear by @juliusgeo in [#3971](https://github.com/hatchet-dev/hatchet/pull/3971) ### Fixed - - Correctly handle child key-based caching in durable task child spawning by @mrkaye97 in [#3955](https://github.com/hatchet-dev/hatchet/pull/3955) + ## v0.86.23 - 2026-05-20 ### Fixed - - Attempt at improving worker actions query by @mrkaye97 in [#3774](https://github.com/hatchet-dev/hatchet/pull/3774) + ## v0.86.22 - 2026-05-20 ### Added - - Entitlement check by @grutt in [#3969](https://github.com/hatchet-dev/hatchet/pull/3969) - Move workflow search bar to the top of workflows table by @abelanger5 in [#3967](https://github.com/hatchet-dev/hatchet/pull/3967) - Add Dockerfile for all SDK e2e tests, fix flaky e2e tests by @juliusgeo in [#3846](https://github.com/hatchet-dev/hatchet/pull/3846) + ## v0.86.21 - 2026-05-19 ### Changed - - Pass `insertedAt` to OLAP payload reads by @mrkaye97 in [#3960](https://github.com/hatchet-dev/hatchet/pull/3960) ### Removed - - Revert "feat: route frontend billing through control plane" by @grutt in [#3963](https://github.com/hatchet-dev/hatchet/pull/3963) + ## v0.86.20 - 2026-05-19 ### Changed - - Use `externalId` for retrieving payloads by @mrkaye97 in [#3914](https://github.com/hatchet-dev/hatchet/pull/3914) + ## v0.86.19 - 2026-05-19 ### Added - - Add logging for serial operation by @juliusgeo in [#3957](https://github.com/hatchet-dev/hatchet/pull/3957) - Added validation for seed password by @NathanDrake007 in [#3783](https://github.com/hatchet-dev/hatchet/pull/3783) - Route frontend billing through control plane by @grutt ### Fixed - - Deadlock on trigger workflows by @mrkaye97 in [#3906](https://github.com/hatchet-dev/hatchet/pull/3906) - Re-order auth flow to prevent side effects on handleCookieAuth by @gregfurman in [#3923](https://github.com/hatchet-dev/hatchet/pull/3923) - Dedupe security check in hatchet-lite by @MicroYui in [#3928](https://github.com/hatchet-dev/hatchet/pull/3928) @@ -180,2391 +170,2264 @@ - Poll plan by @grutt - Dedicated state by @grutt + ## v0.86.18 - 2026-05-14 ### Fixed - - New user flow on control plane deployment by @grutt in [#3891](https://github.com/hatchet-dev/hatchet/pull/3891) + ## v0.86.17 - 2026-05-13 ### Fixed - - Separate transactions in payload migration by @mrkaye97 in [#3907](https://github.com/hatchet-dev/hatchet/pull/3907) + ## v0.86.16 - 2026-05-13 ### Added - - Add error if counts don't match so we can debug by @mrkaye97 in [#3905](https://github.com/hatchet-dev/hatchet/pull/3905) + ## v0.86.15 - 2026-05-13 ### Added - - Paginate through payloads by external id, not using the PK by @mrkaye97 in [#3883](https://github.com/hatchet-dev/hatchet/pull/3883) + ## v0.86.14 - 2026-05-13 ### Added - - Control plane redirect by @grutt in [#3904](https://github.com/hatchet-dev/hatchet/pull/3904) + ## v0.86.12 - 2026-05-12 ### Fixed - - Limit the number of re-pubs to be the max death count by @mrkaye97 in [#3899](https://github.com/hatchet-dev/hatchet/pull/3899) + ## v0.86.10 - 2026-05-12 ### Added - - Add external id index to payloads tables by @mrkaye97 in [#3879](https://github.com/hatchet-dev/hatchet/pull/3879) + ## v0.86.9 - 2026-05-12 ### Added - - Add cron schedule time to additional meta by @mrkaye97 in [#3884](https://github.com/hatchet-dev/hatchet/pull/3884) ### Changed - - New `taskNames` query param on the task stats endpoint for KEDA friendliness by @mnafees in [#3791](https://github.com/hatchet-dev/hatchet/pull/3791) ### Fixed - - Durable eviction / restore causing duplicate queue items by @mrkaye97 in [#3864](https://github.com/hatchet-dev/hatchet/pull/3864) + ## v0.86.8 - 2026-05-11 ### Fixed - - Avoid invalid user FK in cookie test by @igor-kupczynski in [#3882](https://github.com/hatchet-dev/hatchet/pull/3882) - Occasional empty payloads on DAG task retries by @juliusgeo in [#3860](https://github.com/hatchet-dev/hatchet/pull/3860) + ## v0.86.7 - 2026-05-11 ### Fixed - - Add backoff to sleeps by @mrkaye97 in [#3880](https://github.com/hatchet-dev/hatchet/pull/3880) + ## v0.86.6 - 2026-05-11 ### Added - - Add migratediag package for DSN redaction and error handling by @igor-kupczynski in [#3878](https://github.com/hatchet-dev/hatchet/pull/3878) ### Fixed - - Allow partial successes on status updates by @mrkaye97 in [#3861](https://github.com/hatchet-dev/hatchet/pull/3861) + ## v0.86.5 - 2026-05-08 ### Fixed - - Use correct log out and invalidate stale cookies by @grutt in [#3858](https://github.com/hatchet-dev/hatchet/pull/3858) + ## v0.86.4 - 2026-05-08 ### Added - - Configurable OLAP MQ QoS by @mrkaye97 in [#3857](https://github.com/hatchet-dev/hatchet/pull/3857) - Add unit tests for DurableEventsListener reconnect and event delivery during retry backoff by @igor-kupczynski ### Fixed - - Durable wait mutex issue by @mrkaye97 + ## v0.86.1 - 2026-05-06 ### Added - - Add cols to offload table to track count diffs by @mrkaye97 in [#3841](https://github.com/hatchet-dev/hatchet/pull/3841) + ## v0.85.10 - 2026-05-06 ### Fixed - - Merge issue? these definitely had been removed before... by @mrkaye97 in [#3839](https://github.com/hatchet-dev/hatchet/pull/3839) + ## v0.85.9 - 2026-05-06 ### Fixed - - Remove dual writes into temp tables for status updates by @mrkaye97 in [#3829](https://github.com/hatchet-dev/hatchet/pull/3829) - Slow cold start when workflows are inactive > 1 day by @juliusgeo in [#3830](https://github.com/hatchet-dev/hatchet/pull/3830) + ## v0.85.8 - 2026-05-06 ### Fixed - - Prune partitions in reconciliation query with min inserted at filter by @mrkaye97 in [#3838](https://github.com/hatchet-dev/hatchet/pull/3838) - Dedupe task ids before updating to assigned by @mrkaye97 in [#3818](https://github.com/hatchet-dev/hatchet/pull/3818) + ## v0.85.7 - 2026-05-05 ### Fixed - - Add back task event tmp dual write by @mrkaye97 in [#3828](https://github.com/hatchet-dev/hatchet/pull/3828) - Improve performance of status metrics query via index usage + partition pruning by @mrkaye97 in [#3800](https://github.com/hatchet-dev/hatchet/pull/3800) - Improve transactional safety of payload offload reads and writes by @mrkaye97 in [#3814](https://github.com/hatchet-dev/hatchet/pull/3814) + ## v0.85.6 - 2026-05-05 ### Fixed - - Payload external id dupe by @mrkaye97 in [#3824](https://github.com/hatchet-dev/hatchet/pull/3824) + ## v0.85.5 - 2026-05-05 ### Added - - Direct task + dag status updates by @mrkaye97 in [#3554](https://github.com/hatchet-dev/hatchet/pull/3554) - Swap links to control plane by @grutt in [#3816](https://github.com/hatchet-dev/hatchet/pull/3816) + ## v0.85.4 - 2026-05-04 ### Fixed - - Show tenant invite modal correctly by @mrkaye97 in [#3815](https://github.com/hatchet-dev/hatchet/pull/3815) + ## v0.85.3 - 2026-05-02 ### Fixed - - Panic on trigger by @mrkaye97 in [#3803](https://github.com/hatchet-dev/hatchet/pull/3803) - Refresh token edge cases by @grutt in [#3802](https://github.com/hatchet-dev/hatchet/pull/3802) + ## v0.85.2 - 2026-05-01 ### Fixed - - Wait for user universe by @grutt in [#3801](https://github.com/hatchet-dev/hatchet/pull/3801) + ## v0.85.1 - 2026-05-01 ### Fixed - - Hoist org management / tenant management page by @mrkaye97 in [#3797](https://github.com/hatchet-dev/hatchet/pull/3797) + ## v0.85.0 - 2026-05-01 ### Added - - Per organization inactivity timeout by @juliusgeo in [#3795](https://github.com/hatchet-dev/hatchet/pull/3795) - Shard-affinity by @grutt in [#3788](https://github.com/hatchet-dev/hatchet/pull/3788) - Add force SSO toggle by @juliusgeo in [#3787](https://github.com/hatchet-dev/hatchet/pull/3787) ### Fixed - - Fixes for SSO UI layout by @juliusgeo in [#3793](https://github.com/hatchet-dev/hatchet/pull/3793) - Fix--control-plane-bash by @grutt in [#3781](https://github.com/hatchet-dev/hatchet/pull/3781) + ## v0.83.61 - 2026-04-29 ### Added - - Feature flag sso by @grutt in [#3786](https://github.com/hatchet-dev/hatchet/pull/3786) ### Changed - - Remove unused err var by @jishnundth in [#3785](https://github.com/hatchet-dev/hatchet/pull/3785) ### Fixed - - Workflow reschedule update fail by @jishnundth in [#3782](https://github.com/hatchet-dev/hatchet/pull/3782) + ## v0.83.60 - 2026-04-28 ### Fixed - - Add cron job to deactivate stale step concurrency configs by @mrkaye97 in [#3775](https://github.com/hatchet-dev/hatchet/pull/3775) - Use `SimpleTable` for span attributes by @mrkaye97 in [#3778](https://github.com/hatchet-dev/hatchet/pull/3778) + ## v0.83.59 - 2026-04-28 ### Added - - Durable go by @grutt in [#3696](https://github.com/hatchet-dev/hatchet/pull/3696) ### Fixed - - Attempt to fix deadlocks, part 1bn by @mrkaye97 in [#3776](https://github.com/hatchet-dev/hatchet/pull/3776) + ## v0.83.57 - 2026-04-28 ### Changed - - Ignore invalid step IDs when inserting or replaying tasks by @mnafees in [#3735](https://github.com/hatchet-dev/hatchet/pull/3735) - Make all list partition calls specific to v1 tenants only by @mnafees in [#3758](https://github.com/hatchet-dev/hatchet/pull/3758) ### Fixed - - Don't drop index by @mrkaye97 in [#3765](https://github.com/hatchet-dev/hatchet/pull/3765) - Increase max password length to 64 and improve validation error message by @NathanDrake007 in [#3713](https://github.com/hatchet-dev/hatchet/pull/3713) + ## v0.83.56 - 2026-04-28 ### Fixed - - Acquire locks right away by @mrkaye97 in [#3762](https://github.com/hatchet-dev/hatchet/pull/3762) + ## v0.83.55 - 2026-04-28 ### Fixed - - Improve performance of cron schedule polling query by @mrkaye97 in [#3754](https://github.com/hatchet-dev/hatchet/pull/3754) + ## v0.83.54 - 2026-04-27 ### Fixed - - Remove row count check, replace with threshold by @mrkaye97 in [#3756](https://github.com/hatchet-dev/hatchet/pull/3756) + ## v0.83.53 - 2026-04-27 ### Added - - Go partition by partition by @mrkaye97 in [#3755](https://github.com/hatchet-dev/hatchet/pull/3755) - Frontend SSO support by @juliusgeo in [#3582](https://github.com/hatchet-dev/hatchet/pull/3582) ### Fixed - - Trace view improvements by @mrkaye97 in [#3702](https://github.com/hatchet-dev/hatchet/pull/3702) + ## v0.83.52 - 2026-04-27 ### Fixed - - Do nothing on conflict, and diff out existing rows by @mrkaye97 in [#3752](https://github.com/hatchet-dev/hatchet/pull/3752) + ## v0.83.50 - 2026-04-27 ### Fixed - - Add exclusive lock, run analyze by @mrkaye97 in [#3739](https://github.com/hatchet-dev/hatchet/pull/3739) + ## v0.83.49 - 2026-04-27 ### Changed - - Error out when marking queue items as resolved by @mnafees in [#3736](https://github.com/hatchet-dev/hatchet/pull/3736) ### Fixed - - Add row count check before swapping by @mrkaye97 in [#3709](https://github.com/hatchet-dev/hatchet/pull/3709) + ## v0.83.48 - 2026-04-24 ### Fixed - - Fetching state causing redirect loop on signup by @mrkaye97 in [#3708](https://github.com/hatchet-dev/hatchet/pull/3708) + ## v0.83.47 - 2026-04-24 ### Fixed - - Broken migration by @mrkaye97 in [#3707](https://github.com/hatchet-dev/hatchet/pull/3707) + ## v0.83.46 - 2026-04-24 ### Fixed - - Remove status partitioning on `v1_(runs|dags|tasks)_olap` by @mrkaye97 in [#3603](https://github.com/hatchet-dev/hatchet/pull/3603) + ## v0.83.45 - 2026-04-24 ### Fixed - - Regen control plane api by @abelanger5 in [#3705](https://github.com/hatchet-dev/hatchet/pull/3705) + ## v0.83.44 - 2026-04-24 ### Changed - - Make sure we call for durable task invocation only for durable tasks in the scheduler by @mnafees in [#3698](https://github.com/hatchet-dev/hatchet/pull/3698) + ## v0.83.43 - 2026-04-24 ### Added - - Separate frontend and server url, pass the server url as part of the tenant by @abelanger5 in [#3697](https://github.com/hatchet-dev/hatchet/pull/3697) + ## v0.83.42 - 2026-04-24 ### Fixed - - Couple more frontend + webhooks things by @mrkaye97 in [#3695](https://github.com/hatchet-dev/hatchet/pull/3695) + ## v0.83.41 - 2026-04-23 ### Fixed - - Tailwind hell by @mrkaye97 in [#3693](https://github.com/hatchet-dev/hatchet/pull/3693) + ## v0.83.40 - 2026-04-23 ### Fixed - - Couple more frontend bugs by @mrkaye97 in [#3692](https://github.com/hatchet-dev/hatchet/pull/3692) + ## v0.83.39 - 2026-04-23 ### Fixed - - Return from delete query by @abelanger5 in [#3690](https://github.com/hatchet-dev/hatchet/pull/3690) - Validate tenant membership on V1DagListTasks by @abelanger5 in [#3691](https://github.com/hatchet-dev/hatchet/pull/3691) + ## v0.83.38 - 2026-04-23 ### Added - - Workflow filter FTS by @mrkaye97 in [#3685](https://github.com/hatchet-dev/hatchet/pull/3685) ### Changed - - Make sure we don't poll for work in case of deleted tenants by @mnafees in [#3682](https://github.com/hatchet-dev/hatchet/pull/3682) - Also clean slot configs for workers by @mnafees in [#3666](https://github.com/hatchet-dev/hatchet/pull/3666) - Cache CEL programs in order to avoid expensive heap allocations on each event match by @mnafees in [#3667](https://github.com/hatchet-dev/hatchet/pull/3667) ### Fixed - - Clean up a bunch of settings pages on the dashboard by @mrkaye97 in [#3669](https://github.com/hatchet-dev/hatchet/pull/3669) - Heights of event log, etc. on the dag view by @mrkaye97 in [#3687](https://github.com/hatchet-dev/hatchet/pull/3687) - Significantly speed up event queries by @mrkaye97 in [#3688](https://github.com/hatchet-dev/hatchet/pull/3688) + ## v0.83.37 - 2026-04-22 ### Fixed - - Collision on lastTenant key by @abelanger5 in [#3671](https://github.com/hatchet-dev/hatchet/pull/3671) - Use exchange token for getting plans by @abelanger5 in [#3668](https://github.com/hatchet-dev/hatchet/pull/3668) + ## v0.83.36 - 2026-04-22 ### Added - - Remove "account" dropdown, add notifications dropdown by @mrkaye97 in [#3665](https://github.com/hatchet-dev/hatchet/pull/3665) ### Fixed - - Webhook responses, event info on context, internal fix for labels matches by @mrkaye97 in [#3625](https://github.com/hatchet-dev/hatchet/pull/3625) + ## v0.83.35 - 2026-04-22 ### Removed - - Revert "Remove "account" dropdown', add notifications dropdown " by @mrkaye97 in [#3664](https://github.com/hatchet-dev/hatchet/pull/3664) + ## v0.83.34 - 2026-04-21 ### Changed - - Cleanup old workers via daily gocron by @mnafees in [#3663](https://github.com/hatchet-dev/hatchet/pull/3663) ### Removed - - Remove "account" dropdown', add notifications dropdown by @TehShrike in [#3365](https://github.com/hatchet-dev/hatchet/pull/3365) + ## v0.83.33 - 2026-04-21 ### Fixed - - Disable version info for control plane, add sync for tenant alerting settings by @abelanger5 in [#3659](https://github.com/hatchet-dev/hatchet/pull/3659) + ## v0.83.32 - 2026-04-21 ### Added - - Durable execution frontend work and API improvements by @mrkaye97 in [#3639](https://github.com/hatchet-dev/hatchet/pull/3639) + ## v0.83.31 - 2026-04-21 ### Fixed - - Use exchange token interceptor for cloud endpoints by @abelanger5 in [#3658](https://github.com/hatchet-dev/hatchet/pull/3658) + ## v0.83.30 - 2026-04-21 ### Fixed - - Set cloud enabled when control plane is enabled by @abelanger5 in [#3657](https://github.com/hatchet-dev/hatchet/pull/3657) + ## v0.83.29 - 2026-04-21 ### Fixed - - Fix control-plane logout bug, fix oauth redirect urls by @juliusgeo in [#3656](https://github.com/hatchet-dev/hatchet/pull/3656) + ## v0.83.28 - 2026-04-21 ### Changed - - Conditionally use control-plane metadata endpoint by @juliusgeo in [#3654](https://github.com/hatchet-dev/hatchet/pull/3654) + ## v0.83.27 - 2026-04-20 ### Added - - Rate limit deletion by @juliusgeo in [#3638](https://github.com/hatchet-dev/hatchet/pull/3638) ### Fixed - - Infinite rerender bug and handle 403 better by @abelanger5 in [#3652](https://github.com/hatchet-dev/hatchet/pull/3652) + ## v0.83.26 - 2026-04-20 ### Added - - Sync repository for syncing data into a tenant by @abelanger5 in [#3614](https://github.com/hatchet-dev/hatchet/pull/3614) ### Changed - - Generate insecure keysets locally by @juliusgeo in [#3622](https://github.com/hatchet-dev/hatchet/pull/3622) ### Fixed - - Exchange token authz, remove n+1 tenant lookup for organizations, slack oauth casing by @abelanger5 in [#3631](https://github.com/hatchet-dev/hatchet/pull/3631) + ## v0.83.25 - 2026-04-15 ### Fixed - - Configurable schedulerCheckActive Interval by @grutt in [#3624](https://github.com/hatchet-dev/hatchet/pull/3624) + ## v0.83.24 - 2026-04-15 ### Changed - - Rename PgBouncer env vars by @mnafees in [#3319](https://github.com/hatchet-dev/hatchet/pull/3319) ### Fixed - - Empty parent task external id on olap dags and runs by @mrkaye97 in [#3605](https://github.com/hatchet-dev/hatchet/pull/3605) - Broken doc links by @mrkaye97 in [#3616](https://github.com/hatchet-dev/hatchet/pull/3616) + ## v0.83.23 - 2026-04-13 ### Added - - No limit modal with no limits by @grutt in [#3572](https://github.com/hatchet-dev/hatchet/pull/3572) ### Changed - - Make rbac package generic and reusable by @abelanger5 in [#3581](https://github.com/hatchet-dev/hatchet/pull/3581) ### Fixed - - Add healthcheck to harness engine startup by @gregfurman in [#3601](https://github.com/hatchet-dev/hatchet/pull/3601) + ## v0.83.22 - 2026-04-10 ### Removed - - Run actions on gha runners again by @mrkaye97 in [#3588](https://github.com/hatchet-dev/hatchet/pull/3588) ### Fixed - - Worker labels not respected on retry by @mrkaye97 in [#3591](https://github.com/hatchet-dev/hatchet/pull/3591) - Run payloads job at midnight by @mrkaye97 in [#3578](https://github.com/hatchet-dev/hatchet/pull/3578) + ## v0.83.18 - 2026-04-07 ### Added - - Wait for event with lookback window by @mrkaye97 in [#3442](https://github.com/hatchet-dev/hatchet/pull/3442) + ## v0.83.17 - 2026-04-07 ### Added - - Feat--offers by @grutt in [#3511](https://github.com/hatchet-dev/hatchet/pull/3511) + ## v0.83.15 - 2026-04-03 ### Added - - Exchange token mechanism and CORs headers by @abelanger5 in [#3405](https://github.com/hatchet-dev/hatchet/pull/3405) - Control plane phase 2, frontend changes by @abelanger5 in [#3536](https://github.com/hatchet-dev/hatchet/pull/3536) ### Fixed - - OTel trace lookup insert deadlock by @mrkaye97 in [#3542](https://github.com/hatchet-dev/hatchet/pull/3542) + ## v0.83.14 - 2026-04-03 ### Fixed - - Properly list workflows in dropdown by @mrkaye97 in [#3534](https://github.com/hatchet-dev/hatchet/pull/3534) + ## v0.83.13 - 2026-04-03 ### Added - - Enable event log on core db by @mrkaye97 in [#3537](https://github.com/hatchet-dev/hatchet/pull/3537) - Add output tab to workflow run details by @grutt in [#3393](https://github.com/hatchet-dev/hatchet/pull/3393) ### Fixed - - Fix--dag-nesting by @grutt in [#3447](https://github.com/hatchet-dev/hatchet/pull/3447) + ## v0.83.10 - 2026-04-01 ### Fixed - - No self-referencing spans by @grutt in [#3445](https://github.com/hatchet-dev/hatchet/pull/3445) + ## v0.83.9 - 2026-04-01 ### Fixed - - Otel-bug-bash by @grutt in [#3389](https://github.com/hatchet-dev/hatchet/pull/3389) + ## v0.83.8 - 2026-04-01 ### Changed - - Update management API swagger with audit logs endpoint by @mnafees in [#3414](https://github.com/hatchet-dev/hatchet/pull/3414) + ## v0.83.7 - 2026-04-01 ### Changed - - Backend proxy for getting feature flags by @mrkaye97 in [#3437](https://github.com/hatchet-dev/hatchet/pull/3437) + ## v0.83.5 - 2026-04-01 ### Added - - Log filtering by workflow by @mrkaye97 in [#3435](https://github.com/hatchet-dev/hatchet/pull/3435) ### Changed - - Do not error out for historical run data for workflows and workflow versions that may have been deleted by @mnafees in [#3403](https://github.com/hatchet-dev/hatchet/pull/3403) ### Fixed - - Flaky Cypress test by @mrkaye97 in [#3436](https://github.com/hatchet-dev/hatchet/pull/3436) + ## v0.83.4 - 2026-03-26 ### Added - - Add more detailed logging to in memory advisory lock, configurable timeout by @juliusgeo in [#3408](https://github.com/hatchet-dev/hatchet/pull/3408) + ## v0.83.3 - 2026-03-25 ### Changed - - (fix): Make sure sqlc generates TotalReads as int64 by @juliusgeo in [#3404](https://github.com/hatchet-dev/hatchet/pull/3404) + ## v0.83.2 - 2026-03-25 ### Added - - Add in memory, keyed, locking queue to protect RunConcurrencyStrategy by @juliusgeo in [#3384](https://github.com/hatchet-dev/hatchet/pull/3384) + ## v0.83.1 - 2026-03-25 ### Added - - Add timeout context to OTel shutdown by @juliusgeo in [#3366](https://github.com/hatchet-dev/hatchet/pull/3366) ### Changed - - Use inserted at for proper indexing in DAG replay queries by @mnafees in [#3399](https://github.com/hatchet-dev/hatchet/pull/3399) ### Fixed - - Panic on lookup table create by @grutt in [#3385](https://github.com/hatchet-dev/hatchet/pull/3385) + ## v0.83.0 - 2026-03-24 ### Added - - Enable tenant-scoped logs view by @abelanger5 in [#3381](https://github.com/hatchet-dev/hatchet/pull/3381) ### Changed - - Get rid of jsonschema for security fix by @mnafees in [#3379](https://github.com/hatchet-dev/hatchet/pull/3379) + ## v0.82.3 - 2026-03-23 ### Changed - - Hide log-level view by @abelanger5 in [#3374](https://github.com/hatchet-dev/hatchet/pull/3374) ### Fixed - - Bump engine ver by @mrkaye97 in [#3373](https://github.com/hatchet-dev/hatchet/pull/3373) + ## v0.82.2 - 2026-03-23 ### Fixed - - Write engine spans to lookup table by @grutt in [#3371](https://github.com/hatchet-dev/hatchet/pull/3371) + ## v0.82.1 - 2026-03-23 ### Changed - - Move OTel tables to OLAP repo by @mnafees in [#3369](https://github.com/hatchet-dev/hatchet/pull/3369) - Don't flicker the runs table when refetching by @TehShrike in [#3367](https://github.com/hatchet-dev/hatchet/pull/3367) + ## v0.82.0 - 2026-03-23 ### Added - - Pay as you go pricing by @grutt in [#3353](https://github.com/hatchet-dev/hatchet/pull/3353) ### Changed - - Observability overhaul + traces support by @mnafees in [#3213](https://github.com/hatchet-dev/hatchet/pull/3213) - Improve error message when failing to send task to worker by @juliusgeo in [#3350](https://github.com/hatchet-dev/hatchet/pull/3350) - Some fixes for load test plot generation by @juliusgeo in [#3338](https://github.com/hatchet-dev/hatchet/pull/3338) ### Removed - - Remove server ShutdownWait by @juliusgeo in [#3351](https://github.com/hatchet-dev/hatchet/pull/3351) ### Fixed - - Fallback when Tenant ID is zero UUID by @gregfurman in [#3349](https://github.com/hatchet-dev/hatchet/pull/3349) + ## v0.81.2 - 2026-03-19 ### Added - - Add min height to logs chart by @abelanger5 in [#3337](https://github.com/hatchet-dev/hatchet/pull/3337) - Add latency plots to load test by @juliusgeo in [#3259](https://github.com/hatchet-dev/hatchet/pull/3259) ### Changed - - Only return non-archived tenants in TenantMember queries by @TehShrike in [#3317](https://github.com/hatchet-dev/hatchet/pull/3317) + ## v0.81.0 - 2026-03-18 ### Added - - Tenant-scoped logs view by @abelanger5 in [#3307](https://github.com/hatchet-dev/hatchet/pull/3307) + ## v0.80.10 - 2026-03-18 ### Changed - - Separate out partition cleanup helpers for visibility by @juliusgeo in [#3321](https://github.com/hatchet-dev/hatchet/pull/3321) + ## v0.80.9 - 2026-03-18 ### Fixed - - Honor active docker context when DOCKER_HOST is unset by @BloggerBust in [#3251](https://github.com/hatchet-dev/hatchet/pull/3251) + ## v0.80.8 - 2026-03-18 ### Changed - - Guarantee that organization tenants will always be an array by @TehShrike in [#3316](https://github.com/hatchet-dev/hatchet/pull/3316) - New organizations+tenants screen by @TehShrike in [#3198](https://github.com/hatchet-dev/hatchet/pull/3198) - Improved invitation accept screen by @TehShrike in [#3151](https://github.com/hatchet-dev/hatchet/pull/3151) ### Removed - - Remove dispatch backlog, replace with timeout lock acquisition by @juliusgeo in [#3290](https://github.com/hatchet-dev/hatchet/pull/3290) ### Fixed - - Cleanup orphaned metrics by @grutt in [#3300](https://github.com/hatchet-dev/hatchet/pull/3300) + ## v0.80.4 - 2026-03-17 ### Added - - Add `workflow_run_external_id` to trigger run ack proto by @mrkaye97 in [#3299](https://github.com/hatchet-dev/hatchet/pull/3299) ### Fixed - - Enhance SQL query name extraction in otel tracer and fallback by @grutt in [#3277](https://github.com/hatchet-dev/hatchet/pull/3277) - Silence tenant alert error by @grutt in [#3298](https://github.com/hatchet-dev/hatchet/pull/3298) + ## v0.80.3 - 2026-03-16 ### Added - - Add cleanup module to handle graceful shutdown, improve logging experience by @juliusgeo in [#3260](https://github.com/hatchet-dev/hatchet/pull/3260) - Durable Execution Revamp by @mrkaye97 in [#2954](https://github.com/hatchet-dev/hatchet/pull/2954) ### Fixed - - Remove join in currency queries by @grutt in [#3294](https://github.com/hatchet-dev/hatchet/pull/3294) + ## v0.79.44 - 2026-03-15 ### Added - - Add support for additional RBAC configruation via YAML configuration by @grutt in [#3285](https://github.com/hatchet-dev/hatchet/pull/3285) ### Changed - - Gracefully handle empty bulk scheduled deletes by @avirajkhare00 in [#3279](https://github.com/hatchet-dev/hatchet/pull/3279) ### Fixed - - Sort registered workflows on the worker page by @mrkaye97 in [#3284](https://github.com/hatchet-dev/hatchet/pull/3284) - Validate the presence of null unicode in output by @gregfurman in [#3164](https://github.com/hatchet-dev/hatchet/pull/3164) - Configure logger for Hatchet client by @gregfurman in [#3046](https://github.com/hatchet-dev/hatchet/pull/3046) + ## v0.79.43 - 2026-03-13 ### Fixed - - Delete missing workers by @mrkaye97 in [#3273](https://github.com/hatchet-dev/hatchet/pull/3273) - Dont double count wf by @grutt in [#3270](https://github.com/hatchet-dev/hatchet/pull/3270) + ## v0.79.41 - 2026-03-13 ### Changed - - Attempt to fix deadlock in `PollScheduledWorkflows` by scoping `FOR UPDATE` lock by @mnafees in [#3261](https://github.com/hatchet-dev/hatchet/pull/3261) - Janky Fix: Extract input payloads for standalone tasks for dashboard by @mrkaye97 in [#3128](https://github.com/hatchet-dev/hatchet/pull/3128) + ## v0.79.39 - 2026-03-13 ### Fixed - - Error on max aggregate keys by @grutt in [#3267](https://github.com/hatchet-dev/hatchet/pull/3267) - Otel config loader and trunc query name by @grutt in [#3266](https://github.com/hatchet-dev/hatchet/pull/3266) + ## v0.79.38 - 2026-03-13 ### Fixed - - Analytics no set by @grutt in [#3264](https://github.com/hatchet-dev/hatchet/pull/3264) - Add user id to context on login by @grutt in [#3256](https://github.com/hatchet-dev/hatchet/pull/3256) + ## v0.79.37 - 2026-03-12 ### Fixed - - Dont burry analytics properties by @grutt in [#3254](https://github.com/hatchet-dev/hatchet/pull/3254) + ## v0.79.36 - 2026-03-12 ### Added - - Feat--consistent-analytics-events by @grutt in [#3239](https://github.com/hatchet-dev/hatchet/pull/3239) + ## v0.79.35 - 2026-03-12 ### Changed - - Pool gzip writers to reduce RabbitMQ message compression allocations by @mnafees in [#3103](https://github.com/hatchet-dev/hatchet/pull/3103) + ## v0.79.34 - 2026-03-12 ### Added - - Env var for stream event buffer timeout by @mrkaye97 in [#3223](https://github.com/hatchet-dev/hatchet/pull/3223) ### Changed - - Don't panic in AuthZ, bubble up instead by @juliusgeo in [#3238](https://github.com/hatchet-dev/hatchet/pull/3238) ### Fixed - - Failure after cancellation by @grutt in [#3243](https://github.com/hatchet-dev/hatchet/pull/3243) - Fix owner invitation bug by @juliusgeo in [#3230](https://github.com/hatchet-dev/hatchet/pull/3230) + ## v0.79.33 - 2026-03-10 ### Changed - - RBAC v0 by @juliusgeo in [#3185](https://github.com/hatchet-dev/hatchet/pull/3185) ### Fixed - - Fix frontend build issues with latest cloud API by @mnafees in [#3216](https://github.com/hatchet-dev/hatchet/pull/3216) + ## v0.79.29 - 2026-03-07 ### Added - - Add callback support for tenant and tenant member updates by @abelanger5 in [#3201](https://github.com/hatchet-dev/hatchet/pull/3201) + ## v0.79.23 - 2026-03-06 ### Changed - - Pause workflow query is deprecated so remove the option from the frontend by @mnafees in [#3183](https://github.com/hatchet-dev/hatchet/pull/3183) + ## v0.79.17 - 2026-03-04 ### Fixed - - Go unexported type by @mrkaye97 in [#3160](https://github.com/hatchet-dev/hatchet/pull/3160) + ## v0.79.16 - 2026-03-04 ### Added - - Add queue to update scheduled cron triggers on-demand by @juliusgeo in [#3149](https://github.com/hatchet-dev/hatchet/pull/3149) + ## v0.79.15 - 2026-03-04 ### Added - - Dynamic worker label assign by @mrkaye97 in [#3137](https://github.com/hatchet-dev/hatchet/pull/3137) + ## v0.79.14 - 2026-03-03 ### Added - - Add seconds granularity to cron jobs by @juliusgeo in [#3136](https://github.com/hatchet-dev/hatchet/pull/3136) ### Changed - - Enable loadtest with PgBouncer by @mnafees in [#3143](https://github.com/hatchet-dev/hatchet/pull/3143) + ## v0.79.13 - 2026-03-02 ### Added - - User callback additional methods by @abelanger5 in [#3057](https://github.com/hatchet-dev/hatchet/pull/3057) + ## v0.79.12 - 2026-02-28 ### Fixed - - More small tenant switching + z index issues by @mrkaye97 in [#3124](https://github.com/hatchet-dev/hatchet/pull/3124) - Rm z index for action dialog by @mrkaye97 in [#3120](https://github.com/hatchet-dev/hatchet/pull/3120) + ## v0.79.11 - 2026-02-27 ### Added - - New "create organization" and "create tenant" interfaces by @TehShrike in [#3068](https://github.com/hatchet-dev/hatchet/pull/3068) ### Fixed - - Modals should appear above the mobile sidebar by @TehShrike in [#3114](https://github.com/hatchet-dev/hatchet/pull/3114) + ## v0.79.10 - 2026-02-26 ### Fixed - - External ids by @mrkaye97 in [#3111](https://github.com/hatchet-dev/hatchet/pull/3111) + ## v0.79.9 - 2026-02-26 ### Added - - Add `ctx.WasSkipped` helper to the Go SDK by @mnafees in [#3094](https://github.com/hatchet-dev/hatchet/pull/3094) - Add credit balance query and display in subscription component by @grutt in [#3107](https://github.com/hatchet-dev/hatchet/pull/3107) + ## v0.79.7 - 2026-02-25 ### Changed - - Non blocking ctx.Log with meaningful retries by @mnafees in [#3106](https://github.com/hatchet-dev/hatchet/pull/3106) + ## v0.79.6 - 2026-02-25 ### Changed - - [Go] Feat: Details Getter by @mrkaye97 in [#3105](https://github.com/hatchet-dev/hatchet/pull/3105) + ## v0.79.5 - 2026-02-24 ### Added - - Add env vars for max conn lifetime and idle time for pgx by @mnafees in [#3096](https://github.com/hatchet-dev/hatchet/pull/3096) + ## v0.79.4 - 2026-02-24 ### Added - - Add missing primary key to `"WorkflowTriggerCronRef"` by @mnafees in [#3086](https://github.com/hatchet-dev/hatchet/pull/3086) ### Fixed - - Fix cross-strategy slot contamination in chained concurrency gates by @mnafees in [#3089](https://github.com/hatchet-dev/hatchet/pull/3089) + ## v0.79.3 - 2026-02-23 ### Changed - - Make sure to use 60 seconds timeout for PutWorkflowVersion by @mnafees in [#3085](https://github.com/hatchet-dev/hatchet/pull/3085) + ## v0.79.2 - 2026-02-23 ### Fixed - - Move event log to a tab on the task run detail by @mrkaye97 in [#3067](https://github.com/hatchet-dev/hatchet/pull/3067) + ## v0.79.1 - 2026-02-21 ### Added - - Add new agent instruction tools by @grutt in [#3059](https://github.com/hatchet-dev/hatchet/pull/3059) ### Fixed - - Expand long logs on click instead of on hover by @mrkaye97 in [#3069](https://github.com/hatchet-dev/hatchet/pull/3069) - Input in transformer by @mrkaye97 in [#3070](https://github.com/hatchet-dev/hatchet/pull/3070) + ## v0.79.0 - 2026-02-20 ### Added - - Runs commands by @abelanger5 in [#3058](https://github.com/hatchet-dev/hatchet/pull/3058) + ## v0.78.30 - 2026-02-19 ### Added - - Add migration for worker slot config index by @grutt in [#3062](https://github.com/hatchet-dev/hatchet/pull/3062) + ## v0.78.27 - 2026-02-18 ### Added - - New search bar component by @abelanger5 in [#2909](https://github.com/hatchet-dev/hatchet/pull/2909) - Multiple slot types by @grutt in [#2927](https://github.com/hatchet-dev/hatchet/pull/2927) ### Fixed - - Fix--durable-slot-acquisition by @grutt in [#3048](https://github.com/hatchet-dev/hatchet/pull/3048) + ## v0.78.26 - 2026-02-16 ### Added - - Feat--llm-readable-docs by @grutt in [#3030](https://github.com/hatchet-dev/hatchet/pull/3030) - Official Ruby SDK by @grutt in [#3004](https://github.com/hatchet-dev/hatchet/pull/3004) ### Changed - - Return event ID after successful webhook trigger by @mnafees in [#3039](https://github.com/hatchet-dev/hatchet/pull/3039) + ## v0.78.25 - 2026-02-13 ### Added - - Add python and typescript webhook client by @jishnundth in [#2959](https://github.com/hatchet-dev/hatchet/pull/2959) ### Changed - - [hotfix] Corrected custom value meter for resource limit by @mnafees in [#3021](https://github.com/hatchet-dev/hatchet/pull/3021) - New `UpdateLimits` method for `TenantResourceLimit` table by @mnafees in [#2895](https://github.com/hatchet-dev/hatchet/pull/2895) + ## v0.78.23 - 2026-02-13 ### Changed - - More deprecation messages for older Go SDKs by @mnafees in [#3006](https://github.com/hatchet-dev/hatchet/pull/3006) ### Fixed - - DAG height on TUI by @abelanger5 in [#3019](https://github.com/hatchet-dev/hatchet/pull/3019) - Remove null bytes from error message to prevent db crash by @mrkaye97 in [#3010](https://github.com/hatchet-dev/hatchet/pull/3010) + ## v0.78.22 - 2026-02-11 ### Changed - - Truncate to first 10k characters of log line in Go SDK by @mnafees in [#2998](https://github.com/hatchet-dev/hatchet/pull/2998) + ## v0.78.21 - 2026-02-11 ### Added - - Reduced cold starts for new workers and queues by @abelanger5 in [#2969](https://github.com/hatchet-dev/hatchet/pull/2969) + ## v0.78.20 - 2026-02-11 ### Added - - Add support for Svix webhooks by @mnafees in [#2996](https://github.com/hatchet-dev/hatchet/pull/2996) + ## v0.78.19 - 2026-02-11 ### Added - - Log on delayed heartbeat by @grutt in [#2994](https://github.com/hatchet-dev/hatchet/pull/2994) + ## v0.78.18 - 2026-02-10 ### Changed - - Do not replay invalid tasks by @mnafees in [#2976](https://github.com/hatchet-dev/hatchet/pull/2976) + ## v0.78.16 - 2026-02-10 ### Changed - - [hotfix] Fix Docker frontend build issue by @mnafees in [#2983](https://github.com/hatchet-dev/hatchet/pull/2983) - [hotfix] Fix `BillingRequired` component by @mnafees in [#2982](https://github.com/hatchet-dev/hatchet/pull/2982) - Tasks marked as skipped in the UI by @mnafees in [#2978](https://github.com/hatchet-dev/hatchet/pull/2978) - Mark old v0 and generics-based v1 Go SDK methods as deprecated by @mnafees in [#2962](https://github.com/hatchet-dev/hatchet/pull/2962) + ## v0.78.12 - 2026-02-05 ### Fixed - - Check `uuid.Nil` when creating or groups too by @mrkaye97 in [#2958](https://github.com/hatchet-dev/hatchet/pull/2958) - Wrapped types in the Go SDK by @abelanger5 in [#2957](https://github.com/hatchet-dev/hatchet/pull/2957) + ## v0.78.11 - 2026-02-05 ### Fixed - - Always generate UUID for OrGroup by @mrkaye97 in [#2955](https://github.com/hatchet-dev/hatchet/pull/2955) + ## v0.78.10 - 2026-02-05 ### Fixed - - More panics by @mrkaye97 in [#2945](https://github.com/hatchet-dev/hatchet/pull/2945) + ## v0.78.9 - 2026-02-04 ### Fixed - - Make uuid optional for desired worker by @mrkaye97 in [#2946](https://github.com/hatchet-dev/hatchet/pull/2946) + ## v0.78.8 - 2026-02-04 ### Changed - - Go SDK gRPC client reconnection improvements by @mnafees in [#2934](https://github.com/hatchet-dev/hatchet/pull/2934) ### Fixed - - UUID Panics by @mrkaye97 in [#2944](https://github.com/hatchet-dev/hatchet/pull/2944) + ## v0.78.7 - 2026-02-04 ### Added - - Extend webhook support for scope_expression and payload by @jishnundth in [#2874](https://github.com/hatchet-dev/hatchet/pull/2874) + ## v0.78.6 - 2026-02-04 ### Fixed - - Don't cast user id in session.Values by @mrkaye97 in [#2937](https://github.com/hatchet-dev/hatchet/pull/2937) + ## v0.78.5 - 2026-02-04 ### Fixed - - Resource ID type by @mrkaye97 in [#2929](https://github.com/hatchet-dev/hatchet/pull/2929) + ## v0.78.4 - 2026-02-04 ### Changed - - Use typed maps by @abelanger5 in [#2928](https://github.com/hatchet-dev/hatchet/pull/2928) + ## v0.78.3 - 2026-02-03 ### Fixed - - Explicit use of tx in olap readPayloads by @abelanger5 in [#2925](https://github.com/hatchet-dev/hatchet/pull/2925) + ## v0.78.2 - 2026-02-03 ### Fixed - - Startup for optimistic scheduler by @abelanger5 in [#2924](https://github.com/hatchet-dev/hatchet/pull/2924) + ## v0.78.0 - 2026-02-03 ### Added - - Durable user event log by @abelanger5 in [#2861](https://github.com/hatchet-dev/hatchet/pull/2861) - Email alert support via SMTP by @gregfurman in [#2868](https://github.com/hatchet-dev/hatchet/pull/2868) ### Changed - - Attempt II at removing `pgtype.UUID` everywhere + convert string UUIDs into `uuid.UUID` by @mrkaye97 in [#2894](https://github.com/hatchet-dev/hatchet/pull/2894) - Management tokens that never expire by @mnafees in [#2889](https://github.com/hatchet-dev/hatchet/pull/2889) ### Fixed - - Add back SendTemplateEmail methods, fix postmark emails by @abelanger5 in [#2910](https://github.com/hatchet-dev/hatchet/pull/2910) - Dag update distinct locks by @grutt in [#2903](https://github.com/hatchet-dev/hatchet/pull/2903) - Only fetch finalized workflow runs by @grutt in [#2896](https://github.com/hatchet-dev/hatchet/pull/2896) - Compute payload size correctly for pg_notify by @abelanger5 in [#2873](https://github.com/hatchet-dev/hatchet/pull/2873) - Orphaned inactive queues by @grutt in [#2893](https://github.com/hatchet-dev/hatchet/pull/2893) + ## v0.77.37 - 2026-01-30 ### Changed - - Log Search Frontend, Part II by @mrkaye97 in [#2886](https://github.com/hatchet-dev/hatchet/pull/2886) + ## v0.77.36 - 2026-01-29 ### Changed - - Make sure we query for pending invites in the create tenant page by @mnafees in [#2883](https://github.com/hatchet-dev/hatchet/pull/2883) + ## v0.77.35 - 2026-01-29 ### Added - - Log Search Frontend, Part I by @mrkaye97 in [#2830](https://github.com/hatchet-dev/hatchet/pull/2830) + ## v0.77.34 - 2026-01-29 ### Fixed - - Tenant resource limit resource col migration by @grutt in [#2885](https://github.com/hatchet-dev/hatchet/pull/2885) + ## v0.77.33 - 2026-01-29 ### Added - - Workflow input JSON schema in trigger preview by @mrkaye97 in [#2851](https://github.com/hatchet-dev/hatchet/pull/2851) ### Fixed - - Validate json at edges and dont retry on invalid by @grutt in [#2882](https://github.com/hatchet-dev/hatchet/pull/2882) - Typo by @grutt in [#2875](https://github.com/hatchet-dev/hatchet/pull/2875) - Big int alignment for cleanup function by @grutt in [#2877](https://github.com/hatchet-dev/hatchet/pull/2877) + ## v0.77.32 - 2026-01-28 ### Added - - OTel Collector by @mrkaye97 in [#2863](https://github.com/hatchet-dev/hatchet/pull/2863) + ## v0.77.30 - 2026-01-27 ### Fixed - - Make metrics in graph align with badge metrics by @mrkaye97 in [#2858](https://github.com/hatchet-dev/hatchet/pull/2858) + ## v0.77.25 - 2026-01-26 ### Removed - - Revert "fix: make point metrics line up with badges " by @mrkaye97 in [#2857](https://github.com/hatchet-dev/hatchet/pull/2857) + ## v0.77.24 - 2026-01-26 ### Fixed - - Fix with coalesce by @mnafees in [#2856](https://github.com/hatchet-dev/hatchet/pull/2856) - Make point metrics line up with badges by @mrkaye97 in [#2739](https://github.com/hatchet-dev/hatchet/pull/2739) + ## v0.77.23 - 2026-01-26 ### Added - - Add order by direction param to v1LogLineList by @mrkaye97 in [#2849](https://github.com/hatchet-dev/hatchet/pull/2849) ### Changed - - More tenant related repo methods by @mnafees in [#2854](https://github.com/hatchet-dev/hatchet/pull/2854) + ## v0.77.21 - 2026-01-23 ### Changed - - [Go] Feat: Webhooks feature client for the Go SDK by @mrkaye97 in [#2792](https://github.com/hatchet-dev/hatchet/pull/2792) + ## v0.77.16 - 2026-01-21 ### Fixed - - List concurrency strategies queries by @grutt in [#2838](https://github.com/hatchet-dev/hatchet/pull/2838) + ## v0.77.13 - 2026-01-21 ### Added - - Add search and levels to logs API by @mrkaye97 in [#2835](https://github.com/hatchet-dev/hatchet/pull/2835) ### Changed - - Take exclusive lock by @mnafees in [#2837](https://github.com/hatchet-dev/hatchet/pull/2837) - Updates by @grutt in [#2827](https://github.com/hatchet-dev/hatchet/pull/2827) ### Fixed - - Flaky integration test by @grutt in [#2834](https://github.com/hatchet-dev/hatchet/pull/2834) + ## v0.77.9 - 2026-01-21 ### Changed - - [hotfix] Indicate unauthorized access to billing page for non-owners by @mnafees in [#2833](https://github.com/hatchet-dev/hatchet/pull/2833) + ## v0.77.5 - 2026-01-20 ### Changed - - Have log line lookups use external id by @abelanger5 in [#2822](https://github.com/hatchet-dev/hatchet/pull/2822) + ## v0.77.1 - 2026-01-19 ### Fixed - - Fix naming of migration by @mnafees in [#2819](https://github.com/hatchet-dev/hatchet/pull/2819) + ## v0.77.0 - 2026-01-19 ### Changed - - Billing changes by @mnafees in [#2643](https://github.com/hatchet-dev/hatchet/pull/2643) + ## v0.75.4 - 2026-01-18 ### Changed - - Shrink task metrics chart a bunch by @mrkaye97 in [#2738](https://github.com/hatchet-dev/hatchet/pull/2738) ### Fixed - - Typescript post-quickstart by @abelanger5 in [#2809](https://github.com/hatchet-dev/hatchet/pull/2809) + ## v0.75.3 - 2026-01-16 ### Fixed - - Minor quickstart issues by @abelanger5 in [#2807](https://github.com/hatchet-dev/hatchet/pull/2807) + ## v0.75.2 - 2026-01-16 ### Added - - Update quickstarts with package manager support, e2e tests for quickstarts by @abelanger5 in [#2801](https://github.com/hatchet-dev/hatchet/pull/2801) ### Changed - - New onboarding flow by @mnafees in [#2757](https://github.com/hatchet-dev/hatchet/pull/2757) - Auth front-end changes by @sebastiangraz in [#2802](https://github.com/hatchet-dev/hatchet/pull/2802) + ## v0.75.1 - 2026-01-15 ### Changed - - Small fixes/improvements to CLI logic by @abelanger5 in [#2793](https://github.com/hatchet-dev/hatchet/pull/2793) + ## v0.75.0 - 2026-01-13 ### Added - - Hatchet cli by @abelanger5 in [#2701](https://github.com/hatchet-dev/hatchet/pull/2701) + ## v0.74.14 - 2026-01-12 ### Fixed - - Concurrency display on workflow page by @mrkaye97 in [#2780](https://github.com/hatchet-dev/hatchet/pull/2780) + ## v0.74.13 - 2026-01-12 ### Added - - Add additional meta to the run detail getter by @mrkaye97 in [#2770](https://github.com/hatchet-dev/hatchet/pull/2770) ### Fixed - - Regression on v0 PutWorkflow for scheduling timeout by @abelanger5 in [#2779](https://github.com/hatchet-dev/hatchet/pull/2779) + ## v0.74.12 - 2026-01-11 ### Fixed - - Statement timeout by @mrkaye97 in [#2774](https://github.com/hatchet-dev/hatchet/pull/2774) - Actually reconnect to postgres if conn fails. by @m-kostrzewa in [#2772](https://github.com/hatchet-dev/hatchet/pull/2772) + ## v0.74.11 - 2026-01-10 ### Added - - Run detail getter on the engine by @mrkaye97 in [#2725](https://github.com/hatchet-dev/hatchet/pull/2725) ### Fixed - - Worker id by @mrkaye97 in [#2773](https://github.com/hatchet-dev/hatchet/pull/2773) + ## v0.74.8 - 2026-01-08 ### Fixed - - Better error on deprecated endpoints by @abelanger5 in [#2763](https://github.com/hatchet-dev/hatchet/pull/2763) - Chunk and recursively retry too-large message sends by @mrkaye97 in [#2761](https://github.com/hatchet-dev/hatchet/pull/2761) + ## v0.74.7 - 2026-01-07 ### Fixed - - Un-hard-code location by @mrkaye97 in [#2760](https://github.com/hatchet-dev/hatchet/pull/2760) + ## v0.74.6 - 2026-01-07 ### Fixed - - Payload location issue by @mrkaye97 in [#2759](https://github.com/hatchet-dev/hatchet/pull/2759) - Child runs counts missing filter by @mrkaye97 in [#2744](https://github.com/hatchet-dev/hatchet/pull/2744) + ## v0.74.5 - 2026-01-06 ### Changed - - Send `create:user` Event from OAuth Flow by @undrash in [#2683](https://github.com/hatchet-dev/hatchet/pull/2683) ### Removed - - UI version removal by @abelanger5 in [#2756](https://github.com/hatchet-dev/hatchet/pull/2756) + ## v0.74.4 - 2026-01-06 ### Changed - - Use try advisory for replat tasks by @mnafees in [#2755](https://github.com/hatchet-dev/hatchet/pull/2755) + ## v0.74.3 - 2026-01-06 ### Changed - - Set a connection-level statement timeout by @abelanger5 in [#2750](https://github.com/hatchet-dev/hatchet/pull/2750) - Move v1 packages, remove webhook worker references by @abelanger5 in [#2749](https://github.com/hatchet-dev/hatchet/pull/2749) ### Fixed - - More frontend work - table cleanup, worker detail page improvements, etc. by @mrkaye97 in [#2746](https://github.com/hatchet-dev/hatchet/pull/2746) + ## v0.74.2 - 2025-12-31 ### Changed - - Consolidate repository methods by @abelanger5 in [#2730](https://github.com/hatchet-dev/hatchet/pull/2730) + ## v0.74.1 - 2025-12-31 ### Changed - - Remove v0-exclusive database queries by @abelanger5 in [#2729](https://github.com/hatchet-dev/hatchet/pull/2729) - Remove v0 paths from codebase by @abelanger5 in [#2728](https://github.com/hatchet-dev/hatchet/pull/2728) ### Fixed - - Handle panic by @mrkaye97 in [#2732](https://github.com/hatchet-dev/hatchet/pull/2732) - Frozen refetch state gets stuck by @mrkaye97 in [#2736](https://github.com/hatchet-dev/hatchet/pull/2736) - Remove action dropdown, fix a couple broken tooltips by @mrkaye97 in [#2737](https://github.com/hatchet-dev/hatchet/pull/2737) + ## v0.73.110 - 2025-12-26 ### Fixed - - Layout by @mrkaye97 in [#2724](https://github.com/hatchet-dev/hatchet/pull/2724) + ## v0.73.109 - 2025-12-26 ### Fixed - - Tenant invite accept flow by @mrkaye97 in [#2723](https://github.com/hatchet-dev/hatchet/pull/2723) - Goroutine to periodically extend lease during reconciliation by @mrkaye97 in [#2722](https://github.com/hatchet-dev/hatchet/pull/2722) + ## v0.73.108 - 2025-12-26 ### Fixed - - Rename migration by @mrkaye97 in [#2721](https://github.com/hatchet-dev/hatchet/pull/2721) + ## v0.73.107 - 2025-12-26 ### Changed - - Publish `COULD_NOT_SEND_TO_WORKER` OLAP event due to worker backlog by @mnafees in [#2710](https://github.com/hatchet-dev/hatchet/pull/2710) - Reuse timers for delayed semaphore release in MQ buffers by @mnafees in [#2691](https://github.com/hatchet-dev/hatchet/pull/2691) - Msgqueue msg IDs as constants for ease of navigation and readability by @mnafees in [#2692](https://github.com/hatchet-dev/hatchet/pull/2692) ### Removed - - Revert "Revert "chore: run list query optimizations " " by @mrkaye97 in [#2720](https://github.com/hatchet-dev/hatchet/pull/2720) ### Fixed - - Static rate limits resetting to zero by @mrkaye97 in [#2714](https://github.com/hatchet-dev/hatchet/pull/2714) - Minor fe bugs and nits by @grutt in [#2711](https://github.com/hatchet-dev/hatchet/pull/2711) + ## v0.73.106 - 2025-12-23 ### Added - - Improved navigation by @grutt in [#2704](https://github.com/hatchet-dev/hatchet/pull/2704) - Hatchet Metrics Monitoring, I by @mrkaye97 in [#2699](https://github.com/hatchet-dev/hatchet/pull/2699) ### Removed - - Revert "chore: run list query optimizations " by @mrkaye97 in [#2708](https://github.com/hatchet-dev/hatchet/pull/2708) ### Fixed - - Rm cleanup logic for now by @mrkaye97 in [#2707](https://github.com/hatchet-dev/hatchet/pull/2707) - WithToken should override environment variable by @abelanger5 in [#2706](https://github.com/hatchet-dev/hatchet/pull/2706) - Fix move fixture by @grutt in [#2705](https://github.com/hatchet-dev/hatchet/pull/2705) + ## v0.73.104 - 2025-12-23 ### Added - - Improved error boundaries by @grutt in [#2689](https://github.com/hatchet-dev/hatchet/pull/2689) - Bulk management schedules by @grutt in [#2687](https://github.com/hatchet-dev/hatchet/pull/2687) ### Fixed - -- Fix layout bugs by @grutt in [#2703](https://github.com/hatchet-dev/hatchet/pull/2703) +- Fix layout bugs by @grutt in [#2703](https://github.com/hatchet-dev/hatchet/pull/2703) - Dead alert links by @mrkaye97 in [#2688](https://github.com/hatchet-dev/hatchet/pull/2688) + ## v0.73.103 - 2025-12-22 ### Fixed - - Dynamically-sized chunks on payload read by @mrkaye97 in [#2700](https://github.com/hatchet-dev/hatchet/pull/2700) + ## v0.73.102 - 2025-12-22 ### Removed - - Revert "Feat: Hatchet Metrics Monitoring, I " by @mrkaye97 in [#2698](https://github.com/hatchet-dev/hatchet/pull/2698) ### Fixed - - Table name by @mrkaye97 in [#2697](https://github.com/hatchet-dev/hatchet/pull/2697) + ## v0.73.101 - 2025-12-22 ### Added - - Hatchet Metrics Monitoring, I by @mrkaye97 in [#2480](https://github.com/hatchet-dev/hatchet/pull/2480) + ## v0.73.99 - 2025-12-22 ### Fixed - - Last bits of payload job cleanup by @mrkaye97 in [#2690](https://github.com/hatchet-dev/hatchet/pull/2690) - Rare cases of duplicate writes causing stuck updates by @abelanger5 in [#2681](https://github.com/hatchet-dev/hatchet/pull/2681) - Filter + pagination state handling hack by @mrkaye97 in [#2682](https://github.com/hatchet-dev/hatchet/pull/2682) + ## v0.73.98 - 2025-12-17 ### Added - - New event getter + janky v0 fix by @mrkaye97 in [#2667](https://github.com/hatchet-dev/hatchet/pull/2667) ### Changed - - Remove inline button styles where possible by @mrkaye97 in [#2671](https://github.com/hatchet-dev/hatchet/pull/2671) + ## v0.73.97 - 2025-12-16 ### Fixed - - Payload List Index Performance by @mrkaye97 in [#2669](https://github.com/hatchet-dev/hatchet/pull/2669) - Root redirect with last tenant atom by @mrkaye97 in [#2664](https://github.com/hatchet-dev/hatchet/pull/2664) + ## v0.73.96 - 2025-12-15 ### Fixed - - Pagination by bounds by @mrkaye97 in [#2654](https://github.com/hatchet-dev/hatchet/pull/2654) + ## v0.73.94 - 2025-12-11 ### Changed - - Write to S3 outside of goroutine by @mrkaye97 in [#2646](https://github.com/hatchet-dev/hatchet/pull/2646) + ## v0.73.93 - 2025-12-11 ### Fixed - - OLAP Immediate Offloads by @mrkaye97 in [#2644](https://github.com/hatchet-dev/hatchet/pull/2644) + ## v0.73.92 - 2025-12-11 ### Added - - Add oldest queued + running jobs to task stats by @mrkaye97 in [#2638](https://github.com/hatchet-dev/hatchet/pull/2638) + ## v0.73.91 - 2025-12-10 ### Added - - Parallelize replication from PG -> External by @mrkaye97 in [#2637](https://github.com/hatchet-dev/hatchet/pull/2637) + ## v0.73.90 - 2025-12-10 ### Fixed - - Global Lease for OLAP by @mrkaye97 in [#2635](https://github.com/hatchet-dev/hatchet/pull/2635) + ## v0.73.89 - 2025-12-10 ### Fixed - - Query logic bug by @mrkaye97 in [#2631](https://github.com/hatchet-dev/hatchet/pull/2631) + ## v0.73.88 - 2025-12-10 ### Added - - Add support for slack slash commands in webhook by @sidpremkumar in [#2630](https://github.com/hatchet-dev/hatchet/pull/2630) ### Fixed - - Don't reset offset if a new process acquires lease by @mrkaye97 in [#2628](https://github.com/hatchet-dev/hatchet/pull/2628) + ## v0.73.87 - 2025-12-09 ### Added - - OLAP Payload Cutover Job by @mrkaye97 in [#2618](https://github.com/hatchet-dev/hatchet/pull/2618) + ## v0.73.86 - 2025-12-08 ### Changed - - Simplify external store signature by @mrkaye97 in [#2616](https://github.com/hatchet-dev/hatchet/pull/2616) + ## v0.73.85 - 2025-12-08 ### Changed - - Update Expression Page + Slack Webhook Onboarding by @sidpremkumar in [#2614](https://github.com/hatchet-dev/hatchet/pull/2614) ### Fixed - - Fix slack challenge + interactive webhook by @sidpremkumar in [#2612](https://github.com/hatchet-dev/hatchet/pull/2612) + ## v0.73.84 - 2025-12-08 ### Added - - Process all old partitions in a loop by @mrkaye97 in [#2613](https://github.com/hatchet-dev/hatchet/pull/2613) + ## v0.73.83 - 2025-12-08 ### Added - - Add guide for downgrading versions by @mnafees in [#2588](https://github.com/hatchet-dev/hatchet/pull/2588) + ## v0.73.82 - 2025-12-05 ### Fixed - - Add validation by @mrkaye97 in [#2610](https://github.com/hatchet-dev/hatchet/pull/2610) + ## v0.73.81 - 2025-12-05 ### Fixed - - Leasing for payload job by @mrkaye97 in [#2609](https://github.com/hatchet-dev/hatchet/pull/2609) + ## v0.73.80 - 2025-12-05 ### Added - - Job for payload cutovers to external by @mrkaye97 in [#2586](https://github.com/hatchet-dev/hatchet/pull/2586) - Dlq for dispatcher queues by @abelanger5 in [#2600](https://github.com/hatchet-dev/hatchet/pull/2600) ### Changed - - Initialize concurrency keys slice for replayed tasks by @mnafees in [#2549](https://github.com/hatchet-dev/hatchet/pull/2549) ### Fixed - - Fix double toast on sidebar by @sidpremkumar in [#2607](https://github.com/hatchet-dev/hatchet/pull/2607) + ## v0.73.78 - 2025-12-03 ### Changed - - Cross-Domain Tracking and Analytics Refactoring by @undrash in [#2587](https://github.com/hatchet-dev/hatchet/pull/2587) ### Fixed - - Prevent large worker gRPC stream backlogs by @abelanger5 in [#2597](https://github.com/hatchet-dev/hatchet/pull/2597) - Don't trigger posthog Pageview on query param changes by @undrash in [#2598](https://github.com/hatchet-dev/hatchet/pull/2598) - Load shed on slow worker backlogs by @abelanger5 in [#2595](https://github.com/hatchet-dev/hatchet/pull/2595) + ## v0.73.76 - 2025-12-02 ### Fixed - - Move check for large payloads to after json.Marshal by @abelanger5 in [#2594](https://github.com/hatchet-dev/hatchet/pull/2594) + ## v0.73.75 - 2025-12-02 ### Fixed - - Ensure that slow worker doesn't interrupt dispatcher, guard large RabbitMQ pubs by @abelanger5 in [#2591](https://github.com/hatchet-dev/hatchet/pull/2591) - GetLatestWorkflowVersionForWorkflows by @grutt in [#2590](https://github.com/hatchet-dev/hatchet/pull/2590) + ## v0.73.74 - 2025-11-28 ### Added - - Add sent to worker event in the dispatcher by @mrkaye97 in [#2584](https://github.com/hatchet-dev/hatchet/pull/2584) + ## v0.73.73 - 2025-11-26 ### Added - - Add gzip compression by @sidpremkumar in [#2539](https://github.com/hatchet-dev/hatchet/pull/2539) ### Fixed - - Noisy Payload Error by @mrkaye97 in [#2561](https://github.com/hatchet-dev/hatchet/pull/2561) + ## v0.73.72 - 2025-11-26 ### Added - - Add tooltip showing full step name on hover by @mrkaye97 in [#2563](https://github.com/hatchet-dev/hatchet/pull/2563) ### Changed - - Analyze v1 lookup table by @grutt in [#2568](https://github.com/hatchet-dev/hatchet/pull/2568) - Optimize UUID sqlchelpers by @mnafees in [#2532](https://github.com/hatchet-dev/hatchet/pull/2532) - Add spans to worker list handler by @grutt in [#2554](https://github.com/hatchet-dev/hatchet/pull/2554) ### Removed - - Revert "optimize UUID sqlchelpers " by @mrkaye97 in [#2571](https://github.com/hatchet-dev/hatchet/pull/2571) ### Fixed - - Query optimization get latest workflow version by @grutt in [#2576](https://github.com/hatchet-dev/hatchet/pull/2576) - OLAP Task Event Dual Write Bug by @mrkaye97 in [#2572](https://github.com/hatchet-dev/hatchet/pull/2572) - Add whitespace-pre to log by @H01001000 in [#2555](https://github.com/hatchet-dev/hatchet/pull/2555) + ## v0.73.71 - 2025-11-21 ### Changed - - Common pgxpool afterconnect method by @mnafees in [#2553](https://github.com/hatchet-dev/hatchet/pull/2553) + ## v0.73.70 - 2025-11-20 ### Changed - - [Go SDK] Resubscribe and get a new listener stream when gRPC connections fail by @mnafees in [#2544](https://github.com/hatchet-dev/hatchet/pull/2544) + ## v0.73.68 - 2025-11-18 ### Fixed - - Use sessionStorage instead of localStorage by @mrkaye97 in [#2541](https://github.com/hatchet-dev/hatchet/pull/2541) + ## v0.73.67 - 2025-11-18 ### Added - - Initial cross-domain identify setup by @mrkaye97 in [#2533](https://github.com/hatchet-dev/hatchet/pull/2533) ### Fixed - - Small scheduler optimizations by @abelanger5 in [#2426](https://github.com/hatchet-dev/hatchet/pull/2426) + ## v0.73.66 - 2025-11-17 ### Added - - REST API Instrumentation by @mrkaye97 in [#2529](https://github.com/hatchet-dev/hatchet/pull/2529) ### Fixed - - Revert n+1 queries on the list API by @mrkaye97 in [#2531](https://github.com/hatchet-dev/hatchet/pull/2531) + ## v0.73.65 - 2025-11-14 ### Changed - - Case on conflict for v1_statuses_olap entry by @mnafees in [#2528](https://github.com/hatchet-dev/hatchet/pull/2528) - Attempt to fix pgx multi dimensional slice reflection error #1 by @mnafees in [#2523](https://github.com/hatchet-dev/hatchet/pull/2523) - Archive tenant modal flow by @mnafees in [#2509](https://github.com/hatchet-dev/hatchet/pull/2509) ### Fixed - - Fix seq scan in `PollCronSchedules` query by @mnafees in [#2524](https://github.com/hatchet-dev/hatchet/pull/2524) - Log dupes by @mrkaye97 in [#2526](https://github.com/hatchet-dev/hatchet/pull/2526) - Fix nil error in `handleTaskBulkAssignedTask` by @mnafees in [#2427](https://github.com/hatchet-dev/hatchet/pull/2427) + ## v0.73.64 - 2025-11-12 ### Changed - - [Go SDK] Case on worker labels for durable tasks by @mnafees in [#2511](https://github.com/hatchet-dev/hatchet/pull/2511) + ## v0.73.63 - 2025-11-07 ### Added - - Add pagination support for V1LogLineList by @jishnundth in [#2354](https://github.com/hatchet-dev/hatchet/pull/2354) ### Changed - - Immediate Payload Offloads OLAP Wiring by @mrkaye97 in [#2492](https://github.com/hatchet-dev/hatchet/pull/2492) + ## v0.73.62 - 2025-11-07 ### Changed - - Pass labels to durable worker by @mnafees in [#2504](https://github.com/hatchet-dev/hatchet/pull/2504) + ## v0.73.61 - 2025-11-06 ### Added - - Configurable OLAP status update size limits by @mrkaye97 in [#2499](https://github.com/hatchet-dev/hatchet/pull/2499) ### Fixed - - Propagate parent id through to `V1TaskSummary` properly by @mrkaye97 in [#2496](https://github.com/hatchet-dev/hatchet/pull/2496) + ## v0.73.60 - 2025-11-04 ### Fixed - - Deadlocks on trigger, olap prometheus background worker, otel improvements by @mnafees in [#2475](https://github.com/hatchet-dev/hatchet/pull/2475) + ## v0.73.58 - 2025-11-02 ### Added - - Add grpc otel spans, better tx debugging by @abelanger5 in [#2474](https://github.com/hatchet-dev/hatchet/pull/2474) ### Changed - - Update frontend onboarding steps by @sidpremkumar in [#2478](https://github.com/hatchet-dev/hatchet/pull/2478) - [hotfix] Temporarily increase `TestLoadCLI` average threshold by @mnafees in [#2461](https://github.com/hatchet-dev/hatchet/pull/2461) ### Fixed - - Fix Go SDK cron inputs by @mnafees in [#2481](https://github.com/hatchet-dev/hatchet/pull/2481) - Include payload partitions in olap partitions to drop by @mrkaye97 in [#2472](https://github.com/hatchet-dev/hatchet/pull/2472) + ## v0.73.56 - 2025-10-30 ### Changed - - Update managed compute regions by @mnafees in [#2470](https://github.com/hatchet-dev/hatchet/pull/2470) ### Fixed - - Read payloads from payload store for event API by @mrkaye97 in [#2471](https://github.com/hatchet-dev/hatchet/pull/2471) + ## v0.73.55 - 2025-10-30 ### Fixed - - Re-enable writes by @mrkaye97 in [#2469](https://github.com/hatchet-dev/hatchet/pull/2469) + ## v0.73.54 - 2025-10-30 ### Changed - - Run cleanup on more tables by @mnafees in [#2467](https://github.com/hatchet-dev/hatchet/pull/2467) ### Fixed - - Don't send expiry alert on internal proxy tokens by @abelanger5 in [#2468](https://github.com/hatchet-dev/hatchet/pull/2468) + ## v0.73.53 - 2025-10-30 ### Changed - - No need to check for partitions when updating them by @mnafees in [#2466](https://github.com/hatchet-dev/hatchet/pull/2466) + ## v0.73.52 - 2025-10-30 ### Changed - - [hotfix] Meaningful casing for engine liveness and readiness probes by @mnafees in [#2465](https://github.com/hatchet-dev/hatchet/pull/2465) + ## v0.73.51 - 2025-10-30 ### Changed - - Increase timeout and log more by @mnafees in [#2464](https://github.com/hatchet-dev/hatchet/pull/2464) + ## v0.73.50 - 2025-10-30 ### Changed - - Do not run cleanup on `v1_workflow_concurrency_slot` by @mnafees in [#2463](https://github.com/hatchet-dev/hatchet/pull/2463) + ## v0.73.49 - 2025-10-30 ### Added - - Add support for non-wal payload store logic to skip main db by @sidpremkumar in [#2445](https://github.com/hatchet-dev/hatchet/pull/2445) ### Changed - - Logs for liveness and readiness endpoints + PG conn stats by @mnafees in [#2460](https://github.com/hatchet-dev/hatchet/pull/2460) ### Fixed - - Reduce status update limits from 10k -> 1k by @abelanger5 in [#2462](https://github.com/hatchet-dev/hatchet/pull/2462) + ## v0.73.47 - 2025-10-28 ### Changed - - [hotfix] Fix running task stats without concurrency keys by @mnafees in [#2452](https://github.com/hatchet-dev/hatchet/pull/2452) + ## v0.73.46 - 2025-10-28 ### Changed - - New tenant task stats endpoint by @mnafees in [#2433](https://github.com/hatchet-dev/hatchet/pull/2433) - Retry RMQ messages indefinitely with aggressive logging after 5 retries by @mnafees in [#2448](https://github.com/hatchet-dev/hatchet/pull/2448) - Increase timeout to 30 seconds by @mnafees in [#2449](https://github.com/hatchet-dev/hatchet/pull/2449) ### Fixed - - Fix confusing error by @mnafees in [#2447](https://github.com/hatchet-dev/hatchet/pull/2447) + ## v0.73.45 - 2025-10-23 ### Added - - Add vars to tune concurrency poller by @mnafees in [#2428](https://github.com/hatchet-dev/hatchet/pull/2428) ### Changed - - Run cleanup job every minute by @mnafees in [#2440](https://github.com/hatchet-dev/hatchet/pull/2440) ### Fixed - - Payload performance by @abelanger5 in [#2441](https://github.com/hatchet-dev/hatchet/pull/2441) + ## v0.73.44 - 2025-10-21 ### Fixed - - Move err check to before len check by @abelanger5 in [#2437](https://github.com/hatchet-dev/hatchet/pull/2437) + ## v0.73.43 - 2025-10-20 ### Added - - OLAP Payloads by @mrkaye97 in [#2410](https://github.com/hatchet-dev/hatchet/pull/2410) + ## v0.73.42 - 2025-10-17 ### Fixed - - Fix race condition in child spawn by @mnafees in [#2429](https://github.com/hatchet-dev/hatchet/pull/2429) + ## v0.73.41 - 2025-10-16 ### Fixed - - Fix for Hatchet Lite to still properly fallback to using the `postgres` message queue kind by @mnafees in [#2392](https://github.com/hatchet-dev/hatchet/pull/2392) + ## v0.73.40 - 2025-10-16 ### Changed - - Cleanup job for old and invalid entries by @mnafees in [#2378](https://github.com/hatchet-dev/hatchet/pull/2378) - [hotfix] Avoid throwing error logs from ratelimit MW for invalid API routes by @mnafees in [#2420](https://github.com/hatchet-dev/hatchet/pull/2420) ### Fixed - - Fix OTel span attribute naming convention by @mnafees in [#2409](https://github.com/hatchet-dev/hatchet/pull/2409) - Swallow idempotency key error for scheduled runs by @mrkaye97 in [#2425](https://github.com/hatchet-dev/hatchet/pull/2425) + ## [0.73.40-alpha.0] - 2025-10-15 ### Fixed - - Payload fallback for child runs by @mrkaye97 in [#2421](https://github.com/hatchet-dev/hatchet/pull/2421) + ## v0.73.38 - 2025-10-15 ### Added - - Stateful polling intervals by @abelanger5 in [#2417](https://github.com/hatchet-dev/hatchet/pull/2417) - Scheduled run detail view, bulk cancel / replay with pagination helper by @mrkaye97 in [#2416](https://github.com/hatchet-dev/hatchet/pull/2416) ### Changed - - Introduce vars to tune `ANALYZE` job gocron run intervals by @mnafees in [#2407](https://github.com/hatchet-dev/hatchet/pull/2407) - Use `UTC` for all pgx connections and check for database TZ by @mnafees in [#2398](https://github.com/hatchet-dev/hatchet/pull/2398) + ## v0.73.35 - 2025-10-08 ### Added - - Gzip compression for large payloads, persistent OLAP writes by @mrkaye97 in [#2368](https://github.com/hatchet-dev/hatchet/pull/2368) - Immediate Payload Offloads by @mrkaye97 in [#2375](https://github.com/hatchet-dev/hatchet/pull/2375) - Pausable Crons by @mrkaye97 in [#2395](https://github.com/hatchet-dev/hatchet/pull/2395) ### Changed - - Properly case on output byte length by @mnafees in [#2394](https://github.com/hatchet-dev/hatchet/pull/2394) ### Fixed - - Optimize concurrency slot trigger method by @abelanger5 in [#2391](https://github.com/hatchet-dev/hatchet/pull/2391) + ## v0.73.34 - 2025-10-03 ### Changed - - Include `tenant_id` in OTel spans wherever possible by @mnafees in [#2382](https://github.com/hatchet-dev/hatchet/pull/2382) ### Fixed - - Payload fallbacks, WAL conflict handling, WAL eviction by @mrkaye97 in [#2372](https://github.com/hatchet-dev/hatchet/pull/2372) - Run analyze every 3 hours by @mrkaye97 in [#2380](https://github.com/hatchet-dev/hatchet/pull/2380) + ## v0.73.33 - 2025-10-02 ### Added - - Add ApplyNamespace for BulkRunWorkflow by @icbd in [#2374](https://github.com/hatchet-dev/hatchet/pull/2374) + ## v0.73.32 - 2025-09-30 ### Changed - - Candidate Fix: WAL Write Dupes by @mrkaye97 in [#2369](https://github.com/hatchet-dev/hatchet/pull/2369) - Event payload max height, popover positioning by @mrkaye97 in [#2367](https://github.com/hatchet-dev/hatchet/pull/2367) + ## v0.73.31 - 2025-09-30 ### Fixed - - Relax check constraint to allow null payloads by @mrkaye97 in [#2366](https://github.com/hatchet-dev/hatchet/pull/2366) + ## v0.73.30 - 2025-09-30 ### Added - - Max channels for rabbitmq by @abelanger5 in [#2365](https://github.com/hatchet-dev/hatchet/pull/2365) ### Changed - - Ignore tenants with deletedAt non null by @mnafees in [#2364](https://github.com/hatchet-dev/hatchet/pull/2364) + ## v0.73.29 - 2025-09-29 ### Changed - - Use member populator for tenant member API ops by @mnafees in [#2363](https://github.com/hatchet-dev/hatchet/pull/2363) ### Fixed - - Disable inner scroll by @mrkaye97 in [#2362](https://github.com/hatchet-dev/hatchet/pull/2362) + ## v0.73.28 - 2025-09-29 ### Changed - - Worker detail fixes by @mrkaye97 in [#2353](https://github.com/hatchet-dev/hatchet/pull/2353) ### Fixed - - Use separate connections for pub and sub by @abelanger5 in [#2358](https://github.com/hatchet-dev/hatchet/pull/2358) + ## v0.73.26 - 2025-09-26 ### Fixed - - Async start step run action event by @abelanger5 in [#2351](https://github.com/hatchet-dev/hatchet/pull/2351) + ## v0.73.25 - 2025-09-26 ### Changed - - FE Polish, VI: Make badges dynamically sized, use slate instead of fuchsia for queued, display ms on dates by @mrkaye97 in [#2352](https://github.com/hatchet-dev/hatchet/pull/2352) + ## v0.73.24 - 2025-09-26 ### Fixed - - Scope override by @mrkaye97 in [#2349](https://github.com/hatchet-dev/hatchet/pull/2349) + ## v0.73.23 - 2025-09-26 ### Fixed - - Rename metrics queries, always refetch queue metrics, change default refetch interval, configurable WAL poll limit by @mrkaye97 in [#2346](https://github.com/hatchet-dev/hatchet/pull/2346) + ## v0.73.22 - 2025-09-25 ### Changed - - Error log if we send >10mb message over the internal queue by @mrkaye97 in [#2345](https://github.com/hatchet-dev/hatchet/pull/2345) + ## v0.73.21 - 2025-09-25 ### Changed - - FE Polish V: Searchable workflows, additional metadata tab by @mrkaye97 in [#2342](https://github.com/hatchet-dev/hatchet/pull/2342) ### Fixed - - Improve DAG status updates by @abelanger5 in [#2343](https://github.com/hatchet-dev/hatchet/pull/2343) - Show legacy data by @mrkaye97 in [#2344](https://github.com/hatchet-dev/hatchet/pull/2344) + ## v0.73.20 - 2025-09-24 ### Changed - - FE Polish, IV: Tooltips, task event scrolling by @mrkaye97 in [#2335](https://github.com/hatchet-dev/hatchet/pull/2335) - FE Polish, III: More state management fixes, worker pages by @mrkaye97 in [#2332](https://github.com/hatchet-dev/hatchet/pull/2332) ### Fixed - - Event getter backwards compat by @mrkaye97 in [#2337](https://github.com/hatchet-dev/hatchet/pull/2337) - Use `SplitN` instead of `Split` by @mrkaye97 in [#2336](https://github.com/hatchet-dev/hatchet/pull/2336) - Stable ordering for flattened tasks + child tasks by @mrkaye97 in [#2334](https://github.com/hatchet-dev/hatchet/pull/2334) + ## v0.73.18 - 2025-09-23 ### Fixed - - Rogue effect hook, some more cleanup by @mrkaye97 in [#2329](https://github.com/hatchet-dev/hatchet/pull/2329) + ## v0.73.17 - 2025-09-23 ### Added - - Add error level logs if we fall back to the task input for monitoring by @mrkaye97 in [#2328](https://github.com/hatchet-dev/hatchet/pull/2328) + ## v0.73.16 - 2025-09-23 ### Fixed - - Frontend polish, Part II by @mrkaye97 in [#2327](https://github.com/hatchet-dev/hatchet/pull/2327) + ## v0.73.15 - 2025-09-23 ### Changed - - Update docs to use Go SDK v1 by @mnafees in [#2313](https://github.com/hatchet-dev/hatchet/pull/2313) + ## v0.73.14 - 2025-09-22 ### Added - - Show statuses of run filters with colors by @mrkaye97 in [#2325](https://github.com/hatchet-dev/hatchet/pull/2325) + ## v0.73.13 - 2025-09-21 ### Added - - Support dynamic rate limit durations by @abelanger5 in [#2320](https://github.com/hatchet-dev/hatchet/pull/2320) ### Changed - - Compact toolbar, refetch improvements, table improvements by @mrkaye97 in [#2292](https://github.com/hatchet-dev/hatchet/pull/2292) ### Fixed - - Skip locked on queue updates by @abelanger5 in [#2321](https://github.com/hatchet-dev/hatchet/pull/2321) + ## v0.73.12 - 2025-09-19 ### Fixed - - Payload WAL dupes by @mrkaye97 in [#2319](https://github.com/hatchet-dev/hatchet/pull/2319) + ## v0.73.11 - 2025-09-19 ### Fixed - - Update payload properly on replay by @mrkaye97 in [#2317](https://github.com/hatchet-dev/hatchet/pull/2317) + ## v0.73.10 - 2025-09-18 ### Fixed - - Payloads OLAP backwards compat by @mrkaye97 in [#2316](https://github.com/hatchet-dev/hatchet/pull/2316) + ## v0.73.9 - 2025-09-18 ### Fixed - - Event filtering edge case by @mrkaye97 in [#2311](https://github.com/hatchet-dev/hatchet/pull/2311) + ## v0.73.8 - 2025-09-18 ### Fixed - - Fix seed default tenant slug by @mnafees in [#2315](https://github.com/hatchet-dev/hatchet/pull/2315) + ## v0.73.7 - 2025-09-17 ### Fixed - - Empty state by @grutt in [#2310](https://github.com/hatchet-dev/hatchet/pull/2310) + ## v0.73.6 - 2025-09-17 ### Changed - - Properly fall back to tenant switcher by @mnafees in [#2307](https://github.com/hatchet-dev/hatchet/pull/2307) ### Fixed - - DAG details rendering in side panel, backwards compatible event list API by @mrkaye97 in [#2309](https://github.com/hatchet-dev/hatchet/pull/2309) + ## v0.73.4 - 2025-09-16 ### Changed - - Allow RabbitMQ to be used with Hatchet Lite by @mnafees in [#2128](https://github.com/hatchet-dev/hatchet/pull/2128) + ## v0.73.3 - 2025-09-16 ### Changed - - [hotfix] CLI arg to specify average duration per event threshold for loadtest to succeed by @mnafees in [#2300](https://github.com/hatchet-dev/hatchet/pull/2300) ### Fixed - - Fix `GetDetails` in `Runs` feature client of Go SDK v1 by @mnafees in [#2297](https://github.com/hatchet-dev/hatchet/pull/2297) - WAL partition poll function type by @mrkaye97 in [#2301](https://github.com/hatchet-dev/hatchet/pull/2301) + ## v0.73.2 - 2025-09-15 ### Added - - Add organization by @mnafees in [#2299](https://github.com/hatchet-dev/hatchet/pull/2299) + ## v0.73.1 - 2025-09-12 ### Fixed - - Revert partition pruning by @mrkaye97 in [#2295](https://github.com/hatchet-dev/hatchet/pull/2295) + ## v0.73.0 - 2025-09-12 ### Added - - Add panic handler to Go SDK by @mnafees in [#2293](https://github.com/hatchet-dev/hatchet/pull/2293) - Partition pruning for `ListTaskParentOutputs`, lookup index for `v1_payload_wal` by @mrkaye97 in [#2294](https://github.com/hatchet-dev/hatchet/pull/2294) - Payload Store Repository by @mrkaye97 in [#2047](https://github.com/hatchet-dev/hatchet/pull/2047) ### Removed - - Remove `nginx` and use custom static fileservers by @mnafees in [#1928](https://github.com/hatchet-dev/hatchet/pull/1928) ### Fixed - - Scheduled runs race w/ idempotency key check by @mrkaye97 in [#2077](https://github.com/hatchet-dev/hatchet/pull/2077) + ## v0.72.8 - 2025-09-11 ### Added - - Filters UI, Events page refactor, Misc. other fixes by @mrkaye97 in [#2276](https://github.com/hatchet-dev/hatchet/pull/2276) -- Feat improve auth error handling by @grutt in [#1893](https://github.com/hatchet-dev/hatchet/pull/1893) +- Feat improve auth error handling by @grutt in [#1893](https://github.com/hatchet-dev/hatchet/pull/1893) + ## v0.72.4 - 2025-09-10 ### Changed - - Do not show archived tenants anywhere by @mnafees in [#2280](https://github.com/hatchet-dev/hatchet/pull/2280) - Docs-in-app, Part I by @mrkaye97 in [#2183](https://github.com/hatchet-dev/hatchet/pull/2183) + ## v0.72.3 - 2025-09-09 ### Changed - - Org UI feedback improvements by @mnafees in [#2275](https://github.com/hatchet-dev/hatchet/pull/2275) - Error out instead of panic by @mnafees in [#2274](https://github.com/hatchet-dev/hatchet/pull/2274) + ## v0.72.2 - 2025-09-09 ### Added - - Worker slot Prom metrics by @mrkaye97 in [#2195](https://github.com/hatchet-dev/hatchet/pull/2195) ### Changed - - Go SDK v1 feature client changes by @mnafees in [#2160](https://github.com/hatchet-dev/hatchet/pull/2160) ### Fixed - - Fix custom auth casing by @mnafees in [#2268](https://github.com/hatchet-dev/hatchet/pull/2268) + ## v0.72.1 - 2025-09-08 ### Changed - - Expired invite status by @mnafees in [#2266](https://github.com/hatchet-dev/hatchet/pull/2266) ### Fixed - - Fix type names by @mnafees in [#2264](https://github.com/hatchet-dev/hatchet/pull/2264) + ## v0.72.0 - 2025-09-05 ### Changed - - Introduce UI for Organizations by @mnafees in [#2247](https://github.com/hatchet-dev/hatchet/pull/2247) - Make sure to case on err properly by @mnafees in [#2248](https://github.com/hatchet-dev/hatchet/pull/2248) - Properly handle 404s from populator middleware to avoid panics by @mnafees in [#2238](https://github.com/hatchet-dev/hatchet/pull/2238) ### Fixed - - Fixes for organization selector by @mnafees in [#2257](https://github.com/hatchet-dev/hatchet/pull/2257) + ## v0.71.13 - 2025-09-02 ### Changed - - Periodically run `ANALYZE` on `v1_task` and `v1_task_event` by @mnafees in [#2236](https://github.com/hatchet-dev/hatchet/pull/2236) - Guard cleanAdditionalMetadata against JSON null; client: avoid null AdditionalMetadata in BulkPush; add regression test by @xcono in [#2191](https://github.com/hatchet-dev/hatchet/pull/2191) - Inject into context by @mnafees in [#2213](https://github.com/hatchet-dev/hatchet/pull/2213) - Onboarding key by @grutt in [#2212](https://github.com/hatchet-dev/hatchet/pull/2212) ### Fixed - - Rm annoying loki logs by @mrkaye97 in [#2224](https://github.com/hatchet-dev/hatchet/pull/2224) - Remove rate limited items from in memory buffer by @abelanger5 in [#2207](https://github.com/hatchet-dev/hatchet/pull/2207) + ## v0.71.10 - 2025-08-26 ### Fixed - - Remove `custom auth` by @mrkaye97 in [#2203](https://github.com/hatchet-dev/hatchet/pull/2203) + ## v0.71.9 - 2025-08-26 ### Fixed - - Explicit ordering in ReleaseTasks and lock parent slots by @abelanger5 in [#2201](https://github.com/hatchet-dev/hatchet/pull/2201) - Don't query database when flush is called concurrently by @abelanger5 in [#2202](https://github.com/hatchet-dev/hatchet/pull/2202) - Confusing error message by @abelanger5 in [#2199](https://github.com/hatchet-dev/hatchet/pull/2199) + ## v0.71.8 - 2025-08-25 ### Fixed - - Child runs not rendering after one day, empty worker ids, additional meta filters not being applied to counts by @mrkaye97 in [#2196](https://github.com/hatchet-dev/hatchet/pull/2196) + ## [0.71.8-alpha.0] - 2025-08-25 ### Added - - Improved onboarding part 1 by @grutt in [#2186](https://github.com/hatchet-dev/hatchet/pull/2186) ### Fixed - - Match and cancel newest/in progress deadlocks by @abelanger5 in [#2190](https://github.com/hatchet-dev/hatchet/pull/2190) + ## v0.71.7 - 2025-08-22 ### Added - - Add visibility to stream send event by @abelanger5 in [#2174](https://github.com/hatchet-dev/hatchet/pull/2174) - Analytics events by @grutt in [#2171](https://github.com/hatchet-dev/hatchet/pull/2171) ### Changed - - Re-enable refetch queue metrics, fix action button / dropdown state by @mrkaye97 in [#2182](https://github.com/hatchet-dev/hatchet/pull/2182) - Limit frequency of updates to rate limits by @abelanger5 in [#2173](https://github.com/hatchet-dev/hatchet/pull/2173) + ## v0.71.4 - 2025-08-20 ### Added - - Run `ANALYZE` on a few tables once a day by @mrkaye97 in [#2163](https://github.com/hatchet-dev/hatchet/pull/2163) - Add Linear to preconfigured webhooks by @mrkaye97 in [#2157](https://github.com/hatchet-dev/hatchet/pull/2157) ### Changed - - Introduce `customAuth` to the OpenAPI spec by @mnafees in [#2168](https://github.com/hatchet-dev/hatchet/pull/2168) ### Fixed - - Move rate limited queue items off the main queue by @abelanger5 in [#2155](https://github.com/hatchet-dev/hatchet/pull/2155) - Populate DAG Metadata Sequentially by @mrkaye97 in [#2156](https://github.com/hatchet-dev/hatchet/pull/2156) - Auto-generate docs snippets and examples by @mrkaye97 in [#2139](https://github.com/hatchet-dev/hatchet/pull/2139) - Deadlocking on DAG concurrency by @mrkaye97 in [#2111](https://github.com/hatchet-dev/hatchet/pull/2111) + ## v0.70.7 - 2025-08-14 ### Added - - Webhook fixes / improvements by @mrkaye97 in [#2131](https://github.com/hatchet-dev/hatchet/pull/2131) ### Changed - - Runs list state management + bug fixes part I by @mrkaye97 in [#2114](https://github.com/hatchet-dev/hatchet/pull/2114) - [hotfix] Better messaging around tenant prometheus metrics empty state by @mnafees in [#2124](https://github.com/hatchet-dev/hatchet/pull/2124) - Workflow combobox search functionality by @mnafees in [#2118](https://github.com/hatchet-dev/hatchet/pull/2118) ### Fixed - - Add back sync docs script by @mrkaye97 in [#2123](https://github.com/hatchet-dev/hatchet/pull/2123) + ## v0.70.6 - 2025-08-12 ### Added - - Add k8s pod info to traces by @mnafees in [#2109](https://github.com/hatchet-dev/hatchet/pull/2109) ### Changed - - [Python] Feat: Dependency Injection, Improved error handling by @mrkaye97 in [#2067](https://github.com/hatchet-dev/hatchet/pull/2067) - [HAT-432] Enforce task priorities to be between 1 and 3 by @mnafees in [#2110](https://github.com/hatchet-dev/hatchet/pull/2110) ### Fixed - - Optimize DAG timing query for Prom by @mrkaye97 in [#2102](https://github.com/hatchet-dev/hatchet/pull/2102) - Waterfall panic + query simplification by @mrkaye97 in [#2116](https://github.com/hatchet-dev/hatchet/pull/2116) - Don't wait for grpc stream send on rabbitmq loop by @abelanger5 in [#2115](https://github.com/hatchet-dev/hatchet/pull/2115) + ## v0.70.5 - 2025-08-07 ### Changed - - Fail task tracing by @mrkaye97 in [#2101](https://github.com/hatchet-dev/hatchet/pull/2101) + ## v0.70.4 - 2025-08-06 ### Added - - Add contextual data for trigger via events by @mnafees in [#2092](https://github.com/hatchet-dev/hatchet/pull/2092) ### Fixed - - Call `PopulateTaskRunData` sequentially by @mrkaye97 in [#2097](https://github.com/hatchet-dev/hatchet/pull/2097) + ## v0.70.3 - 2025-08-06 ### Added - - Add telemetry to task status repo methods by @mnafees in [#2091](https://github.com/hatchet-dev/hatchet/pull/2091) ### Fixed - - Improve performance of `UpdateTasksToAssigned` by @mrkaye97 in [#2094](https://github.com/hatchet-dev/hatchet/pull/2094) + ## v0.70.2 - 2025-08-06 ### Added - - Add telemetry around task statuses in controller by @mnafees in [#2090](https://github.com/hatchet-dev/hatchet/pull/2090) ### Fixed - - ProcessTaskTimeouts limit and timeout by @grutt in [#2087](https://github.com/hatchet-dev/hatchet/pull/2087) - Webhook copy improvements by @mrkaye97 in [#2081](https://github.com/hatchet-dev/hatchet/pull/2081) diff --git a/frontend/docs/pages/reference/changelog/python.mdx b/frontend/docs/pages/reference/changelog/python.mdx index 87b44bc0d8..e5124f87ec 100644 --- a/frontend/docs/pages/reference/changelog/python.mdx +++ b/frontend/docs/pages/reference/changelog/python.mdx @@ -1,5 +1,15 @@ {/* AUTOGENERATED — do not edit. Run `task sync-changelog` to regenerate from sdks/python/CHANGELOG.md */} +## v1.34.0 + +### Added + +- Adds support for defining **idempotency keys** on workflows and standalone tasks, which ensures that they're only run once in a provided time window, based on a CEL expression. + +### Fixed + +- Fixed a bug where synchronous log calls via `asyncio.to_thread` (or other threads) could block workers. + ## v1.33.6 - 2026-05-27 ### Changed diff --git a/frontend/docs/pages/reference/changelog/ruby.mdx b/frontend/docs/pages/reference/changelog/ruby.mdx index d76acaea3c..4f72bdc3d4 100644 --- a/frontend/docs/pages/reference/changelog/ruby.mdx +++ b/frontend/docs/pages/reference/changelog/ruby.mdx @@ -1,5 +1,11 @@ {/* AUTOGENERATED — do not edit. Run `task sync-changelog` to regenerate from sdks/ruby/src/CHANGELOG.md */} +## v0.4.0 - 2026-06-03 + +### Added + +- Adds support for defining **idempotency keys** on workflows and standalone tasks via an `idempotency` option, which ensures that they're only run once in a provided time window, based on a CEL expression. Triggers that collide with an existing run raise an `IdempotencyCollisionError` containing the existing run's ID. + ## v0.3.0 - 2026-04-28 ### Added diff --git a/frontend/docs/pages/reference/changelog/typescript.mdx b/frontend/docs/pages/reference/changelog/typescript.mdx index 24196c117d..468c16a893 100644 --- a/frontend/docs/pages/reference/changelog/typescript.mdx +++ b/frontend/docs/pages/reference/changelog/typescript.mdx @@ -1,5 +1,11 @@ {/* AUTOGENERATED — do not edit. Run `task sync-changelog` to regenerate from sdks/typescript/CHANGELOG.md */} +## v1.24.0 - 2026-06-03 + +### Added + +- Adds support for defining **idempotency keys** on workflows and standalone tasks via an `idempotency` option, which ensures that they're only run once in a provided time window, based on a CEL expression. Triggers that collide with an existing run throw an `IdempotencyCollisionError` containing the existing run's ID. + ## v1.23.0 - 2026-05-27 ### Added From 433ebfcc2543e12619c4dfbc2a32daf50379b365 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Thu, 4 Jun 2026 09:54:34 -0400 Subject: [PATCH 63/69] chore: lockfile --- sdks/python/poetry.lock | 58 ++++++++++++++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/sdks/python/poetry.lock b/sdks/python/poetry.lock index 4348677aca..6121117a2f 100644 --- a/sdks/python/poetry.lock +++ b/sdks/python/poetry.lock @@ -398,7 +398,7 @@ description = "Python package for providing Mozilla's CA Bundle." optional = true python-versions = ">=3.7" groups = ["main"] -markers = "extra == \"openai\" or extra == \"claude\" or extra == \"docs\" or extra == \"otel\" or extra == \"test\"" +markers = "extra == \"otel\" or extra == \"test\" or extra == \"openai\" or extra == \"claude\" or extra == \"docs\"" files = [ {file = "certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa"}, {file = "certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7"}, @@ -509,7 +509,7 @@ description = "The Real First Universal Charset Detector. Open, modern and activ optional = true python-versions = ">=3.7" groups = ["main"] -markers = "extra == \"openai\" or extra == \"otel\" or extra == \"test\"" +markers = "extra == \"otel\" or extra == \"test\" or extra == \"openai\"" files = [ {file = "charset_normalizer-3.4.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2e1d8ca8611099001949d1cdfaefc510cf0f212484fe7c565f735b68c78c3c95"}, {file = "charset_normalizer-3.4.6-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e25369dc110d58ddf29b949377a93e0716d72a24f62bad72b2b39f155949c1fd"}, @@ -690,7 +690,7 @@ description = "Cross-platform colored terminal text." optional = true python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" groups = ["main"] -markers = "(extra == \"openai\" or extra == \"claude\" or extra == \"docs\" or extra == \"lint\") and sys_platform != \"emscripten\" and platform_system == \"Windows\" or (extra == \"docs\" or extra == \"lint\" or extra == \"openai\") and platform_system == \"Windows\" or extra == \"test\" and sys_platform == \"win32\"" +markers = "(sys_platform != \"emscripten\" and (extra == \"openai\" or extra == \"claude\") or extra == \"docs\" or extra == \"lint\") and platform_system == \"Windows\" or extra == \"test\" and sys_platform == \"win32\" or platform_system == \"Windows\" and (extra == \"docs\" or extra == \"openai\")" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -801,7 +801,7 @@ description = "Backport of PEP 654 (exception groups)" optional = true python-versions = ">=3.7" groups = ["main"] -markers = "(extra == \"openai\" or extra == \"claude\" or extra == \"docs\" or extra == \"test\") and python_version == \"3.10\"" +markers = "(extra == \"test\" or extra == \"openai\" or extra == \"claude\" or extra == \"docs\") and python_version == \"3.10\"" files = [ {file = "exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598"}, {file = "exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219"}, @@ -1017,10 +1017,9 @@ dev = ["flake8", "markdown", "twine", "wheel"] name = "googleapis-common-protos" version = "1.73.1" description = "Common protobufs used in Google APIs" -optional = true +optional = false python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"otel\"" files = [ {file = "googleapis_common_protos-1.73.1-py3-none-any.whl", hash = "sha256:e51f09eb0a43a8602f5a915870972e6b4a394088415c79d79605a46d8e826ee8"}, {file = "googleapis_common_protos-1.73.1.tar.gz", hash = "sha256:13114f0e9d2391756a0194c3a8131974ed7bffb06086569ba193364af59163b6"}, @@ -1125,6 +1124,23 @@ typing-extensions = ">=4.12,<5.0" [package.extras] protobuf = ["grpcio-tools (>=1.80.0)"] +[[package]] +name = "grpcio-status" +version = "1.80.0" +description = "Status proto mapping for gRPC" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "grpcio_status-1.80.0-py3-none-any.whl", hash = "sha256:4b56990363af50dbf2c2ebb80f1967185c07d87aa25aa2bea45ddb75fc181dbe"}, + {file = "grpcio_status-1.80.0.tar.gz", hash = "sha256:df73802a4c89a3ea88aa2aff971e886fccce162bc2e6511408b3d67a144381cd"}, +] + +[package.dependencies] +googleapis-common-protos = ">=1.5.5" +grpcio = ">=1.80.0" +protobuf = ">=6.31.1,<7.0.0" + [[package]] name = "grpcio-tools" version = "1.80.0" @@ -2415,7 +2431,7 @@ description = "Core utilities for Python packages" optional = true python-versions = ">=3.8" groups = ["main"] -markers = "extra == \"docs\" or extra == \"lint\" or extra == \"otel\" or extra == \"test\"" +markers = "extra == \"otel\" or extra == \"lint\" or extra == \"test\" or extra == \"docs\"" files = [ {file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"}, {file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"}, @@ -2428,7 +2444,7 @@ description = "Utility library for gitignore style pattern matching of file path optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"docs\" or extra == \"lint\"" +markers = "extra == \"lint\" or extra == \"docs\"" files = [ {file = "pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723"}, {file = "pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645"}, @@ -2447,7 +2463,7 @@ description = "A small Python package for determining appropriate platform-speci optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"docs\" or extra == \"lint\"" +markers = "extra == \"lint\" or extra == \"docs\"" files = [ {file = "platformdirs-4.9.4-py3-none-any.whl", hash = "sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868"}, {file = "platformdirs-4.9.4.tar.gz", hash = "sha256:1ec356301b7dc906d83f371c8f487070e99d3ccf9e501686456394622a01a934"}, @@ -3356,7 +3372,7 @@ description = "Python HTTP for Humans." optional = true python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"openai\" or extra == \"otel\" or extra == \"test\"" +markers = "extra == \"otel\" or extra == \"test\" or extra == \"openai\"" files = [ {file = "requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0"}, {file = "requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed"}, @@ -3671,7 +3687,7 @@ description = "A lil' TOML parser" optional = true python-versions = ">=3.8" groups = ["main"] -markers = "(extra == \"docs\" or extra == \"lint\" or extra == \"test\") and python_version == \"3.10\"" +markers = "(extra == \"lint\" or extra == \"test\" or extra == \"docs\") and python_version == \"3.10\"" files = [ {file = "tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30"}, {file = "tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a"}, @@ -3774,6 +3790,22 @@ files = [ {file = "types_grpcio-1.0.0.20251009.tar.gz", hash = "sha256:a8f615ea7a47b31f10da028ab5258d4f1611fbd70719ca450fc0ab3fb9c62b63"}, ] +[[package]] +name = "types-grpcio-status" +version = "1.0.0.20260408" +description = "Typing stubs for grpcio-status" +optional = true +python-versions = ">=3.10" +groups = ["main"] +markers = "extra == \"lint\"" +files = [ + {file = "types_grpcio_status-1.0.0.20260408-py3-none-any.whl", hash = "sha256:5d421512b6a9ae5d0f84cde9c251394da5bd77ddc96e122221ac23c8e1ce3acf"}, + {file = "types_grpcio_status-1.0.0.20260408.tar.gz", hash = "sha256:8c8b5a5fd5e661488bb3dfb924fd3562892bae01b9a24e607a26bb0029f3fb71"}, +] + +[package.dependencies] +types-grpcio = "*" + [[package]] name = "types-html5lib" version = "1.1.11.20251117" @@ -4227,7 +4259,7 @@ type = ["pytest-mypy"] [extras] claude = ["claude-agent-sdk", "mcp"] docs = ["bs4", "fastapi", "markdownify", "mkdocs", "mkdocstrings", "openai", "pydoclint", "types-beautifulsoup4", "uvicorn"] -lint = ["black", "click", "mypy", "psutil", "ruff", "types-grpcio", "types-protobuf", "types-psutil", "types-requests"] +lint = ["black", "click", "mypy", "psutil", "ruff", "types-grpcio", "types-grpcio-status", "types-protobuf", "types-psutil", "types-requests"] openai = ["openai-agents"] otel = ["opentelemetry-api", "opentelemetry-distro", "opentelemetry-exporter-otlp", "opentelemetry-exporter-otlp-proto-http", "opentelemetry-instrumentation", "opentelemetry-sdk"] test = ["boto3", "botocore", "psycopg", "pytest", "pytest-asyncio", "pytest-env", "pytest-retry", "pytest-xdist", "requests"] @@ -4235,4 +4267,4 @@ test = ["boto3", "botocore", "psycopg", "pytest", "pytest-asyncio", "pytest-env" [metadata] lock-version = "2.1" python-versions = ">=3.10,<4" -content-hash = "7a52a196892b7243fcc72b8b04a53d427d755f0dbb184d9b70ce8b0ad1f335b9" +content-hash = "0b633e610de35c58a2d63700f0d229fe49f41d715c2fe883388e13b194c4b31a" From 82b3976aa4a0d3065b684a10a6556c056bee327d Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Thu, 4 Jun 2026 09:58:16 -0400 Subject: [PATCH 64/69] chore: lint --- .../pages/reference/changelog/platform.mdx | 765 +++++++++++------- 1 file changed, 451 insertions(+), 314 deletions(-) diff --git a/frontend/docs/pages/reference/changelog/platform.mdx b/frontend/docs/pages/reference/changelog/platform.mdx index 18d50f997e..daa0ff5de0 100644 --- a/frontend/docs/pages/reference/changelog/platform.mdx +++ b/frontend/docs/pages/reference/changelog/platform.mdx @@ -3,165 +3,175 @@ ## v0.88.0 - 2026-05-30 ### Fixed -- Use leases to protect concurrent partition creation (un-revert) by @juliusgeo in [#4051](https://github.com/hatchet-dev/hatchet/pull/4051) +- Use leases to protect concurrent partition creation (un-revert) by @juliusgeo in [#4051](https://github.com/hatchet-dev/hatchet/pull/4051) ## v0.87.12 - 2026-05-30 ### Fixed -- Re-enable external id dupe check, add flag to enable dynamic window size by @mrkaye97 in [#4050](https://github.com/hatchet-dev/hatchet/pull/4050) +- Re-enable external id dupe check, add flag to enable dynamic window size by @mrkaye97 in [#4050](https://github.com/hatchet-dev/hatchet/pull/4050) ## v0.87.11 - 2026-05-30 ### Fixed -- Remove external id check for now, it's too expensive by @mrkaye97 in [#4048](https://github.com/hatchet-dev/hatchet/pull/4048) +- Remove external id check for now, it's too expensive by @mrkaye97 in [#4048](https://github.com/hatchet-dev/hatchet/pull/4048) ## v0.87.10 - 2026-05-30 ### Fixed -- Lengthen timeout for external id dupe check by @mrkaye97 in [#4047](https://github.com/hatchet-dev/hatchet/pull/4047) +- Lengthen timeout for external id dupe check by @mrkaye97 in [#4047](https://github.com/hatchet-dev/hatchet/pull/4047) ## v0.87.9 - 2026-05-29 ### Fixed -- Migration order for partition leases by @juliusgeo in [#4046](https://github.com/hatchet-dev/hatchet/pull/4046) +- Migration order for partition leases by @juliusgeo in [#4046](https://github.com/hatchet-dev/hatchet/pull/4046) ## v0.87.8 - 2026-05-29 ### Fixed -- Use leases to protect concurrent partition creation by @juliusgeo in [#4044](https://github.com/hatchet-dev/hatchet/pull/4044) +- Use leases to protect concurrent partition creation by @juliusgeo in [#4044](https://github.com/hatchet-dev/hatchet/pull/4044) ## v0.87.7 - 2026-05-29 ### Added + - Support named shard deployment targets by @igor-kupczynski in [#4042](https://github.com/hatchet-dev/hatchet/pull/4042) ### Fixed + - Some more payload offload improvements by @mrkaye97 in [#4041](https://github.com/hatchet-dev/hatchet/pull/4041) - Sleep bug for anything over 59s by @darren-west in [#4012](https://github.com/hatchet-dev/hatchet/pull/4012) - Update quickstart template SDK versions by @BloggerBust in [#4025](https://github.com/hatchet-dev/hatchet/pull/4025) - ## v0.87.6 - 2026-05-28 ### Added -- Index file-based payload offloads by @mrkaye97 in [#3979](https://github.com/hatchet-dev/hatchet/pull/3979) +- Index file-based payload offloads by @mrkaye97 in [#3979](https://github.com/hatchet-dev/hatchet/pull/3979) ## v0.87.3 - 2026-05-28 ### Fixed -- Go use durable listener for async results by @grutt in [#4019](https://github.com/hatchet-dev/hatchet/pull/4019) +- Go use durable listener for async results by @grutt in [#4019](https://github.com/hatchet-dev/hatchet/pull/4019) ## v0.87.1 - 2026-05-26 ### Added -- Move billing to control plane by @grutt in [#3982](https://github.com/hatchet-dev/hatchet/pull/3982) +- Move billing to control plane by @grutt in [#3982](https://github.com/hatchet-dev/hatchet/pull/3982) ## v0.86.32 - 2026-05-26 ### Added + - Show structured log fields in log viewer by @MicroYui in [#3972](https://github.com/hatchet-dev/hatchet/pull/3972) ### Changed + - Use shared Dialog for plan upgrade modal by @jishnundth in [#3887](https://github.com/hatchet-dev/hatchet/pull/3887) ### Removed + - Remove backoff overflow test by @juliusgeo in [#3990](https://github.com/hatchet-dev/hatchet/pull/3990) ### Fixed -- Hide github tab if managed workers are disabled by @mrkaye97 in [#3988](https://github.com/hatchet-dev/hatchet/pull/3988) +- Hide github tab if managed workers are disabled by @mrkaye97 in [#3988](https://github.com/hatchet-dev/hatchet/pull/3988) ## v0.86.29 - 2026-05-22 ### Fixed -- Migration number by @mrkaye97 in [#3987](https://github.com/hatchet-dev/hatchet/pull/3987) +- Migration number by @mrkaye97 in [#3987](https://github.com/hatchet-dev/hatchet/pull/3987) ## v0.86.28 - 2026-05-22 ### Fixed -- A couple more migration performance issues by @mrkaye97 in [#3942](https://github.com/hatchet-dev/hatchet/pull/3942) +- A couple more migration performance issues by @mrkaye97 in [#3942](https://github.com/hatchet-dev/hatchet/pull/3942) ## v0.86.27 - 2026-05-22 ### Added + - Make Hatchet Lite version configurable by @Ujjwal-Singh-20 in [#3925](https://github.com/hatchet-dev/hatchet/pull/3925) ### Removed + - Revert "chore: move requests to control plane" by @grutt ### Fixed -- Payload external id duplicates from durable child spawning by @mrkaye97 in [#3984](https://github.com/hatchet-dev/hatchet/pull/3984) +- Payload external id duplicates from durable child spawning by @mrkaye97 in [#3984](https://github.com/hatchet-dev/hatchet/pull/3984) ## v0.86.26 - 2026-05-21 ### Fixed -- Increase limit default to 10k by @mrkaye97 in [#3978](https://github.com/hatchet-dev/hatchet/pull/3978) +- Increase limit default to 10k by @mrkaye97 in [#3978](https://github.com/hatchet-dev/hatchet/pull/3978) ## v0.86.25 - 2026-05-20 ### Fixed -- Only run worker id action getter query when needed by @mrkaye97 in [#3976](https://github.com/hatchet-dev/hatchet/pull/3976) +- Only run worker id action getter query when needed by @mrkaye97 in [#3976](https://github.com/hatchet-dev/hatchet/pull/3976) ## v0.86.24 - 2026-05-20 ### Changed + - Make SSO Configuration UI more clear by @juliusgeo in [#3971](https://github.com/hatchet-dev/hatchet/pull/3971) ### Fixed -- Correctly handle child key-based caching in durable task child spawning by @mrkaye97 in [#3955](https://github.com/hatchet-dev/hatchet/pull/3955) +- Correctly handle child key-based caching in durable task child spawning by @mrkaye97 in [#3955](https://github.com/hatchet-dev/hatchet/pull/3955) ## v0.86.23 - 2026-05-20 ### Fixed -- Attempt at improving worker actions query by @mrkaye97 in [#3774](https://github.com/hatchet-dev/hatchet/pull/3774) +- Attempt at improving worker actions query by @mrkaye97 in [#3774](https://github.com/hatchet-dev/hatchet/pull/3774) ## v0.86.22 - 2026-05-20 ### Added + - Entitlement check by @grutt in [#3969](https://github.com/hatchet-dev/hatchet/pull/3969) - Move workflow search bar to the top of workflows table by @abelanger5 in [#3967](https://github.com/hatchet-dev/hatchet/pull/3967) - Add Dockerfile for all SDK e2e tests, fix flaky e2e tests by @juliusgeo in [#3846](https://github.com/hatchet-dev/hatchet/pull/3846) - ## v0.86.21 - 2026-05-19 ### Changed + - Pass `insertedAt` to OLAP payload reads by @mrkaye97 in [#3960](https://github.com/hatchet-dev/hatchet/pull/3960) ### Removed -- Revert "feat: route frontend billing through control plane" by @grutt in [#3963](https://github.com/hatchet-dev/hatchet/pull/3963) +- Revert "feat: route frontend billing through control plane" by @grutt in [#3963](https://github.com/hatchet-dev/hatchet/pull/3963) ## v0.86.20 - 2026-05-19 ### Changed -- Use `externalId` for retrieving payloads by @mrkaye97 in [#3914](https://github.com/hatchet-dev/hatchet/pull/3914) +- Use `externalId` for retrieving payloads by @mrkaye97 in [#3914](https://github.com/hatchet-dev/hatchet/pull/3914) ## v0.86.19 - 2026-05-19 ### Added + - Add logging for serial operation by @juliusgeo in [#3957](https://github.com/hatchet-dev/hatchet/pull/3957) - Added validation for seed password by @NathanDrake007 in [#3783](https://github.com/hatchet-dev/hatchet/pull/3783) - Route frontend billing through control plane by @grutt ### Fixed + - Deadlock on trigger workflows by @mrkaye97 in [#3906](https://github.com/hatchet-dev/hatchet/pull/3906) - Re-order auth flow to prevent side effects on handleCookieAuth by @gregfurman in [#3923](https://github.com/hatchet-dev/hatchet/pull/3923) - Dedupe security check in hatchet-lite by @MicroYui in [#3928](https://github.com/hatchet-dev/hatchet/pull/3928) @@ -170,2264 +180,2391 @@ - Poll plan by @grutt - Dedicated state by @grutt - ## v0.86.18 - 2026-05-14 ### Fixed -- New user flow on control plane deployment by @grutt in [#3891](https://github.com/hatchet-dev/hatchet/pull/3891) +- New user flow on control plane deployment by @grutt in [#3891](https://github.com/hatchet-dev/hatchet/pull/3891) ## v0.86.17 - 2026-05-13 ### Fixed -- Separate transactions in payload migration by @mrkaye97 in [#3907](https://github.com/hatchet-dev/hatchet/pull/3907) +- Separate transactions in payload migration by @mrkaye97 in [#3907](https://github.com/hatchet-dev/hatchet/pull/3907) ## v0.86.16 - 2026-05-13 ### Added -- Add error if counts don't match so we can debug by @mrkaye97 in [#3905](https://github.com/hatchet-dev/hatchet/pull/3905) +- Add error if counts don't match so we can debug by @mrkaye97 in [#3905](https://github.com/hatchet-dev/hatchet/pull/3905) ## v0.86.15 - 2026-05-13 ### Added -- Paginate through payloads by external id, not using the PK by @mrkaye97 in [#3883](https://github.com/hatchet-dev/hatchet/pull/3883) +- Paginate through payloads by external id, not using the PK by @mrkaye97 in [#3883](https://github.com/hatchet-dev/hatchet/pull/3883) ## v0.86.14 - 2026-05-13 ### Added -- Control plane redirect by @grutt in [#3904](https://github.com/hatchet-dev/hatchet/pull/3904) +- Control plane redirect by @grutt in [#3904](https://github.com/hatchet-dev/hatchet/pull/3904) ## v0.86.12 - 2026-05-12 ### Fixed -- Limit the number of re-pubs to be the max death count by @mrkaye97 in [#3899](https://github.com/hatchet-dev/hatchet/pull/3899) +- Limit the number of re-pubs to be the max death count by @mrkaye97 in [#3899](https://github.com/hatchet-dev/hatchet/pull/3899) ## v0.86.10 - 2026-05-12 ### Added -- Add external id index to payloads tables by @mrkaye97 in [#3879](https://github.com/hatchet-dev/hatchet/pull/3879) +- Add external id index to payloads tables by @mrkaye97 in [#3879](https://github.com/hatchet-dev/hatchet/pull/3879) ## v0.86.9 - 2026-05-12 ### Added + - Add cron schedule time to additional meta by @mrkaye97 in [#3884](https://github.com/hatchet-dev/hatchet/pull/3884) ### Changed + - New `taskNames` query param on the task stats endpoint for KEDA friendliness by @mnafees in [#3791](https://github.com/hatchet-dev/hatchet/pull/3791) ### Fixed -- Durable eviction / restore causing duplicate queue items by @mrkaye97 in [#3864](https://github.com/hatchet-dev/hatchet/pull/3864) +- Durable eviction / restore causing duplicate queue items by @mrkaye97 in [#3864](https://github.com/hatchet-dev/hatchet/pull/3864) ## v0.86.8 - 2026-05-11 ### Fixed + - Avoid invalid user FK in cookie test by @igor-kupczynski in [#3882](https://github.com/hatchet-dev/hatchet/pull/3882) - Occasional empty payloads on DAG task retries by @juliusgeo in [#3860](https://github.com/hatchet-dev/hatchet/pull/3860) - ## v0.86.7 - 2026-05-11 ### Fixed -- Add backoff to sleeps by @mrkaye97 in [#3880](https://github.com/hatchet-dev/hatchet/pull/3880) +- Add backoff to sleeps by @mrkaye97 in [#3880](https://github.com/hatchet-dev/hatchet/pull/3880) ## v0.86.6 - 2026-05-11 ### Added + - Add migratediag package for DSN redaction and error handling by @igor-kupczynski in [#3878](https://github.com/hatchet-dev/hatchet/pull/3878) ### Fixed -- Allow partial successes on status updates by @mrkaye97 in [#3861](https://github.com/hatchet-dev/hatchet/pull/3861) +- Allow partial successes on status updates by @mrkaye97 in [#3861](https://github.com/hatchet-dev/hatchet/pull/3861) ## v0.86.5 - 2026-05-08 ### Fixed -- Use correct log out and invalidate stale cookies by @grutt in [#3858](https://github.com/hatchet-dev/hatchet/pull/3858) +- Use correct log out and invalidate stale cookies by @grutt in [#3858](https://github.com/hatchet-dev/hatchet/pull/3858) ## v0.86.4 - 2026-05-08 ### Added + - Configurable OLAP MQ QoS by @mrkaye97 in [#3857](https://github.com/hatchet-dev/hatchet/pull/3857) - Add unit tests for DurableEventsListener reconnect and event delivery during retry backoff by @igor-kupczynski ### Fixed -- Durable wait mutex issue by @mrkaye97 +- Durable wait mutex issue by @mrkaye97 ## v0.86.1 - 2026-05-06 ### Added -- Add cols to offload table to track count diffs by @mrkaye97 in [#3841](https://github.com/hatchet-dev/hatchet/pull/3841) +- Add cols to offload table to track count diffs by @mrkaye97 in [#3841](https://github.com/hatchet-dev/hatchet/pull/3841) ## v0.85.10 - 2026-05-06 ### Fixed -- Merge issue? these definitely had been removed before... by @mrkaye97 in [#3839](https://github.com/hatchet-dev/hatchet/pull/3839) +- Merge issue? these definitely had been removed before... by @mrkaye97 in [#3839](https://github.com/hatchet-dev/hatchet/pull/3839) ## v0.85.9 - 2026-05-06 ### Fixed + - Remove dual writes into temp tables for status updates by @mrkaye97 in [#3829](https://github.com/hatchet-dev/hatchet/pull/3829) - Slow cold start when workflows are inactive > 1 day by @juliusgeo in [#3830](https://github.com/hatchet-dev/hatchet/pull/3830) - ## v0.85.8 - 2026-05-06 ### Fixed + - Prune partitions in reconciliation query with min inserted at filter by @mrkaye97 in [#3838](https://github.com/hatchet-dev/hatchet/pull/3838) - Dedupe task ids before updating to assigned by @mrkaye97 in [#3818](https://github.com/hatchet-dev/hatchet/pull/3818) - ## v0.85.7 - 2026-05-05 ### Fixed + - Add back task event tmp dual write by @mrkaye97 in [#3828](https://github.com/hatchet-dev/hatchet/pull/3828) - Improve performance of status metrics query via index usage + partition pruning by @mrkaye97 in [#3800](https://github.com/hatchet-dev/hatchet/pull/3800) - Improve transactional safety of payload offload reads and writes by @mrkaye97 in [#3814](https://github.com/hatchet-dev/hatchet/pull/3814) - ## v0.85.6 - 2026-05-05 ### Fixed -- Payload external id dupe by @mrkaye97 in [#3824](https://github.com/hatchet-dev/hatchet/pull/3824) +- Payload external id dupe by @mrkaye97 in [#3824](https://github.com/hatchet-dev/hatchet/pull/3824) ## v0.85.5 - 2026-05-05 ### Added + - Direct task + dag status updates by @mrkaye97 in [#3554](https://github.com/hatchet-dev/hatchet/pull/3554) - Swap links to control plane by @grutt in [#3816](https://github.com/hatchet-dev/hatchet/pull/3816) - ## v0.85.4 - 2026-05-04 ### Fixed -- Show tenant invite modal correctly by @mrkaye97 in [#3815](https://github.com/hatchet-dev/hatchet/pull/3815) +- Show tenant invite modal correctly by @mrkaye97 in [#3815](https://github.com/hatchet-dev/hatchet/pull/3815) ## v0.85.3 - 2026-05-02 ### Fixed + - Panic on trigger by @mrkaye97 in [#3803](https://github.com/hatchet-dev/hatchet/pull/3803) - Refresh token edge cases by @grutt in [#3802](https://github.com/hatchet-dev/hatchet/pull/3802) - ## v0.85.2 - 2026-05-01 ### Fixed -- Wait for user universe by @grutt in [#3801](https://github.com/hatchet-dev/hatchet/pull/3801) +- Wait for user universe by @grutt in [#3801](https://github.com/hatchet-dev/hatchet/pull/3801) ## v0.85.1 - 2026-05-01 ### Fixed -- Hoist org management / tenant management page by @mrkaye97 in [#3797](https://github.com/hatchet-dev/hatchet/pull/3797) +- Hoist org management / tenant management page by @mrkaye97 in [#3797](https://github.com/hatchet-dev/hatchet/pull/3797) ## v0.85.0 - 2026-05-01 ### Added + - Per organization inactivity timeout by @juliusgeo in [#3795](https://github.com/hatchet-dev/hatchet/pull/3795) - Shard-affinity by @grutt in [#3788](https://github.com/hatchet-dev/hatchet/pull/3788) - Add force SSO toggle by @juliusgeo in [#3787](https://github.com/hatchet-dev/hatchet/pull/3787) ### Fixed + - Fixes for SSO UI layout by @juliusgeo in [#3793](https://github.com/hatchet-dev/hatchet/pull/3793) - Fix--control-plane-bash by @grutt in [#3781](https://github.com/hatchet-dev/hatchet/pull/3781) - ## v0.83.61 - 2026-04-29 ### Added + - Feature flag sso by @grutt in [#3786](https://github.com/hatchet-dev/hatchet/pull/3786) ### Changed + - Remove unused err var by @jishnundth in [#3785](https://github.com/hatchet-dev/hatchet/pull/3785) ### Fixed -- Workflow reschedule update fail by @jishnundth in [#3782](https://github.com/hatchet-dev/hatchet/pull/3782) +- Workflow reschedule update fail by @jishnundth in [#3782](https://github.com/hatchet-dev/hatchet/pull/3782) ## v0.83.60 - 2026-04-28 ### Fixed + - Add cron job to deactivate stale step concurrency configs by @mrkaye97 in [#3775](https://github.com/hatchet-dev/hatchet/pull/3775) - Use `SimpleTable` for span attributes by @mrkaye97 in [#3778](https://github.com/hatchet-dev/hatchet/pull/3778) - ## v0.83.59 - 2026-04-28 ### Added + - Durable go by @grutt in [#3696](https://github.com/hatchet-dev/hatchet/pull/3696) ### Fixed -- Attempt to fix deadlocks, part 1bn by @mrkaye97 in [#3776](https://github.com/hatchet-dev/hatchet/pull/3776) +- Attempt to fix deadlocks, part 1bn by @mrkaye97 in [#3776](https://github.com/hatchet-dev/hatchet/pull/3776) ## v0.83.57 - 2026-04-28 ### Changed + - Ignore invalid step IDs when inserting or replaying tasks by @mnafees in [#3735](https://github.com/hatchet-dev/hatchet/pull/3735) - Make all list partition calls specific to v1 tenants only by @mnafees in [#3758](https://github.com/hatchet-dev/hatchet/pull/3758) ### Fixed + - Don't drop index by @mrkaye97 in [#3765](https://github.com/hatchet-dev/hatchet/pull/3765) - Increase max password length to 64 and improve validation error message by @NathanDrake007 in [#3713](https://github.com/hatchet-dev/hatchet/pull/3713) - ## v0.83.56 - 2026-04-28 ### Fixed -- Acquire locks right away by @mrkaye97 in [#3762](https://github.com/hatchet-dev/hatchet/pull/3762) +- Acquire locks right away by @mrkaye97 in [#3762](https://github.com/hatchet-dev/hatchet/pull/3762) ## v0.83.55 - 2026-04-28 ### Fixed -- Improve performance of cron schedule polling query by @mrkaye97 in [#3754](https://github.com/hatchet-dev/hatchet/pull/3754) +- Improve performance of cron schedule polling query by @mrkaye97 in [#3754](https://github.com/hatchet-dev/hatchet/pull/3754) ## v0.83.54 - 2026-04-27 ### Fixed -- Remove row count check, replace with threshold by @mrkaye97 in [#3756](https://github.com/hatchet-dev/hatchet/pull/3756) +- Remove row count check, replace with threshold by @mrkaye97 in [#3756](https://github.com/hatchet-dev/hatchet/pull/3756) ## v0.83.53 - 2026-04-27 ### Added + - Go partition by partition by @mrkaye97 in [#3755](https://github.com/hatchet-dev/hatchet/pull/3755) - Frontend SSO support by @juliusgeo in [#3582](https://github.com/hatchet-dev/hatchet/pull/3582) ### Fixed -- Trace view improvements by @mrkaye97 in [#3702](https://github.com/hatchet-dev/hatchet/pull/3702) +- Trace view improvements by @mrkaye97 in [#3702](https://github.com/hatchet-dev/hatchet/pull/3702) ## v0.83.52 - 2026-04-27 ### Fixed -- Do nothing on conflict, and diff out existing rows by @mrkaye97 in [#3752](https://github.com/hatchet-dev/hatchet/pull/3752) +- Do nothing on conflict, and diff out existing rows by @mrkaye97 in [#3752](https://github.com/hatchet-dev/hatchet/pull/3752) ## v0.83.50 - 2026-04-27 ### Fixed -- Add exclusive lock, run analyze by @mrkaye97 in [#3739](https://github.com/hatchet-dev/hatchet/pull/3739) +- Add exclusive lock, run analyze by @mrkaye97 in [#3739](https://github.com/hatchet-dev/hatchet/pull/3739) ## v0.83.49 - 2026-04-27 ### Changed + - Error out when marking queue items as resolved by @mnafees in [#3736](https://github.com/hatchet-dev/hatchet/pull/3736) ### Fixed -- Add row count check before swapping by @mrkaye97 in [#3709](https://github.com/hatchet-dev/hatchet/pull/3709) +- Add row count check before swapping by @mrkaye97 in [#3709](https://github.com/hatchet-dev/hatchet/pull/3709) ## v0.83.48 - 2026-04-24 ### Fixed -- Fetching state causing redirect loop on signup by @mrkaye97 in [#3708](https://github.com/hatchet-dev/hatchet/pull/3708) +- Fetching state causing redirect loop on signup by @mrkaye97 in [#3708](https://github.com/hatchet-dev/hatchet/pull/3708) ## v0.83.47 - 2026-04-24 ### Fixed -- Broken migration by @mrkaye97 in [#3707](https://github.com/hatchet-dev/hatchet/pull/3707) +- Broken migration by @mrkaye97 in [#3707](https://github.com/hatchet-dev/hatchet/pull/3707) ## v0.83.46 - 2026-04-24 ### Fixed -- Remove status partitioning on `v1_(runs|dags|tasks)_olap` by @mrkaye97 in [#3603](https://github.com/hatchet-dev/hatchet/pull/3603) +- Remove status partitioning on `v1_(runs|dags|tasks)_olap` by @mrkaye97 in [#3603](https://github.com/hatchet-dev/hatchet/pull/3603) ## v0.83.45 - 2026-04-24 ### Fixed -- Regen control plane api by @abelanger5 in [#3705](https://github.com/hatchet-dev/hatchet/pull/3705) +- Regen control plane api by @abelanger5 in [#3705](https://github.com/hatchet-dev/hatchet/pull/3705) ## v0.83.44 - 2026-04-24 ### Changed -- Make sure we call for durable task invocation only for durable tasks in the scheduler by @mnafees in [#3698](https://github.com/hatchet-dev/hatchet/pull/3698) +- Make sure we call for durable task invocation only for durable tasks in the scheduler by @mnafees in [#3698](https://github.com/hatchet-dev/hatchet/pull/3698) ## v0.83.43 - 2026-04-24 ### Added -- Separate frontend and server url, pass the server url as part of the tenant by @abelanger5 in [#3697](https://github.com/hatchet-dev/hatchet/pull/3697) +- Separate frontend and server url, pass the server url as part of the tenant by @abelanger5 in [#3697](https://github.com/hatchet-dev/hatchet/pull/3697) ## v0.83.42 - 2026-04-24 ### Fixed -- Couple more frontend + webhooks things by @mrkaye97 in [#3695](https://github.com/hatchet-dev/hatchet/pull/3695) +- Couple more frontend + webhooks things by @mrkaye97 in [#3695](https://github.com/hatchet-dev/hatchet/pull/3695) ## v0.83.41 - 2026-04-23 ### Fixed -- Tailwind hell by @mrkaye97 in [#3693](https://github.com/hatchet-dev/hatchet/pull/3693) +- Tailwind hell by @mrkaye97 in [#3693](https://github.com/hatchet-dev/hatchet/pull/3693) ## v0.83.40 - 2026-04-23 ### Fixed -- Couple more frontend bugs by @mrkaye97 in [#3692](https://github.com/hatchet-dev/hatchet/pull/3692) +- Couple more frontend bugs by @mrkaye97 in [#3692](https://github.com/hatchet-dev/hatchet/pull/3692) ## v0.83.39 - 2026-04-23 ### Fixed + - Return from delete query by @abelanger5 in [#3690](https://github.com/hatchet-dev/hatchet/pull/3690) - Validate tenant membership on V1DagListTasks by @abelanger5 in [#3691](https://github.com/hatchet-dev/hatchet/pull/3691) - ## v0.83.38 - 2026-04-23 ### Added + - Workflow filter FTS by @mrkaye97 in [#3685](https://github.com/hatchet-dev/hatchet/pull/3685) ### Changed + - Make sure we don't poll for work in case of deleted tenants by @mnafees in [#3682](https://github.com/hatchet-dev/hatchet/pull/3682) - Also clean slot configs for workers by @mnafees in [#3666](https://github.com/hatchet-dev/hatchet/pull/3666) - Cache CEL programs in order to avoid expensive heap allocations on each event match by @mnafees in [#3667](https://github.com/hatchet-dev/hatchet/pull/3667) ### Fixed + - Clean up a bunch of settings pages on the dashboard by @mrkaye97 in [#3669](https://github.com/hatchet-dev/hatchet/pull/3669) - Heights of event log, etc. on the dag view by @mrkaye97 in [#3687](https://github.com/hatchet-dev/hatchet/pull/3687) - Significantly speed up event queries by @mrkaye97 in [#3688](https://github.com/hatchet-dev/hatchet/pull/3688) - ## v0.83.37 - 2026-04-22 ### Fixed + - Collision on lastTenant key by @abelanger5 in [#3671](https://github.com/hatchet-dev/hatchet/pull/3671) - Use exchange token for getting plans by @abelanger5 in [#3668](https://github.com/hatchet-dev/hatchet/pull/3668) - ## v0.83.36 - 2026-04-22 ### Added + - Remove "account" dropdown, add notifications dropdown by @mrkaye97 in [#3665](https://github.com/hatchet-dev/hatchet/pull/3665) ### Fixed -- Webhook responses, event info on context, internal fix for labels matches by @mrkaye97 in [#3625](https://github.com/hatchet-dev/hatchet/pull/3625) +- Webhook responses, event info on context, internal fix for labels matches by @mrkaye97 in [#3625](https://github.com/hatchet-dev/hatchet/pull/3625) ## v0.83.35 - 2026-04-22 ### Removed -- Revert "Remove "account" dropdown', add notifications dropdown " by @mrkaye97 in [#3664](https://github.com/hatchet-dev/hatchet/pull/3664) +- Revert "Remove "account" dropdown', add notifications dropdown " by @mrkaye97 in [#3664](https://github.com/hatchet-dev/hatchet/pull/3664) ## v0.83.34 - 2026-04-21 ### Changed + - Cleanup old workers via daily gocron by @mnafees in [#3663](https://github.com/hatchet-dev/hatchet/pull/3663) ### Removed -- Remove "account" dropdown', add notifications dropdown by @TehShrike in [#3365](https://github.com/hatchet-dev/hatchet/pull/3365) +- Remove "account" dropdown', add notifications dropdown by @TehShrike in [#3365](https://github.com/hatchet-dev/hatchet/pull/3365) ## v0.83.33 - 2026-04-21 ### Fixed -- Disable version info for control plane, add sync for tenant alerting settings by @abelanger5 in [#3659](https://github.com/hatchet-dev/hatchet/pull/3659) +- Disable version info for control plane, add sync for tenant alerting settings by @abelanger5 in [#3659](https://github.com/hatchet-dev/hatchet/pull/3659) ## v0.83.32 - 2026-04-21 ### Added -- Durable execution frontend work and API improvements by @mrkaye97 in [#3639](https://github.com/hatchet-dev/hatchet/pull/3639) +- Durable execution frontend work and API improvements by @mrkaye97 in [#3639](https://github.com/hatchet-dev/hatchet/pull/3639) ## v0.83.31 - 2026-04-21 ### Fixed -- Use exchange token interceptor for cloud endpoints by @abelanger5 in [#3658](https://github.com/hatchet-dev/hatchet/pull/3658) +- Use exchange token interceptor for cloud endpoints by @abelanger5 in [#3658](https://github.com/hatchet-dev/hatchet/pull/3658) ## v0.83.30 - 2026-04-21 ### Fixed -- Set cloud enabled when control plane is enabled by @abelanger5 in [#3657](https://github.com/hatchet-dev/hatchet/pull/3657) +- Set cloud enabled when control plane is enabled by @abelanger5 in [#3657](https://github.com/hatchet-dev/hatchet/pull/3657) ## v0.83.29 - 2026-04-21 ### Fixed -- Fix control-plane logout bug, fix oauth redirect urls by @juliusgeo in [#3656](https://github.com/hatchet-dev/hatchet/pull/3656) +- Fix control-plane logout bug, fix oauth redirect urls by @juliusgeo in [#3656](https://github.com/hatchet-dev/hatchet/pull/3656) ## v0.83.28 - 2026-04-21 ### Changed -- Conditionally use control-plane metadata endpoint by @juliusgeo in [#3654](https://github.com/hatchet-dev/hatchet/pull/3654) +- Conditionally use control-plane metadata endpoint by @juliusgeo in [#3654](https://github.com/hatchet-dev/hatchet/pull/3654) ## v0.83.27 - 2026-04-20 ### Added + - Rate limit deletion by @juliusgeo in [#3638](https://github.com/hatchet-dev/hatchet/pull/3638) ### Fixed -- Infinite rerender bug and handle 403 better by @abelanger5 in [#3652](https://github.com/hatchet-dev/hatchet/pull/3652) +- Infinite rerender bug and handle 403 better by @abelanger5 in [#3652](https://github.com/hatchet-dev/hatchet/pull/3652) ## v0.83.26 - 2026-04-20 ### Added + - Sync repository for syncing data into a tenant by @abelanger5 in [#3614](https://github.com/hatchet-dev/hatchet/pull/3614) ### Changed + - Generate insecure keysets locally by @juliusgeo in [#3622](https://github.com/hatchet-dev/hatchet/pull/3622) ### Fixed -- Exchange token authz, remove n+1 tenant lookup for organizations, slack oauth casing by @abelanger5 in [#3631](https://github.com/hatchet-dev/hatchet/pull/3631) +- Exchange token authz, remove n+1 tenant lookup for organizations, slack oauth casing by @abelanger5 in [#3631](https://github.com/hatchet-dev/hatchet/pull/3631) ## v0.83.25 - 2026-04-15 ### Fixed -- Configurable schedulerCheckActive Interval by @grutt in [#3624](https://github.com/hatchet-dev/hatchet/pull/3624) +- Configurable schedulerCheckActive Interval by @grutt in [#3624](https://github.com/hatchet-dev/hatchet/pull/3624) ## v0.83.24 - 2026-04-15 ### Changed + - Rename PgBouncer env vars by @mnafees in [#3319](https://github.com/hatchet-dev/hatchet/pull/3319) ### Fixed + - Empty parent task external id on olap dags and runs by @mrkaye97 in [#3605](https://github.com/hatchet-dev/hatchet/pull/3605) - Broken doc links by @mrkaye97 in [#3616](https://github.com/hatchet-dev/hatchet/pull/3616) - ## v0.83.23 - 2026-04-13 ### Added + - No limit modal with no limits by @grutt in [#3572](https://github.com/hatchet-dev/hatchet/pull/3572) ### Changed + - Make rbac package generic and reusable by @abelanger5 in [#3581](https://github.com/hatchet-dev/hatchet/pull/3581) ### Fixed -- Add healthcheck to harness engine startup by @gregfurman in [#3601](https://github.com/hatchet-dev/hatchet/pull/3601) +- Add healthcheck to harness engine startup by @gregfurman in [#3601](https://github.com/hatchet-dev/hatchet/pull/3601) ## v0.83.22 - 2026-04-10 ### Removed + - Run actions on gha runners again by @mrkaye97 in [#3588](https://github.com/hatchet-dev/hatchet/pull/3588) ### Fixed + - Worker labels not respected on retry by @mrkaye97 in [#3591](https://github.com/hatchet-dev/hatchet/pull/3591) - Run payloads job at midnight by @mrkaye97 in [#3578](https://github.com/hatchet-dev/hatchet/pull/3578) - ## v0.83.18 - 2026-04-07 ### Added -- Wait for event with lookback window by @mrkaye97 in [#3442](https://github.com/hatchet-dev/hatchet/pull/3442) +- Wait for event with lookback window by @mrkaye97 in [#3442](https://github.com/hatchet-dev/hatchet/pull/3442) ## v0.83.17 - 2026-04-07 ### Added -- Feat--offers by @grutt in [#3511](https://github.com/hatchet-dev/hatchet/pull/3511) +- Feat--offers by @grutt in [#3511](https://github.com/hatchet-dev/hatchet/pull/3511) ## v0.83.15 - 2026-04-03 ### Added + - Exchange token mechanism and CORs headers by @abelanger5 in [#3405](https://github.com/hatchet-dev/hatchet/pull/3405) - Control plane phase 2, frontend changes by @abelanger5 in [#3536](https://github.com/hatchet-dev/hatchet/pull/3536) ### Fixed -- OTel trace lookup insert deadlock by @mrkaye97 in [#3542](https://github.com/hatchet-dev/hatchet/pull/3542) +- OTel trace lookup insert deadlock by @mrkaye97 in [#3542](https://github.com/hatchet-dev/hatchet/pull/3542) ## v0.83.14 - 2026-04-03 ### Fixed -- Properly list workflows in dropdown by @mrkaye97 in [#3534](https://github.com/hatchet-dev/hatchet/pull/3534) +- Properly list workflows in dropdown by @mrkaye97 in [#3534](https://github.com/hatchet-dev/hatchet/pull/3534) ## v0.83.13 - 2026-04-03 ### Added + - Enable event log on core db by @mrkaye97 in [#3537](https://github.com/hatchet-dev/hatchet/pull/3537) - Add output tab to workflow run details by @grutt in [#3393](https://github.com/hatchet-dev/hatchet/pull/3393) ### Fixed -- Fix--dag-nesting by @grutt in [#3447](https://github.com/hatchet-dev/hatchet/pull/3447) +- Fix--dag-nesting by @grutt in [#3447](https://github.com/hatchet-dev/hatchet/pull/3447) ## v0.83.10 - 2026-04-01 ### Fixed -- No self-referencing spans by @grutt in [#3445](https://github.com/hatchet-dev/hatchet/pull/3445) +- No self-referencing spans by @grutt in [#3445](https://github.com/hatchet-dev/hatchet/pull/3445) ## v0.83.9 - 2026-04-01 ### Fixed -- Otel-bug-bash by @grutt in [#3389](https://github.com/hatchet-dev/hatchet/pull/3389) +- Otel-bug-bash by @grutt in [#3389](https://github.com/hatchet-dev/hatchet/pull/3389) ## v0.83.8 - 2026-04-01 ### Changed -- Update management API swagger with audit logs endpoint by @mnafees in [#3414](https://github.com/hatchet-dev/hatchet/pull/3414) +- Update management API swagger with audit logs endpoint by @mnafees in [#3414](https://github.com/hatchet-dev/hatchet/pull/3414) ## v0.83.7 - 2026-04-01 ### Changed -- Backend proxy for getting feature flags by @mrkaye97 in [#3437](https://github.com/hatchet-dev/hatchet/pull/3437) +- Backend proxy for getting feature flags by @mrkaye97 in [#3437](https://github.com/hatchet-dev/hatchet/pull/3437) ## v0.83.5 - 2026-04-01 ### Added + - Log filtering by workflow by @mrkaye97 in [#3435](https://github.com/hatchet-dev/hatchet/pull/3435) ### Changed + - Do not error out for historical run data for workflows and workflow versions that may have been deleted by @mnafees in [#3403](https://github.com/hatchet-dev/hatchet/pull/3403) ### Fixed -- Flaky Cypress test by @mrkaye97 in [#3436](https://github.com/hatchet-dev/hatchet/pull/3436) +- Flaky Cypress test by @mrkaye97 in [#3436](https://github.com/hatchet-dev/hatchet/pull/3436) ## v0.83.4 - 2026-03-26 ### Added -- Add more detailed logging to in memory advisory lock, configurable timeout by @juliusgeo in [#3408](https://github.com/hatchet-dev/hatchet/pull/3408) +- Add more detailed logging to in memory advisory lock, configurable timeout by @juliusgeo in [#3408](https://github.com/hatchet-dev/hatchet/pull/3408) ## v0.83.3 - 2026-03-25 ### Changed -- (fix): Make sure sqlc generates TotalReads as int64 by @juliusgeo in [#3404](https://github.com/hatchet-dev/hatchet/pull/3404) +- (fix): Make sure sqlc generates TotalReads as int64 by @juliusgeo in [#3404](https://github.com/hatchet-dev/hatchet/pull/3404) ## v0.83.2 - 2026-03-25 ### Added -- Add in memory, keyed, locking queue to protect RunConcurrencyStrategy by @juliusgeo in [#3384](https://github.com/hatchet-dev/hatchet/pull/3384) +- Add in memory, keyed, locking queue to protect RunConcurrencyStrategy by @juliusgeo in [#3384](https://github.com/hatchet-dev/hatchet/pull/3384) ## v0.83.1 - 2026-03-25 ### Added + - Add timeout context to OTel shutdown by @juliusgeo in [#3366](https://github.com/hatchet-dev/hatchet/pull/3366) ### Changed + - Use inserted at for proper indexing in DAG replay queries by @mnafees in [#3399](https://github.com/hatchet-dev/hatchet/pull/3399) ### Fixed -- Panic on lookup table create by @grutt in [#3385](https://github.com/hatchet-dev/hatchet/pull/3385) +- Panic on lookup table create by @grutt in [#3385](https://github.com/hatchet-dev/hatchet/pull/3385) ## v0.83.0 - 2026-03-24 ### Added + - Enable tenant-scoped logs view by @abelanger5 in [#3381](https://github.com/hatchet-dev/hatchet/pull/3381) ### Changed -- Get rid of jsonschema for security fix by @mnafees in [#3379](https://github.com/hatchet-dev/hatchet/pull/3379) +- Get rid of jsonschema for security fix by @mnafees in [#3379](https://github.com/hatchet-dev/hatchet/pull/3379) ## v0.82.3 - 2026-03-23 ### Changed + - Hide log-level view by @abelanger5 in [#3374](https://github.com/hatchet-dev/hatchet/pull/3374) ### Fixed -- Bump engine ver by @mrkaye97 in [#3373](https://github.com/hatchet-dev/hatchet/pull/3373) +- Bump engine ver by @mrkaye97 in [#3373](https://github.com/hatchet-dev/hatchet/pull/3373) ## v0.82.2 - 2026-03-23 ### Fixed -- Write engine spans to lookup table by @grutt in [#3371](https://github.com/hatchet-dev/hatchet/pull/3371) +- Write engine spans to lookup table by @grutt in [#3371](https://github.com/hatchet-dev/hatchet/pull/3371) ## v0.82.1 - 2026-03-23 ### Changed + - Move OTel tables to OLAP repo by @mnafees in [#3369](https://github.com/hatchet-dev/hatchet/pull/3369) - Don't flicker the runs table when refetching by @TehShrike in [#3367](https://github.com/hatchet-dev/hatchet/pull/3367) - ## v0.82.0 - 2026-03-23 ### Added + - Pay as you go pricing by @grutt in [#3353](https://github.com/hatchet-dev/hatchet/pull/3353) ### Changed + - Observability overhaul + traces support by @mnafees in [#3213](https://github.com/hatchet-dev/hatchet/pull/3213) - Improve error message when failing to send task to worker by @juliusgeo in [#3350](https://github.com/hatchet-dev/hatchet/pull/3350) - Some fixes for load test plot generation by @juliusgeo in [#3338](https://github.com/hatchet-dev/hatchet/pull/3338) ### Removed + - Remove server ShutdownWait by @juliusgeo in [#3351](https://github.com/hatchet-dev/hatchet/pull/3351) ### Fixed -- Fallback when Tenant ID is zero UUID by @gregfurman in [#3349](https://github.com/hatchet-dev/hatchet/pull/3349) +- Fallback when Tenant ID is zero UUID by @gregfurman in [#3349](https://github.com/hatchet-dev/hatchet/pull/3349) ## v0.81.2 - 2026-03-19 ### Added + - Add min height to logs chart by @abelanger5 in [#3337](https://github.com/hatchet-dev/hatchet/pull/3337) - Add latency plots to load test by @juliusgeo in [#3259](https://github.com/hatchet-dev/hatchet/pull/3259) ### Changed -- Only return non-archived tenants in TenantMember queries by @TehShrike in [#3317](https://github.com/hatchet-dev/hatchet/pull/3317) +- Only return non-archived tenants in TenantMember queries by @TehShrike in [#3317](https://github.com/hatchet-dev/hatchet/pull/3317) ## v0.81.0 - 2026-03-18 ### Added -- Tenant-scoped logs view by @abelanger5 in [#3307](https://github.com/hatchet-dev/hatchet/pull/3307) +- Tenant-scoped logs view by @abelanger5 in [#3307](https://github.com/hatchet-dev/hatchet/pull/3307) ## v0.80.10 - 2026-03-18 ### Changed -- Separate out partition cleanup helpers for visibility by @juliusgeo in [#3321](https://github.com/hatchet-dev/hatchet/pull/3321) +- Separate out partition cleanup helpers for visibility by @juliusgeo in [#3321](https://github.com/hatchet-dev/hatchet/pull/3321) ## v0.80.9 - 2026-03-18 ### Fixed -- Honor active docker context when DOCKER_HOST is unset by @BloggerBust in [#3251](https://github.com/hatchet-dev/hatchet/pull/3251) +- Honor active docker context when DOCKER_HOST is unset by @BloggerBust in [#3251](https://github.com/hatchet-dev/hatchet/pull/3251) ## v0.80.8 - 2026-03-18 ### Changed + - Guarantee that organization tenants will always be an array by @TehShrike in [#3316](https://github.com/hatchet-dev/hatchet/pull/3316) - New organizations+tenants screen by @TehShrike in [#3198](https://github.com/hatchet-dev/hatchet/pull/3198) - Improved invitation accept screen by @TehShrike in [#3151](https://github.com/hatchet-dev/hatchet/pull/3151) ### Removed + - Remove dispatch backlog, replace with timeout lock acquisition by @juliusgeo in [#3290](https://github.com/hatchet-dev/hatchet/pull/3290) ### Fixed -- Cleanup orphaned metrics by @grutt in [#3300](https://github.com/hatchet-dev/hatchet/pull/3300) +- Cleanup orphaned metrics by @grutt in [#3300](https://github.com/hatchet-dev/hatchet/pull/3300) ## v0.80.4 - 2026-03-17 ### Added + - Add `workflow_run_external_id` to trigger run ack proto by @mrkaye97 in [#3299](https://github.com/hatchet-dev/hatchet/pull/3299) ### Fixed + - Enhance SQL query name extraction in otel tracer and fallback by @grutt in [#3277](https://github.com/hatchet-dev/hatchet/pull/3277) - Silence tenant alert error by @grutt in [#3298](https://github.com/hatchet-dev/hatchet/pull/3298) - ## v0.80.3 - 2026-03-16 ### Added + - Add cleanup module to handle graceful shutdown, improve logging experience by @juliusgeo in [#3260](https://github.com/hatchet-dev/hatchet/pull/3260) - Durable Execution Revamp by @mrkaye97 in [#2954](https://github.com/hatchet-dev/hatchet/pull/2954) ### Fixed -- Remove join in currency queries by @grutt in [#3294](https://github.com/hatchet-dev/hatchet/pull/3294) +- Remove join in currency queries by @grutt in [#3294](https://github.com/hatchet-dev/hatchet/pull/3294) ## v0.79.44 - 2026-03-15 ### Added + - Add support for additional RBAC configruation via YAML configuration by @grutt in [#3285](https://github.com/hatchet-dev/hatchet/pull/3285) ### Changed + - Gracefully handle empty bulk scheduled deletes by @avirajkhare00 in [#3279](https://github.com/hatchet-dev/hatchet/pull/3279) ### Fixed + - Sort registered workflows on the worker page by @mrkaye97 in [#3284](https://github.com/hatchet-dev/hatchet/pull/3284) - Validate the presence of null unicode in output by @gregfurman in [#3164](https://github.com/hatchet-dev/hatchet/pull/3164) - Configure logger for Hatchet client by @gregfurman in [#3046](https://github.com/hatchet-dev/hatchet/pull/3046) - ## v0.79.43 - 2026-03-13 ### Fixed + - Delete missing workers by @mrkaye97 in [#3273](https://github.com/hatchet-dev/hatchet/pull/3273) - Dont double count wf by @grutt in [#3270](https://github.com/hatchet-dev/hatchet/pull/3270) - ## v0.79.41 - 2026-03-13 ### Changed + - Attempt to fix deadlock in `PollScheduledWorkflows` by scoping `FOR UPDATE` lock by @mnafees in [#3261](https://github.com/hatchet-dev/hatchet/pull/3261) - Janky Fix: Extract input payloads for standalone tasks for dashboard by @mrkaye97 in [#3128](https://github.com/hatchet-dev/hatchet/pull/3128) - ## v0.79.39 - 2026-03-13 ### Fixed + - Error on max aggregate keys by @grutt in [#3267](https://github.com/hatchet-dev/hatchet/pull/3267) - Otel config loader and trunc query name by @grutt in [#3266](https://github.com/hatchet-dev/hatchet/pull/3266) - ## v0.79.38 - 2026-03-13 ### Fixed + - Analytics no set by @grutt in [#3264](https://github.com/hatchet-dev/hatchet/pull/3264) - Add user id to context on login by @grutt in [#3256](https://github.com/hatchet-dev/hatchet/pull/3256) - ## v0.79.37 - 2026-03-12 ### Fixed -- Dont burry analytics properties by @grutt in [#3254](https://github.com/hatchet-dev/hatchet/pull/3254) +- Dont burry analytics properties by @grutt in [#3254](https://github.com/hatchet-dev/hatchet/pull/3254) ## v0.79.36 - 2026-03-12 ### Added -- Feat--consistent-analytics-events by @grutt in [#3239](https://github.com/hatchet-dev/hatchet/pull/3239) +- Feat--consistent-analytics-events by @grutt in [#3239](https://github.com/hatchet-dev/hatchet/pull/3239) ## v0.79.35 - 2026-03-12 ### Changed -- Pool gzip writers to reduce RabbitMQ message compression allocations by @mnafees in [#3103](https://github.com/hatchet-dev/hatchet/pull/3103) +- Pool gzip writers to reduce RabbitMQ message compression allocations by @mnafees in [#3103](https://github.com/hatchet-dev/hatchet/pull/3103) ## v0.79.34 - 2026-03-12 ### Added + - Env var for stream event buffer timeout by @mrkaye97 in [#3223](https://github.com/hatchet-dev/hatchet/pull/3223) ### Changed + - Don't panic in AuthZ, bubble up instead by @juliusgeo in [#3238](https://github.com/hatchet-dev/hatchet/pull/3238) ### Fixed + - Failure after cancellation by @grutt in [#3243](https://github.com/hatchet-dev/hatchet/pull/3243) - Fix owner invitation bug by @juliusgeo in [#3230](https://github.com/hatchet-dev/hatchet/pull/3230) - ## v0.79.33 - 2026-03-10 ### Changed + - RBAC v0 by @juliusgeo in [#3185](https://github.com/hatchet-dev/hatchet/pull/3185) ### Fixed -- Fix frontend build issues with latest cloud API by @mnafees in [#3216](https://github.com/hatchet-dev/hatchet/pull/3216) +- Fix frontend build issues with latest cloud API by @mnafees in [#3216](https://github.com/hatchet-dev/hatchet/pull/3216) ## v0.79.29 - 2026-03-07 ### Added -- Add callback support for tenant and tenant member updates by @abelanger5 in [#3201](https://github.com/hatchet-dev/hatchet/pull/3201) +- Add callback support for tenant and tenant member updates by @abelanger5 in [#3201](https://github.com/hatchet-dev/hatchet/pull/3201) ## v0.79.23 - 2026-03-06 ### Changed -- Pause workflow query is deprecated so remove the option from the frontend by @mnafees in [#3183](https://github.com/hatchet-dev/hatchet/pull/3183) +- Pause workflow query is deprecated so remove the option from the frontend by @mnafees in [#3183](https://github.com/hatchet-dev/hatchet/pull/3183) ## v0.79.17 - 2026-03-04 ### Fixed -- Go unexported type by @mrkaye97 in [#3160](https://github.com/hatchet-dev/hatchet/pull/3160) +- Go unexported type by @mrkaye97 in [#3160](https://github.com/hatchet-dev/hatchet/pull/3160) ## v0.79.16 - 2026-03-04 ### Added -- Add queue to update scheduled cron triggers on-demand by @juliusgeo in [#3149](https://github.com/hatchet-dev/hatchet/pull/3149) +- Add queue to update scheduled cron triggers on-demand by @juliusgeo in [#3149](https://github.com/hatchet-dev/hatchet/pull/3149) ## v0.79.15 - 2026-03-04 ### Added -- Dynamic worker label assign by @mrkaye97 in [#3137](https://github.com/hatchet-dev/hatchet/pull/3137) +- Dynamic worker label assign by @mrkaye97 in [#3137](https://github.com/hatchet-dev/hatchet/pull/3137) ## v0.79.14 - 2026-03-03 ### Added + - Add seconds granularity to cron jobs by @juliusgeo in [#3136](https://github.com/hatchet-dev/hatchet/pull/3136) ### Changed -- Enable loadtest with PgBouncer by @mnafees in [#3143](https://github.com/hatchet-dev/hatchet/pull/3143) +- Enable loadtest with PgBouncer by @mnafees in [#3143](https://github.com/hatchet-dev/hatchet/pull/3143) ## v0.79.13 - 2026-03-02 ### Added -- User callback additional methods by @abelanger5 in [#3057](https://github.com/hatchet-dev/hatchet/pull/3057) +- User callback additional methods by @abelanger5 in [#3057](https://github.com/hatchet-dev/hatchet/pull/3057) ## v0.79.12 - 2026-02-28 ### Fixed + - More small tenant switching + z index issues by @mrkaye97 in [#3124](https://github.com/hatchet-dev/hatchet/pull/3124) - Rm z index for action dialog by @mrkaye97 in [#3120](https://github.com/hatchet-dev/hatchet/pull/3120) - ## v0.79.11 - 2026-02-27 ### Added + - New "create organization" and "create tenant" interfaces by @TehShrike in [#3068](https://github.com/hatchet-dev/hatchet/pull/3068) ### Fixed -- Modals should appear above the mobile sidebar by @TehShrike in [#3114](https://github.com/hatchet-dev/hatchet/pull/3114) +- Modals should appear above the mobile sidebar by @TehShrike in [#3114](https://github.com/hatchet-dev/hatchet/pull/3114) ## v0.79.10 - 2026-02-26 ### Fixed -- External ids by @mrkaye97 in [#3111](https://github.com/hatchet-dev/hatchet/pull/3111) +- External ids by @mrkaye97 in [#3111](https://github.com/hatchet-dev/hatchet/pull/3111) ## v0.79.9 - 2026-02-26 ### Added + - Add `ctx.WasSkipped` helper to the Go SDK by @mnafees in [#3094](https://github.com/hatchet-dev/hatchet/pull/3094) - Add credit balance query and display in subscription component by @grutt in [#3107](https://github.com/hatchet-dev/hatchet/pull/3107) - ## v0.79.7 - 2026-02-25 ### Changed -- Non blocking ctx.Log with meaningful retries by @mnafees in [#3106](https://github.com/hatchet-dev/hatchet/pull/3106) +- Non blocking ctx.Log with meaningful retries by @mnafees in [#3106](https://github.com/hatchet-dev/hatchet/pull/3106) ## v0.79.6 - 2026-02-25 ### Changed -- [Go] Feat: Details Getter by @mrkaye97 in [#3105](https://github.com/hatchet-dev/hatchet/pull/3105) +- [Go] Feat: Details Getter by @mrkaye97 in [#3105](https://github.com/hatchet-dev/hatchet/pull/3105) ## v0.79.5 - 2026-02-24 ### Added -- Add env vars for max conn lifetime and idle time for pgx by @mnafees in [#3096](https://github.com/hatchet-dev/hatchet/pull/3096) +- Add env vars for max conn lifetime and idle time for pgx by @mnafees in [#3096](https://github.com/hatchet-dev/hatchet/pull/3096) ## v0.79.4 - 2026-02-24 ### Added + - Add missing primary key to `"WorkflowTriggerCronRef"` by @mnafees in [#3086](https://github.com/hatchet-dev/hatchet/pull/3086) ### Fixed -- Fix cross-strategy slot contamination in chained concurrency gates by @mnafees in [#3089](https://github.com/hatchet-dev/hatchet/pull/3089) +- Fix cross-strategy slot contamination in chained concurrency gates by @mnafees in [#3089](https://github.com/hatchet-dev/hatchet/pull/3089) ## v0.79.3 - 2026-02-23 ### Changed -- Make sure to use 60 seconds timeout for PutWorkflowVersion by @mnafees in [#3085](https://github.com/hatchet-dev/hatchet/pull/3085) +- Make sure to use 60 seconds timeout for PutWorkflowVersion by @mnafees in [#3085](https://github.com/hatchet-dev/hatchet/pull/3085) ## v0.79.2 - 2026-02-23 ### Fixed -- Move event log to a tab on the task run detail by @mrkaye97 in [#3067](https://github.com/hatchet-dev/hatchet/pull/3067) +- Move event log to a tab on the task run detail by @mrkaye97 in [#3067](https://github.com/hatchet-dev/hatchet/pull/3067) ## v0.79.1 - 2026-02-21 ### Added + - Add new agent instruction tools by @grutt in [#3059](https://github.com/hatchet-dev/hatchet/pull/3059) ### Fixed + - Expand long logs on click instead of on hover by @mrkaye97 in [#3069](https://github.com/hatchet-dev/hatchet/pull/3069) - Input in transformer by @mrkaye97 in [#3070](https://github.com/hatchet-dev/hatchet/pull/3070) - ## v0.79.0 - 2026-02-20 ### Added -- Runs commands by @abelanger5 in [#3058](https://github.com/hatchet-dev/hatchet/pull/3058) +- Runs commands by @abelanger5 in [#3058](https://github.com/hatchet-dev/hatchet/pull/3058) ## v0.78.30 - 2026-02-19 ### Added -- Add migration for worker slot config index by @grutt in [#3062](https://github.com/hatchet-dev/hatchet/pull/3062) +- Add migration for worker slot config index by @grutt in [#3062](https://github.com/hatchet-dev/hatchet/pull/3062) ## v0.78.27 - 2026-02-18 ### Added + - New search bar component by @abelanger5 in [#2909](https://github.com/hatchet-dev/hatchet/pull/2909) - Multiple slot types by @grutt in [#2927](https://github.com/hatchet-dev/hatchet/pull/2927) ### Fixed -- Fix--durable-slot-acquisition by @grutt in [#3048](https://github.com/hatchet-dev/hatchet/pull/3048) +- Fix--durable-slot-acquisition by @grutt in [#3048](https://github.com/hatchet-dev/hatchet/pull/3048) ## v0.78.26 - 2026-02-16 ### Added + - Feat--llm-readable-docs by @grutt in [#3030](https://github.com/hatchet-dev/hatchet/pull/3030) - Official Ruby SDK by @grutt in [#3004](https://github.com/hatchet-dev/hatchet/pull/3004) ### Changed -- Return event ID after successful webhook trigger by @mnafees in [#3039](https://github.com/hatchet-dev/hatchet/pull/3039) +- Return event ID after successful webhook trigger by @mnafees in [#3039](https://github.com/hatchet-dev/hatchet/pull/3039) ## v0.78.25 - 2026-02-13 ### Added + - Add python and typescript webhook client by @jishnundth in [#2959](https://github.com/hatchet-dev/hatchet/pull/2959) ### Changed + - [hotfix] Corrected custom value meter for resource limit by @mnafees in [#3021](https://github.com/hatchet-dev/hatchet/pull/3021) - New `UpdateLimits` method for `TenantResourceLimit` table by @mnafees in [#2895](https://github.com/hatchet-dev/hatchet/pull/2895) - ## v0.78.23 - 2026-02-13 ### Changed + - More deprecation messages for older Go SDKs by @mnafees in [#3006](https://github.com/hatchet-dev/hatchet/pull/3006) ### Fixed + - DAG height on TUI by @abelanger5 in [#3019](https://github.com/hatchet-dev/hatchet/pull/3019) - Remove null bytes from error message to prevent db crash by @mrkaye97 in [#3010](https://github.com/hatchet-dev/hatchet/pull/3010) - ## v0.78.22 - 2026-02-11 ### Changed -- Truncate to first 10k characters of log line in Go SDK by @mnafees in [#2998](https://github.com/hatchet-dev/hatchet/pull/2998) +- Truncate to first 10k characters of log line in Go SDK by @mnafees in [#2998](https://github.com/hatchet-dev/hatchet/pull/2998) ## v0.78.21 - 2026-02-11 ### Added -- Reduced cold starts for new workers and queues by @abelanger5 in [#2969](https://github.com/hatchet-dev/hatchet/pull/2969) +- Reduced cold starts for new workers and queues by @abelanger5 in [#2969](https://github.com/hatchet-dev/hatchet/pull/2969) ## v0.78.20 - 2026-02-11 ### Added -- Add support for Svix webhooks by @mnafees in [#2996](https://github.com/hatchet-dev/hatchet/pull/2996) +- Add support for Svix webhooks by @mnafees in [#2996](https://github.com/hatchet-dev/hatchet/pull/2996) ## v0.78.19 - 2026-02-11 ### Added -- Log on delayed heartbeat by @grutt in [#2994](https://github.com/hatchet-dev/hatchet/pull/2994) +- Log on delayed heartbeat by @grutt in [#2994](https://github.com/hatchet-dev/hatchet/pull/2994) ## v0.78.18 - 2026-02-10 ### Changed -- Do not replay invalid tasks by @mnafees in [#2976](https://github.com/hatchet-dev/hatchet/pull/2976) +- Do not replay invalid tasks by @mnafees in [#2976](https://github.com/hatchet-dev/hatchet/pull/2976) ## v0.78.16 - 2026-02-10 ### Changed + - [hotfix] Fix Docker frontend build issue by @mnafees in [#2983](https://github.com/hatchet-dev/hatchet/pull/2983) - [hotfix] Fix `BillingRequired` component by @mnafees in [#2982](https://github.com/hatchet-dev/hatchet/pull/2982) - Tasks marked as skipped in the UI by @mnafees in [#2978](https://github.com/hatchet-dev/hatchet/pull/2978) - Mark old v0 and generics-based v1 Go SDK methods as deprecated by @mnafees in [#2962](https://github.com/hatchet-dev/hatchet/pull/2962) - ## v0.78.12 - 2026-02-05 ### Fixed + - Check `uuid.Nil` when creating or groups too by @mrkaye97 in [#2958](https://github.com/hatchet-dev/hatchet/pull/2958) - Wrapped types in the Go SDK by @abelanger5 in [#2957](https://github.com/hatchet-dev/hatchet/pull/2957) - ## v0.78.11 - 2026-02-05 ### Fixed -- Always generate UUID for OrGroup by @mrkaye97 in [#2955](https://github.com/hatchet-dev/hatchet/pull/2955) +- Always generate UUID for OrGroup by @mrkaye97 in [#2955](https://github.com/hatchet-dev/hatchet/pull/2955) ## v0.78.10 - 2026-02-05 ### Fixed -- More panics by @mrkaye97 in [#2945](https://github.com/hatchet-dev/hatchet/pull/2945) +- More panics by @mrkaye97 in [#2945](https://github.com/hatchet-dev/hatchet/pull/2945) ## v0.78.9 - 2026-02-04 ### Fixed -- Make uuid optional for desired worker by @mrkaye97 in [#2946](https://github.com/hatchet-dev/hatchet/pull/2946) +- Make uuid optional for desired worker by @mrkaye97 in [#2946](https://github.com/hatchet-dev/hatchet/pull/2946) ## v0.78.8 - 2026-02-04 ### Changed + - Go SDK gRPC client reconnection improvements by @mnafees in [#2934](https://github.com/hatchet-dev/hatchet/pull/2934) ### Fixed -- UUID Panics by @mrkaye97 in [#2944](https://github.com/hatchet-dev/hatchet/pull/2944) +- UUID Panics by @mrkaye97 in [#2944](https://github.com/hatchet-dev/hatchet/pull/2944) ## v0.78.7 - 2026-02-04 ### Added -- Extend webhook support for scope_expression and payload by @jishnundth in [#2874](https://github.com/hatchet-dev/hatchet/pull/2874) +- Extend webhook support for scope_expression and payload by @jishnundth in [#2874](https://github.com/hatchet-dev/hatchet/pull/2874) ## v0.78.6 - 2026-02-04 ### Fixed -- Don't cast user id in session.Values by @mrkaye97 in [#2937](https://github.com/hatchet-dev/hatchet/pull/2937) +- Don't cast user id in session.Values by @mrkaye97 in [#2937](https://github.com/hatchet-dev/hatchet/pull/2937) ## v0.78.5 - 2026-02-04 ### Fixed -- Resource ID type by @mrkaye97 in [#2929](https://github.com/hatchet-dev/hatchet/pull/2929) +- Resource ID type by @mrkaye97 in [#2929](https://github.com/hatchet-dev/hatchet/pull/2929) ## v0.78.4 - 2026-02-04 ### Changed -- Use typed maps by @abelanger5 in [#2928](https://github.com/hatchet-dev/hatchet/pull/2928) +- Use typed maps by @abelanger5 in [#2928](https://github.com/hatchet-dev/hatchet/pull/2928) ## v0.78.3 - 2026-02-03 ### Fixed -- Explicit use of tx in olap readPayloads by @abelanger5 in [#2925](https://github.com/hatchet-dev/hatchet/pull/2925) +- Explicit use of tx in olap readPayloads by @abelanger5 in [#2925](https://github.com/hatchet-dev/hatchet/pull/2925) ## v0.78.2 - 2026-02-03 ### Fixed -- Startup for optimistic scheduler by @abelanger5 in [#2924](https://github.com/hatchet-dev/hatchet/pull/2924) +- Startup for optimistic scheduler by @abelanger5 in [#2924](https://github.com/hatchet-dev/hatchet/pull/2924) ## v0.78.0 - 2026-02-03 ### Added + - Durable user event log by @abelanger5 in [#2861](https://github.com/hatchet-dev/hatchet/pull/2861) - Email alert support via SMTP by @gregfurman in [#2868](https://github.com/hatchet-dev/hatchet/pull/2868) ### Changed + - Attempt II at removing `pgtype.UUID` everywhere + convert string UUIDs into `uuid.UUID` by @mrkaye97 in [#2894](https://github.com/hatchet-dev/hatchet/pull/2894) - Management tokens that never expire by @mnafees in [#2889](https://github.com/hatchet-dev/hatchet/pull/2889) ### Fixed + - Add back SendTemplateEmail methods, fix postmark emails by @abelanger5 in [#2910](https://github.com/hatchet-dev/hatchet/pull/2910) - Dag update distinct locks by @grutt in [#2903](https://github.com/hatchet-dev/hatchet/pull/2903) - Only fetch finalized workflow runs by @grutt in [#2896](https://github.com/hatchet-dev/hatchet/pull/2896) - Compute payload size correctly for pg_notify by @abelanger5 in [#2873](https://github.com/hatchet-dev/hatchet/pull/2873) - Orphaned inactive queues by @grutt in [#2893](https://github.com/hatchet-dev/hatchet/pull/2893) - ## v0.77.37 - 2026-01-30 ### Changed -- Log Search Frontend, Part II by @mrkaye97 in [#2886](https://github.com/hatchet-dev/hatchet/pull/2886) +- Log Search Frontend, Part II by @mrkaye97 in [#2886](https://github.com/hatchet-dev/hatchet/pull/2886) ## v0.77.36 - 2026-01-29 ### Changed -- Make sure we query for pending invites in the create tenant page by @mnafees in [#2883](https://github.com/hatchet-dev/hatchet/pull/2883) +- Make sure we query for pending invites in the create tenant page by @mnafees in [#2883](https://github.com/hatchet-dev/hatchet/pull/2883) ## v0.77.35 - 2026-01-29 ### Added -- Log Search Frontend, Part I by @mrkaye97 in [#2830](https://github.com/hatchet-dev/hatchet/pull/2830) +- Log Search Frontend, Part I by @mrkaye97 in [#2830](https://github.com/hatchet-dev/hatchet/pull/2830) ## v0.77.34 - 2026-01-29 ### Fixed -- Tenant resource limit resource col migration by @grutt in [#2885](https://github.com/hatchet-dev/hatchet/pull/2885) +- Tenant resource limit resource col migration by @grutt in [#2885](https://github.com/hatchet-dev/hatchet/pull/2885) ## v0.77.33 - 2026-01-29 ### Added + - Workflow input JSON schema in trigger preview by @mrkaye97 in [#2851](https://github.com/hatchet-dev/hatchet/pull/2851) ### Fixed + - Validate json at edges and dont retry on invalid by @grutt in [#2882](https://github.com/hatchet-dev/hatchet/pull/2882) - Typo by @grutt in [#2875](https://github.com/hatchet-dev/hatchet/pull/2875) - Big int alignment for cleanup function by @grutt in [#2877](https://github.com/hatchet-dev/hatchet/pull/2877) - ## v0.77.32 - 2026-01-28 ### Added -- OTel Collector by @mrkaye97 in [#2863](https://github.com/hatchet-dev/hatchet/pull/2863) +- OTel Collector by @mrkaye97 in [#2863](https://github.com/hatchet-dev/hatchet/pull/2863) ## v0.77.30 - 2026-01-27 ### Fixed -- Make metrics in graph align with badge metrics by @mrkaye97 in [#2858](https://github.com/hatchet-dev/hatchet/pull/2858) +- Make metrics in graph align with badge metrics by @mrkaye97 in [#2858](https://github.com/hatchet-dev/hatchet/pull/2858) ## v0.77.25 - 2026-01-26 ### Removed -- Revert "fix: make point metrics line up with badges " by @mrkaye97 in [#2857](https://github.com/hatchet-dev/hatchet/pull/2857) +- Revert "fix: make point metrics line up with badges " by @mrkaye97 in [#2857](https://github.com/hatchet-dev/hatchet/pull/2857) ## v0.77.24 - 2026-01-26 ### Fixed + - Fix with coalesce by @mnafees in [#2856](https://github.com/hatchet-dev/hatchet/pull/2856) - Make point metrics line up with badges by @mrkaye97 in [#2739](https://github.com/hatchet-dev/hatchet/pull/2739) - ## v0.77.23 - 2026-01-26 ### Added + - Add order by direction param to v1LogLineList by @mrkaye97 in [#2849](https://github.com/hatchet-dev/hatchet/pull/2849) ### Changed -- More tenant related repo methods by @mnafees in [#2854](https://github.com/hatchet-dev/hatchet/pull/2854) +- More tenant related repo methods by @mnafees in [#2854](https://github.com/hatchet-dev/hatchet/pull/2854) ## v0.77.21 - 2026-01-23 ### Changed -- [Go] Feat: Webhooks feature client for the Go SDK by @mrkaye97 in [#2792](https://github.com/hatchet-dev/hatchet/pull/2792) +- [Go] Feat: Webhooks feature client for the Go SDK by @mrkaye97 in [#2792](https://github.com/hatchet-dev/hatchet/pull/2792) ## v0.77.16 - 2026-01-21 ### Fixed -- List concurrency strategies queries by @grutt in [#2838](https://github.com/hatchet-dev/hatchet/pull/2838) +- List concurrency strategies queries by @grutt in [#2838](https://github.com/hatchet-dev/hatchet/pull/2838) ## v0.77.13 - 2026-01-21 ### Added + - Add search and levels to logs API by @mrkaye97 in [#2835](https://github.com/hatchet-dev/hatchet/pull/2835) ### Changed + - Take exclusive lock by @mnafees in [#2837](https://github.com/hatchet-dev/hatchet/pull/2837) - Updates by @grutt in [#2827](https://github.com/hatchet-dev/hatchet/pull/2827) ### Fixed -- Flaky integration test by @grutt in [#2834](https://github.com/hatchet-dev/hatchet/pull/2834) +- Flaky integration test by @grutt in [#2834](https://github.com/hatchet-dev/hatchet/pull/2834) ## v0.77.9 - 2026-01-21 ### Changed -- [hotfix] Indicate unauthorized access to billing page for non-owners by @mnafees in [#2833](https://github.com/hatchet-dev/hatchet/pull/2833) +- [hotfix] Indicate unauthorized access to billing page for non-owners by @mnafees in [#2833](https://github.com/hatchet-dev/hatchet/pull/2833) ## v0.77.5 - 2026-01-20 ### Changed -- Have log line lookups use external id by @abelanger5 in [#2822](https://github.com/hatchet-dev/hatchet/pull/2822) +- Have log line lookups use external id by @abelanger5 in [#2822](https://github.com/hatchet-dev/hatchet/pull/2822) ## v0.77.1 - 2026-01-19 ### Fixed -- Fix naming of migration by @mnafees in [#2819](https://github.com/hatchet-dev/hatchet/pull/2819) +- Fix naming of migration by @mnafees in [#2819](https://github.com/hatchet-dev/hatchet/pull/2819) ## v0.77.0 - 2026-01-19 ### Changed -- Billing changes by @mnafees in [#2643](https://github.com/hatchet-dev/hatchet/pull/2643) +- Billing changes by @mnafees in [#2643](https://github.com/hatchet-dev/hatchet/pull/2643) ## v0.75.4 - 2026-01-18 ### Changed + - Shrink task metrics chart a bunch by @mrkaye97 in [#2738](https://github.com/hatchet-dev/hatchet/pull/2738) ### Fixed -- Typescript post-quickstart by @abelanger5 in [#2809](https://github.com/hatchet-dev/hatchet/pull/2809) +- Typescript post-quickstart by @abelanger5 in [#2809](https://github.com/hatchet-dev/hatchet/pull/2809) ## v0.75.3 - 2026-01-16 ### Fixed -- Minor quickstart issues by @abelanger5 in [#2807](https://github.com/hatchet-dev/hatchet/pull/2807) +- Minor quickstart issues by @abelanger5 in [#2807](https://github.com/hatchet-dev/hatchet/pull/2807) ## v0.75.2 - 2026-01-16 ### Added + - Update quickstarts with package manager support, e2e tests for quickstarts by @abelanger5 in [#2801](https://github.com/hatchet-dev/hatchet/pull/2801) ### Changed + - New onboarding flow by @mnafees in [#2757](https://github.com/hatchet-dev/hatchet/pull/2757) - Auth front-end changes by @sebastiangraz in [#2802](https://github.com/hatchet-dev/hatchet/pull/2802) - ## v0.75.1 - 2026-01-15 ### Changed -- Small fixes/improvements to CLI logic by @abelanger5 in [#2793](https://github.com/hatchet-dev/hatchet/pull/2793) +- Small fixes/improvements to CLI logic by @abelanger5 in [#2793](https://github.com/hatchet-dev/hatchet/pull/2793) ## v0.75.0 - 2026-01-13 ### Added -- Hatchet cli by @abelanger5 in [#2701](https://github.com/hatchet-dev/hatchet/pull/2701) +- Hatchet cli by @abelanger5 in [#2701](https://github.com/hatchet-dev/hatchet/pull/2701) ## v0.74.14 - 2026-01-12 ### Fixed -- Concurrency display on workflow page by @mrkaye97 in [#2780](https://github.com/hatchet-dev/hatchet/pull/2780) +- Concurrency display on workflow page by @mrkaye97 in [#2780](https://github.com/hatchet-dev/hatchet/pull/2780) ## v0.74.13 - 2026-01-12 ### Added + - Add additional meta to the run detail getter by @mrkaye97 in [#2770](https://github.com/hatchet-dev/hatchet/pull/2770) ### Fixed -- Regression on v0 PutWorkflow for scheduling timeout by @abelanger5 in [#2779](https://github.com/hatchet-dev/hatchet/pull/2779) +- Regression on v0 PutWorkflow for scheduling timeout by @abelanger5 in [#2779](https://github.com/hatchet-dev/hatchet/pull/2779) ## v0.74.12 - 2026-01-11 ### Fixed + - Statement timeout by @mrkaye97 in [#2774](https://github.com/hatchet-dev/hatchet/pull/2774) - Actually reconnect to postgres if conn fails. by @m-kostrzewa in [#2772](https://github.com/hatchet-dev/hatchet/pull/2772) - ## v0.74.11 - 2026-01-10 ### Added + - Run detail getter on the engine by @mrkaye97 in [#2725](https://github.com/hatchet-dev/hatchet/pull/2725) ### Fixed -- Worker id by @mrkaye97 in [#2773](https://github.com/hatchet-dev/hatchet/pull/2773) +- Worker id by @mrkaye97 in [#2773](https://github.com/hatchet-dev/hatchet/pull/2773) ## v0.74.8 - 2026-01-08 ### Fixed + - Better error on deprecated endpoints by @abelanger5 in [#2763](https://github.com/hatchet-dev/hatchet/pull/2763) - Chunk and recursively retry too-large message sends by @mrkaye97 in [#2761](https://github.com/hatchet-dev/hatchet/pull/2761) - ## v0.74.7 - 2026-01-07 ### Fixed -- Un-hard-code location by @mrkaye97 in [#2760](https://github.com/hatchet-dev/hatchet/pull/2760) +- Un-hard-code location by @mrkaye97 in [#2760](https://github.com/hatchet-dev/hatchet/pull/2760) ## v0.74.6 - 2026-01-07 ### Fixed + - Payload location issue by @mrkaye97 in [#2759](https://github.com/hatchet-dev/hatchet/pull/2759) - Child runs counts missing filter by @mrkaye97 in [#2744](https://github.com/hatchet-dev/hatchet/pull/2744) - ## v0.74.5 - 2026-01-06 ### Changed + - Send `create:user` Event from OAuth Flow by @undrash in [#2683](https://github.com/hatchet-dev/hatchet/pull/2683) ### Removed -- UI version removal by @abelanger5 in [#2756](https://github.com/hatchet-dev/hatchet/pull/2756) +- UI version removal by @abelanger5 in [#2756](https://github.com/hatchet-dev/hatchet/pull/2756) ## v0.74.4 - 2026-01-06 ### Changed -- Use try advisory for replat tasks by @mnafees in [#2755](https://github.com/hatchet-dev/hatchet/pull/2755) +- Use try advisory for replat tasks by @mnafees in [#2755](https://github.com/hatchet-dev/hatchet/pull/2755) ## v0.74.3 - 2026-01-06 ### Changed + - Set a connection-level statement timeout by @abelanger5 in [#2750](https://github.com/hatchet-dev/hatchet/pull/2750) - Move v1 packages, remove webhook worker references by @abelanger5 in [#2749](https://github.com/hatchet-dev/hatchet/pull/2749) ### Fixed -- More frontend work - table cleanup, worker detail page improvements, etc. by @mrkaye97 in [#2746](https://github.com/hatchet-dev/hatchet/pull/2746) +- More frontend work - table cleanup, worker detail page improvements, etc. by @mrkaye97 in [#2746](https://github.com/hatchet-dev/hatchet/pull/2746) ## v0.74.2 - 2025-12-31 ### Changed -- Consolidate repository methods by @abelanger5 in [#2730](https://github.com/hatchet-dev/hatchet/pull/2730) +- Consolidate repository methods by @abelanger5 in [#2730](https://github.com/hatchet-dev/hatchet/pull/2730) ## v0.74.1 - 2025-12-31 ### Changed + - Remove v0-exclusive database queries by @abelanger5 in [#2729](https://github.com/hatchet-dev/hatchet/pull/2729) - Remove v0 paths from codebase by @abelanger5 in [#2728](https://github.com/hatchet-dev/hatchet/pull/2728) ### Fixed + - Handle panic by @mrkaye97 in [#2732](https://github.com/hatchet-dev/hatchet/pull/2732) - Frozen refetch state gets stuck by @mrkaye97 in [#2736](https://github.com/hatchet-dev/hatchet/pull/2736) - Remove action dropdown, fix a couple broken tooltips by @mrkaye97 in [#2737](https://github.com/hatchet-dev/hatchet/pull/2737) - ## v0.73.110 - 2025-12-26 ### Fixed -- Layout by @mrkaye97 in [#2724](https://github.com/hatchet-dev/hatchet/pull/2724) +- Layout by @mrkaye97 in [#2724](https://github.com/hatchet-dev/hatchet/pull/2724) ## v0.73.109 - 2025-12-26 ### Fixed + - Tenant invite accept flow by @mrkaye97 in [#2723](https://github.com/hatchet-dev/hatchet/pull/2723) - Goroutine to periodically extend lease during reconciliation by @mrkaye97 in [#2722](https://github.com/hatchet-dev/hatchet/pull/2722) - ## v0.73.108 - 2025-12-26 ### Fixed -- Rename migration by @mrkaye97 in [#2721](https://github.com/hatchet-dev/hatchet/pull/2721) +- Rename migration by @mrkaye97 in [#2721](https://github.com/hatchet-dev/hatchet/pull/2721) ## v0.73.107 - 2025-12-26 ### Changed + - Publish `COULD_NOT_SEND_TO_WORKER` OLAP event due to worker backlog by @mnafees in [#2710](https://github.com/hatchet-dev/hatchet/pull/2710) - Reuse timers for delayed semaphore release in MQ buffers by @mnafees in [#2691](https://github.com/hatchet-dev/hatchet/pull/2691) - Msgqueue msg IDs as constants for ease of navigation and readability by @mnafees in [#2692](https://github.com/hatchet-dev/hatchet/pull/2692) ### Removed + - Revert "Revert "chore: run list query optimizations " " by @mrkaye97 in [#2720](https://github.com/hatchet-dev/hatchet/pull/2720) ### Fixed + - Static rate limits resetting to zero by @mrkaye97 in [#2714](https://github.com/hatchet-dev/hatchet/pull/2714) - Minor fe bugs and nits by @grutt in [#2711](https://github.com/hatchet-dev/hatchet/pull/2711) - ## v0.73.106 - 2025-12-23 ### Added + - Improved navigation by @grutt in [#2704](https://github.com/hatchet-dev/hatchet/pull/2704) - Hatchet Metrics Monitoring, I by @mrkaye97 in [#2699](https://github.com/hatchet-dev/hatchet/pull/2699) ### Removed + - Revert "chore: run list query optimizations " by @mrkaye97 in [#2708](https://github.com/hatchet-dev/hatchet/pull/2708) ### Fixed + - Rm cleanup logic for now by @mrkaye97 in [#2707](https://github.com/hatchet-dev/hatchet/pull/2707) - WithToken should override environment variable by @abelanger5 in [#2706](https://github.com/hatchet-dev/hatchet/pull/2706) - Fix move fixture by @grutt in [#2705](https://github.com/hatchet-dev/hatchet/pull/2705) - ## v0.73.104 - 2025-12-23 ### Added + - Improved error boundaries by @grutt in [#2689](https://github.com/hatchet-dev/hatchet/pull/2689) - Bulk management schedules by @grutt in [#2687](https://github.com/hatchet-dev/hatchet/pull/2687) ### Fixed -- Fix layout bugs by @grutt in [#2703](https://github.com/hatchet-dev/hatchet/pull/2703) -- Dead alert links by @mrkaye97 in [#2688](https://github.com/hatchet-dev/hatchet/pull/2688) +- Fix layout bugs by @grutt in [#2703](https://github.com/hatchet-dev/hatchet/pull/2703) +- Dead alert links by @mrkaye97 in [#2688](https://github.com/hatchet-dev/hatchet/pull/2688) ## v0.73.103 - 2025-12-22 ### Fixed -- Dynamically-sized chunks on payload read by @mrkaye97 in [#2700](https://github.com/hatchet-dev/hatchet/pull/2700) +- Dynamically-sized chunks on payload read by @mrkaye97 in [#2700](https://github.com/hatchet-dev/hatchet/pull/2700) ## v0.73.102 - 2025-12-22 ### Removed + - Revert "Feat: Hatchet Metrics Monitoring, I " by @mrkaye97 in [#2698](https://github.com/hatchet-dev/hatchet/pull/2698) ### Fixed -- Table name by @mrkaye97 in [#2697](https://github.com/hatchet-dev/hatchet/pull/2697) +- Table name by @mrkaye97 in [#2697](https://github.com/hatchet-dev/hatchet/pull/2697) ## v0.73.101 - 2025-12-22 ### Added -- Hatchet Metrics Monitoring, I by @mrkaye97 in [#2480](https://github.com/hatchet-dev/hatchet/pull/2480) +- Hatchet Metrics Monitoring, I by @mrkaye97 in [#2480](https://github.com/hatchet-dev/hatchet/pull/2480) ## v0.73.99 - 2025-12-22 ### Fixed + - Last bits of payload job cleanup by @mrkaye97 in [#2690](https://github.com/hatchet-dev/hatchet/pull/2690) - Rare cases of duplicate writes causing stuck updates by @abelanger5 in [#2681](https://github.com/hatchet-dev/hatchet/pull/2681) - Filter + pagination state handling hack by @mrkaye97 in [#2682](https://github.com/hatchet-dev/hatchet/pull/2682) - ## v0.73.98 - 2025-12-17 ### Added + - New event getter + janky v0 fix by @mrkaye97 in [#2667](https://github.com/hatchet-dev/hatchet/pull/2667) ### Changed -- Remove inline button styles where possible by @mrkaye97 in [#2671](https://github.com/hatchet-dev/hatchet/pull/2671) +- Remove inline button styles where possible by @mrkaye97 in [#2671](https://github.com/hatchet-dev/hatchet/pull/2671) ## v0.73.97 - 2025-12-16 ### Fixed + - Payload List Index Performance by @mrkaye97 in [#2669](https://github.com/hatchet-dev/hatchet/pull/2669) - Root redirect with last tenant atom by @mrkaye97 in [#2664](https://github.com/hatchet-dev/hatchet/pull/2664) - ## v0.73.96 - 2025-12-15 ### Fixed -- Pagination by bounds by @mrkaye97 in [#2654](https://github.com/hatchet-dev/hatchet/pull/2654) +- Pagination by bounds by @mrkaye97 in [#2654](https://github.com/hatchet-dev/hatchet/pull/2654) ## v0.73.94 - 2025-12-11 ### Changed -- Write to S3 outside of goroutine by @mrkaye97 in [#2646](https://github.com/hatchet-dev/hatchet/pull/2646) +- Write to S3 outside of goroutine by @mrkaye97 in [#2646](https://github.com/hatchet-dev/hatchet/pull/2646) ## v0.73.93 - 2025-12-11 ### Fixed -- OLAP Immediate Offloads by @mrkaye97 in [#2644](https://github.com/hatchet-dev/hatchet/pull/2644) +- OLAP Immediate Offloads by @mrkaye97 in [#2644](https://github.com/hatchet-dev/hatchet/pull/2644) ## v0.73.92 - 2025-12-11 ### Added -- Add oldest queued + running jobs to task stats by @mrkaye97 in [#2638](https://github.com/hatchet-dev/hatchet/pull/2638) +- Add oldest queued + running jobs to task stats by @mrkaye97 in [#2638](https://github.com/hatchet-dev/hatchet/pull/2638) ## v0.73.91 - 2025-12-10 ### Added -- Parallelize replication from PG -> External by @mrkaye97 in [#2637](https://github.com/hatchet-dev/hatchet/pull/2637) +- Parallelize replication from PG -> External by @mrkaye97 in [#2637](https://github.com/hatchet-dev/hatchet/pull/2637) ## v0.73.90 - 2025-12-10 ### Fixed -- Global Lease for OLAP by @mrkaye97 in [#2635](https://github.com/hatchet-dev/hatchet/pull/2635) +- Global Lease for OLAP by @mrkaye97 in [#2635](https://github.com/hatchet-dev/hatchet/pull/2635) ## v0.73.89 - 2025-12-10 ### Fixed -- Query logic bug by @mrkaye97 in [#2631](https://github.com/hatchet-dev/hatchet/pull/2631) +- Query logic bug by @mrkaye97 in [#2631](https://github.com/hatchet-dev/hatchet/pull/2631) ## v0.73.88 - 2025-12-10 ### Added + - Add support for slack slash commands in webhook by @sidpremkumar in [#2630](https://github.com/hatchet-dev/hatchet/pull/2630) ### Fixed -- Don't reset offset if a new process acquires lease by @mrkaye97 in [#2628](https://github.com/hatchet-dev/hatchet/pull/2628) +- Don't reset offset if a new process acquires lease by @mrkaye97 in [#2628](https://github.com/hatchet-dev/hatchet/pull/2628) ## v0.73.87 - 2025-12-09 ### Added -- OLAP Payload Cutover Job by @mrkaye97 in [#2618](https://github.com/hatchet-dev/hatchet/pull/2618) +- OLAP Payload Cutover Job by @mrkaye97 in [#2618](https://github.com/hatchet-dev/hatchet/pull/2618) ## v0.73.86 - 2025-12-08 ### Changed -- Simplify external store signature by @mrkaye97 in [#2616](https://github.com/hatchet-dev/hatchet/pull/2616) +- Simplify external store signature by @mrkaye97 in [#2616](https://github.com/hatchet-dev/hatchet/pull/2616) ## v0.73.85 - 2025-12-08 ### Changed + - Update Expression Page + Slack Webhook Onboarding by @sidpremkumar in [#2614](https://github.com/hatchet-dev/hatchet/pull/2614) ### Fixed -- Fix slack challenge + interactive webhook by @sidpremkumar in [#2612](https://github.com/hatchet-dev/hatchet/pull/2612) +- Fix slack challenge + interactive webhook by @sidpremkumar in [#2612](https://github.com/hatchet-dev/hatchet/pull/2612) ## v0.73.84 - 2025-12-08 ### Added -- Process all old partitions in a loop by @mrkaye97 in [#2613](https://github.com/hatchet-dev/hatchet/pull/2613) +- Process all old partitions in a loop by @mrkaye97 in [#2613](https://github.com/hatchet-dev/hatchet/pull/2613) ## v0.73.83 - 2025-12-08 ### Added -- Add guide for downgrading versions by @mnafees in [#2588](https://github.com/hatchet-dev/hatchet/pull/2588) +- Add guide for downgrading versions by @mnafees in [#2588](https://github.com/hatchet-dev/hatchet/pull/2588) ## v0.73.82 - 2025-12-05 ### Fixed -- Add validation by @mrkaye97 in [#2610](https://github.com/hatchet-dev/hatchet/pull/2610) +- Add validation by @mrkaye97 in [#2610](https://github.com/hatchet-dev/hatchet/pull/2610) ## v0.73.81 - 2025-12-05 ### Fixed -- Leasing for payload job by @mrkaye97 in [#2609](https://github.com/hatchet-dev/hatchet/pull/2609) +- Leasing for payload job by @mrkaye97 in [#2609](https://github.com/hatchet-dev/hatchet/pull/2609) ## v0.73.80 - 2025-12-05 ### Added + - Job for payload cutovers to external by @mrkaye97 in [#2586](https://github.com/hatchet-dev/hatchet/pull/2586) - Dlq for dispatcher queues by @abelanger5 in [#2600](https://github.com/hatchet-dev/hatchet/pull/2600) ### Changed + - Initialize concurrency keys slice for replayed tasks by @mnafees in [#2549](https://github.com/hatchet-dev/hatchet/pull/2549) ### Fixed -- Fix double toast on sidebar by @sidpremkumar in [#2607](https://github.com/hatchet-dev/hatchet/pull/2607) +- Fix double toast on sidebar by @sidpremkumar in [#2607](https://github.com/hatchet-dev/hatchet/pull/2607) ## v0.73.78 - 2025-12-03 ### Changed + - Cross-Domain Tracking and Analytics Refactoring by @undrash in [#2587](https://github.com/hatchet-dev/hatchet/pull/2587) ### Fixed + - Prevent large worker gRPC stream backlogs by @abelanger5 in [#2597](https://github.com/hatchet-dev/hatchet/pull/2597) - Don't trigger posthog Pageview on query param changes by @undrash in [#2598](https://github.com/hatchet-dev/hatchet/pull/2598) - Load shed on slow worker backlogs by @abelanger5 in [#2595](https://github.com/hatchet-dev/hatchet/pull/2595) - ## v0.73.76 - 2025-12-02 ### Fixed -- Move check for large payloads to after json.Marshal by @abelanger5 in [#2594](https://github.com/hatchet-dev/hatchet/pull/2594) +- Move check for large payloads to after json.Marshal by @abelanger5 in [#2594](https://github.com/hatchet-dev/hatchet/pull/2594) ## v0.73.75 - 2025-12-02 ### Fixed + - Ensure that slow worker doesn't interrupt dispatcher, guard large RabbitMQ pubs by @abelanger5 in [#2591](https://github.com/hatchet-dev/hatchet/pull/2591) - GetLatestWorkflowVersionForWorkflows by @grutt in [#2590](https://github.com/hatchet-dev/hatchet/pull/2590) - ## v0.73.74 - 2025-11-28 ### Added -- Add sent to worker event in the dispatcher by @mrkaye97 in [#2584](https://github.com/hatchet-dev/hatchet/pull/2584) +- Add sent to worker event in the dispatcher by @mrkaye97 in [#2584](https://github.com/hatchet-dev/hatchet/pull/2584) ## v0.73.73 - 2025-11-26 ### Added + - Add gzip compression by @sidpremkumar in [#2539](https://github.com/hatchet-dev/hatchet/pull/2539) ### Fixed -- Noisy Payload Error by @mrkaye97 in [#2561](https://github.com/hatchet-dev/hatchet/pull/2561) +- Noisy Payload Error by @mrkaye97 in [#2561](https://github.com/hatchet-dev/hatchet/pull/2561) ## v0.73.72 - 2025-11-26 ### Added + - Add tooltip showing full step name on hover by @mrkaye97 in [#2563](https://github.com/hatchet-dev/hatchet/pull/2563) ### Changed + - Analyze v1 lookup table by @grutt in [#2568](https://github.com/hatchet-dev/hatchet/pull/2568) - Optimize UUID sqlchelpers by @mnafees in [#2532](https://github.com/hatchet-dev/hatchet/pull/2532) - Add spans to worker list handler by @grutt in [#2554](https://github.com/hatchet-dev/hatchet/pull/2554) ### Removed + - Revert "optimize UUID sqlchelpers " by @mrkaye97 in [#2571](https://github.com/hatchet-dev/hatchet/pull/2571) ### Fixed + - Query optimization get latest workflow version by @grutt in [#2576](https://github.com/hatchet-dev/hatchet/pull/2576) - OLAP Task Event Dual Write Bug by @mrkaye97 in [#2572](https://github.com/hatchet-dev/hatchet/pull/2572) - Add whitespace-pre to log by @H01001000 in [#2555](https://github.com/hatchet-dev/hatchet/pull/2555) - ## v0.73.71 - 2025-11-21 ### Changed -- Common pgxpool afterconnect method by @mnafees in [#2553](https://github.com/hatchet-dev/hatchet/pull/2553) +- Common pgxpool afterconnect method by @mnafees in [#2553](https://github.com/hatchet-dev/hatchet/pull/2553) ## v0.73.70 - 2025-11-20 ### Changed -- [Go SDK] Resubscribe and get a new listener stream when gRPC connections fail by @mnafees in [#2544](https://github.com/hatchet-dev/hatchet/pull/2544) +- [Go SDK] Resubscribe and get a new listener stream when gRPC connections fail by @mnafees in [#2544](https://github.com/hatchet-dev/hatchet/pull/2544) ## v0.73.68 - 2025-11-18 ### Fixed -- Use sessionStorage instead of localStorage by @mrkaye97 in [#2541](https://github.com/hatchet-dev/hatchet/pull/2541) +- Use sessionStorage instead of localStorage by @mrkaye97 in [#2541](https://github.com/hatchet-dev/hatchet/pull/2541) ## v0.73.67 - 2025-11-18 ### Added + - Initial cross-domain identify setup by @mrkaye97 in [#2533](https://github.com/hatchet-dev/hatchet/pull/2533) ### Fixed -- Small scheduler optimizations by @abelanger5 in [#2426](https://github.com/hatchet-dev/hatchet/pull/2426) +- Small scheduler optimizations by @abelanger5 in [#2426](https://github.com/hatchet-dev/hatchet/pull/2426) ## v0.73.66 - 2025-11-17 ### Added + - REST API Instrumentation by @mrkaye97 in [#2529](https://github.com/hatchet-dev/hatchet/pull/2529) ### Fixed -- Revert n+1 queries on the list API by @mrkaye97 in [#2531](https://github.com/hatchet-dev/hatchet/pull/2531) +- Revert n+1 queries on the list API by @mrkaye97 in [#2531](https://github.com/hatchet-dev/hatchet/pull/2531) ## v0.73.65 - 2025-11-14 ### Changed + - Case on conflict for v1_statuses_olap entry by @mnafees in [#2528](https://github.com/hatchet-dev/hatchet/pull/2528) - Attempt to fix pgx multi dimensional slice reflection error #1 by @mnafees in [#2523](https://github.com/hatchet-dev/hatchet/pull/2523) - Archive tenant modal flow by @mnafees in [#2509](https://github.com/hatchet-dev/hatchet/pull/2509) ### Fixed + - Fix seq scan in `PollCronSchedules` query by @mnafees in [#2524](https://github.com/hatchet-dev/hatchet/pull/2524) - Log dupes by @mrkaye97 in [#2526](https://github.com/hatchet-dev/hatchet/pull/2526) - Fix nil error in `handleTaskBulkAssignedTask` by @mnafees in [#2427](https://github.com/hatchet-dev/hatchet/pull/2427) - ## v0.73.64 - 2025-11-12 ### Changed -- [Go SDK] Case on worker labels for durable tasks by @mnafees in [#2511](https://github.com/hatchet-dev/hatchet/pull/2511) +- [Go SDK] Case on worker labels for durable tasks by @mnafees in [#2511](https://github.com/hatchet-dev/hatchet/pull/2511) ## v0.73.63 - 2025-11-07 ### Added + - Add pagination support for V1LogLineList by @jishnundth in [#2354](https://github.com/hatchet-dev/hatchet/pull/2354) ### Changed -- Immediate Payload Offloads OLAP Wiring by @mrkaye97 in [#2492](https://github.com/hatchet-dev/hatchet/pull/2492) +- Immediate Payload Offloads OLAP Wiring by @mrkaye97 in [#2492](https://github.com/hatchet-dev/hatchet/pull/2492) ## v0.73.62 - 2025-11-07 ### Changed -- Pass labels to durable worker by @mnafees in [#2504](https://github.com/hatchet-dev/hatchet/pull/2504) +- Pass labels to durable worker by @mnafees in [#2504](https://github.com/hatchet-dev/hatchet/pull/2504) ## v0.73.61 - 2025-11-06 ### Added + - Configurable OLAP status update size limits by @mrkaye97 in [#2499](https://github.com/hatchet-dev/hatchet/pull/2499) ### Fixed -- Propagate parent id through to `V1TaskSummary` properly by @mrkaye97 in [#2496](https://github.com/hatchet-dev/hatchet/pull/2496) +- Propagate parent id through to `V1TaskSummary` properly by @mrkaye97 in [#2496](https://github.com/hatchet-dev/hatchet/pull/2496) ## v0.73.60 - 2025-11-04 ### Fixed -- Deadlocks on trigger, olap prometheus background worker, otel improvements by @mnafees in [#2475](https://github.com/hatchet-dev/hatchet/pull/2475) +- Deadlocks on trigger, olap prometheus background worker, otel improvements by @mnafees in [#2475](https://github.com/hatchet-dev/hatchet/pull/2475) ## v0.73.58 - 2025-11-02 ### Added + - Add grpc otel spans, better tx debugging by @abelanger5 in [#2474](https://github.com/hatchet-dev/hatchet/pull/2474) ### Changed + - Update frontend onboarding steps by @sidpremkumar in [#2478](https://github.com/hatchet-dev/hatchet/pull/2478) - [hotfix] Temporarily increase `TestLoadCLI` average threshold by @mnafees in [#2461](https://github.com/hatchet-dev/hatchet/pull/2461) ### Fixed + - Fix Go SDK cron inputs by @mnafees in [#2481](https://github.com/hatchet-dev/hatchet/pull/2481) - Include payload partitions in olap partitions to drop by @mrkaye97 in [#2472](https://github.com/hatchet-dev/hatchet/pull/2472) - ## v0.73.56 - 2025-10-30 ### Changed + - Update managed compute regions by @mnafees in [#2470](https://github.com/hatchet-dev/hatchet/pull/2470) ### Fixed -- Read payloads from payload store for event API by @mrkaye97 in [#2471](https://github.com/hatchet-dev/hatchet/pull/2471) +- Read payloads from payload store for event API by @mrkaye97 in [#2471](https://github.com/hatchet-dev/hatchet/pull/2471) ## v0.73.55 - 2025-10-30 ### Fixed -- Re-enable writes by @mrkaye97 in [#2469](https://github.com/hatchet-dev/hatchet/pull/2469) +- Re-enable writes by @mrkaye97 in [#2469](https://github.com/hatchet-dev/hatchet/pull/2469) ## v0.73.54 - 2025-10-30 ### Changed + - Run cleanup on more tables by @mnafees in [#2467](https://github.com/hatchet-dev/hatchet/pull/2467) ### Fixed -- Don't send expiry alert on internal proxy tokens by @abelanger5 in [#2468](https://github.com/hatchet-dev/hatchet/pull/2468) +- Don't send expiry alert on internal proxy tokens by @abelanger5 in [#2468](https://github.com/hatchet-dev/hatchet/pull/2468) ## v0.73.53 - 2025-10-30 ### Changed -- No need to check for partitions when updating them by @mnafees in [#2466](https://github.com/hatchet-dev/hatchet/pull/2466) +- No need to check for partitions when updating them by @mnafees in [#2466](https://github.com/hatchet-dev/hatchet/pull/2466) ## v0.73.52 - 2025-10-30 ### Changed -- [hotfix] Meaningful casing for engine liveness and readiness probes by @mnafees in [#2465](https://github.com/hatchet-dev/hatchet/pull/2465) +- [hotfix] Meaningful casing for engine liveness and readiness probes by @mnafees in [#2465](https://github.com/hatchet-dev/hatchet/pull/2465) ## v0.73.51 - 2025-10-30 ### Changed -- Increase timeout and log more by @mnafees in [#2464](https://github.com/hatchet-dev/hatchet/pull/2464) +- Increase timeout and log more by @mnafees in [#2464](https://github.com/hatchet-dev/hatchet/pull/2464) ## v0.73.50 - 2025-10-30 ### Changed -- Do not run cleanup on `v1_workflow_concurrency_slot` by @mnafees in [#2463](https://github.com/hatchet-dev/hatchet/pull/2463) +- Do not run cleanup on `v1_workflow_concurrency_slot` by @mnafees in [#2463](https://github.com/hatchet-dev/hatchet/pull/2463) ## v0.73.49 - 2025-10-30 ### Added + - Add support for non-wal payload store logic to skip main db by @sidpremkumar in [#2445](https://github.com/hatchet-dev/hatchet/pull/2445) ### Changed + - Logs for liveness and readiness endpoints + PG conn stats by @mnafees in [#2460](https://github.com/hatchet-dev/hatchet/pull/2460) ### Fixed -- Reduce status update limits from 10k -> 1k by @abelanger5 in [#2462](https://github.com/hatchet-dev/hatchet/pull/2462) +- Reduce status update limits from 10k -> 1k by @abelanger5 in [#2462](https://github.com/hatchet-dev/hatchet/pull/2462) ## v0.73.47 - 2025-10-28 ### Changed -- [hotfix] Fix running task stats without concurrency keys by @mnafees in [#2452](https://github.com/hatchet-dev/hatchet/pull/2452) +- [hotfix] Fix running task stats without concurrency keys by @mnafees in [#2452](https://github.com/hatchet-dev/hatchet/pull/2452) ## v0.73.46 - 2025-10-28 ### Changed + - New tenant task stats endpoint by @mnafees in [#2433](https://github.com/hatchet-dev/hatchet/pull/2433) - Retry RMQ messages indefinitely with aggressive logging after 5 retries by @mnafees in [#2448](https://github.com/hatchet-dev/hatchet/pull/2448) - Increase timeout to 30 seconds by @mnafees in [#2449](https://github.com/hatchet-dev/hatchet/pull/2449) ### Fixed -- Fix confusing error by @mnafees in [#2447](https://github.com/hatchet-dev/hatchet/pull/2447) +- Fix confusing error by @mnafees in [#2447](https://github.com/hatchet-dev/hatchet/pull/2447) ## v0.73.45 - 2025-10-23 ### Added + - Add vars to tune concurrency poller by @mnafees in [#2428](https://github.com/hatchet-dev/hatchet/pull/2428) ### Changed + - Run cleanup job every minute by @mnafees in [#2440](https://github.com/hatchet-dev/hatchet/pull/2440) ### Fixed -- Payload performance by @abelanger5 in [#2441](https://github.com/hatchet-dev/hatchet/pull/2441) +- Payload performance by @abelanger5 in [#2441](https://github.com/hatchet-dev/hatchet/pull/2441) ## v0.73.44 - 2025-10-21 ### Fixed -- Move err check to before len check by @abelanger5 in [#2437](https://github.com/hatchet-dev/hatchet/pull/2437) +- Move err check to before len check by @abelanger5 in [#2437](https://github.com/hatchet-dev/hatchet/pull/2437) ## v0.73.43 - 2025-10-20 ### Added -- OLAP Payloads by @mrkaye97 in [#2410](https://github.com/hatchet-dev/hatchet/pull/2410) +- OLAP Payloads by @mrkaye97 in [#2410](https://github.com/hatchet-dev/hatchet/pull/2410) ## v0.73.42 - 2025-10-17 ### Fixed -- Fix race condition in child spawn by @mnafees in [#2429](https://github.com/hatchet-dev/hatchet/pull/2429) +- Fix race condition in child spawn by @mnafees in [#2429](https://github.com/hatchet-dev/hatchet/pull/2429) ## v0.73.41 - 2025-10-16 ### Fixed -- Fix for Hatchet Lite to still properly fallback to using the `postgres` message queue kind by @mnafees in [#2392](https://github.com/hatchet-dev/hatchet/pull/2392) +- Fix for Hatchet Lite to still properly fallback to using the `postgres` message queue kind by @mnafees in [#2392](https://github.com/hatchet-dev/hatchet/pull/2392) ## v0.73.40 - 2025-10-16 ### Changed + - Cleanup job for old and invalid entries by @mnafees in [#2378](https://github.com/hatchet-dev/hatchet/pull/2378) - [hotfix] Avoid throwing error logs from ratelimit MW for invalid API routes by @mnafees in [#2420](https://github.com/hatchet-dev/hatchet/pull/2420) ### Fixed + - Fix OTel span attribute naming convention by @mnafees in [#2409](https://github.com/hatchet-dev/hatchet/pull/2409) - Swallow idempotency key error for scheduled runs by @mrkaye97 in [#2425](https://github.com/hatchet-dev/hatchet/pull/2425) - ## [0.73.40-alpha.0] - 2025-10-15 ### Fixed -- Payload fallback for child runs by @mrkaye97 in [#2421](https://github.com/hatchet-dev/hatchet/pull/2421) +- Payload fallback for child runs by @mrkaye97 in [#2421](https://github.com/hatchet-dev/hatchet/pull/2421) ## v0.73.38 - 2025-10-15 ### Added + - Stateful polling intervals by @abelanger5 in [#2417](https://github.com/hatchet-dev/hatchet/pull/2417) - Scheduled run detail view, bulk cancel / replay with pagination helper by @mrkaye97 in [#2416](https://github.com/hatchet-dev/hatchet/pull/2416) ### Changed + - Introduce vars to tune `ANALYZE` job gocron run intervals by @mnafees in [#2407](https://github.com/hatchet-dev/hatchet/pull/2407) - Use `UTC` for all pgx connections and check for database TZ by @mnafees in [#2398](https://github.com/hatchet-dev/hatchet/pull/2398) - ## v0.73.35 - 2025-10-08 ### Added + - Gzip compression for large payloads, persistent OLAP writes by @mrkaye97 in [#2368](https://github.com/hatchet-dev/hatchet/pull/2368) - Immediate Payload Offloads by @mrkaye97 in [#2375](https://github.com/hatchet-dev/hatchet/pull/2375) - Pausable Crons by @mrkaye97 in [#2395](https://github.com/hatchet-dev/hatchet/pull/2395) ### Changed + - Properly case on output byte length by @mnafees in [#2394](https://github.com/hatchet-dev/hatchet/pull/2394) ### Fixed -- Optimize concurrency slot trigger method by @abelanger5 in [#2391](https://github.com/hatchet-dev/hatchet/pull/2391) +- Optimize concurrency slot trigger method by @abelanger5 in [#2391](https://github.com/hatchet-dev/hatchet/pull/2391) ## v0.73.34 - 2025-10-03 ### Changed + - Include `tenant_id` in OTel spans wherever possible by @mnafees in [#2382](https://github.com/hatchet-dev/hatchet/pull/2382) ### Fixed + - Payload fallbacks, WAL conflict handling, WAL eviction by @mrkaye97 in [#2372](https://github.com/hatchet-dev/hatchet/pull/2372) - Run analyze every 3 hours by @mrkaye97 in [#2380](https://github.com/hatchet-dev/hatchet/pull/2380) - ## v0.73.33 - 2025-10-02 ### Added -- Add ApplyNamespace for BulkRunWorkflow by @icbd in [#2374](https://github.com/hatchet-dev/hatchet/pull/2374) +- Add ApplyNamespace for BulkRunWorkflow by @icbd in [#2374](https://github.com/hatchet-dev/hatchet/pull/2374) ## v0.73.32 - 2025-09-30 ### Changed + - Candidate Fix: WAL Write Dupes by @mrkaye97 in [#2369](https://github.com/hatchet-dev/hatchet/pull/2369) - Event payload max height, popover positioning by @mrkaye97 in [#2367](https://github.com/hatchet-dev/hatchet/pull/2367) - ## v0.73.31 - 2025-09-30 ### Fixed -- Relax check constraint to allow null payloads by @mrkaye97 in [#2366](https://github.com/hatchet-dev/hatchet/pull/2366) +- Relax check constraint to allow null payloads by @mrkaye97 in [#2366](https://github.com/hatchet-dev/hatchet/pull/2366) ## v0.73.30 - 2025-09-30 ### Added + - Max channels for rabbitmq by @abelanger5 in [#2365](https://github.com/hatchet-dev/hatchet/pull/2365) ### Changed -- Ignore tenants with deletedAt non null by @mnafees in [#2364](https://github.com/hatchet-dev/hatchet/pull/2364) +- Ignore tenants with deletedAt non null by @mnafees in [#2364](https://github.com/hatchet-dev/hatchet/pull/2364) ## v0.73.29 - 2025-09-29 ### Changed + - Use member populator for tenant member API ops by @mnafees in [#2363](https://github.com/hatchet-dev/hatchet/pull/2363) ### Fixed -- Disable inner scroll by @mrkaye97 in [#2362](https://github.com/hatchet-dev/hatchet/pull/2362) +- Disable inner scroll by @mrkaye97 in [#2362](https://github.com/hatchet-dev/hatchet/pull/2362) ## v0.73.28 - 2025-09-29 ### Changed + - Worker detail fixes by @mrkaye97 in [#2353](https://github.com/hatchet-dev/hatchet/pull/2353) ### Fixed -- Use separate connections for pub and sub by @abelanger5 in [#2358](https://github.com/hatchet-dev/hatchet/pull/2358) +- Use separate connections for pub and sub by @abelanger5 in [#2358](https://github.com/hatchet-dev/hatchet/pull/2358) ## v0.73.26 - 2025-09-26 ### Fixed -- Async start step run action event by @abelanger5 in [#2351](https://github.com/hatchet-dev/hatchet/pull/2351) +- Async start step run action event by @abelanger5 in [#2351](https://github.com/hatchet-dev/hatchet/pull/2351) ## v0.73.25 - 2025-09-26 ### Changed -- FE Polish, VI: Make badges dynamically sized, use slate instead of fuchsia for queued, display ms on dates by @mrkaye97 in [#2352](https://github.com/hatchet-dev/hatchet/pull/2352) +- FE Polish, VI: Make badges dynamically sized, use slate instead of fuchsia for queued, display ms on dates by @mrkaye97 in [#2352](https://github.com/hatchet-dev/hatchet/pull/2352) ## v0.73.24 - 2025-09-26 ### Fixed -- Scope override by @mrkaye97 in [#2349](https://github.com/hatchet-dev/hatchet/pull/2349) +- Scope override by @mrkaye97 in [#2349](https://github.com/hatchet-dev/hatchet/pull/2349) ## v0.73.23 - 2025-09-26 ### Fixed -- Rename metrics queries, always refetch queue metrics, change default refetch interval, configurable WAL poll limit by @mrkaye97 in [#2346](https://github.com/hatchet-dev/hatchet/pull/2346) +- Rename metrics queries, always refetch queue metrics, change default refetch interval, configurable WAL poll limit by @mrkaye97 in [#2346](https://github.com/hatchet-dev/hatchet/pull/2346) ## v0.73.22 - 2025-09-25 ### Changed -- Error log if we send >10mb message over the internal queue by @mrkaye97 in [#2345](https://github.com/hatchet-dev/hatchet/pull/2345) +- Error log if we send >10mb message over the internal queue by @mrkaye97 in [#2345](https://github.com/hatchet-dev/hatchet/pull/2345) ## v0.73.21 - 2025-09-25 ### Changed + - FE Polish V: Searchable workflows, additional metadata tab by @mrkaye97 in [#2342](https://github.com/hatchet-dev/hatchet/pull/2342) ### Fixed + - Improve DAG status updates by @abelanger5 in [#2343](https://github.com/hatchet-dev/hatchet/pull/2343) - Show legacy data by @mrkaye97 in [#2344](https://github.com/hatchet-dev/hatchet/pull/2344) - ## v0.73.20 - 2025-09-24 ### Changed + - FE Polish, IV: Tooltips, task event scrolling by @mrkaye97 in [#2335](https://github.com/hatchet-dev/hatchet/pull/2335) - FE Polish, III: More state management fixes, worker pages by @mrkaye97 in [#2332](https://github.com/hatchet-dev/hatchet/pull/2332) ### Fixed + - Event getter backwards compat by @mrkaye97 in [#2337](https://github.com/hatchet-dev/hatchet/pull/2337) - Use `SplitN` instead of `Split` by @mrkaye97 in [#2336](https://github.com/hatchet-dev/hatchet/pull/2336) - Stable ordering for flattened tasks + child tasks by @mrkaye97 in [#2334](https://github.com/hatchet-dev/hatchet/pull/2334) - ## v0.73.18 - 2025-09-23 ### Fixed -- Rogue effect hook, some more cleanup by @mrkaye97 in [#2329](https://github.com/hatchet-dev/hatchet/pull/2329) +- Rogue effect hook, some more cleanup by @mrkaye97 in [#2329](https://github.com/hatchet-dev/hatchet/pull/2329) ## v0.73.17 - 2025-09-23 ### Added -- Add error level logs if we fall back to the task input for monitoring by @mrkaye97 in [#2328](https://github.com/hatchet-dev/hatchet/pull/2328) +- Add error level logs if we fall back to the task input for monitoring by @mrkaye97 in [#2328](https://github.com/hatchet-dev/hatchet/pull/2328) ## v0.73.16 - 2025-09-23 ### Fixed -- Frontend polish, Part II by @mrkaye97 in [#2327](https://github.com/hatchet-dev/hatchet/pull/2327) +- Frontend polish, Part II by @mrkaye97 in [#2327](https://github.com/hatchet-dev/hatchet/pull/2327) ## v0.73.15 - 2025-09-23 ### Changed -- Update docs to use Go SDK v1 by @mnafees in [#2313](https://github.com/hatchet-dev/hatchet/pull/2313) +- Update docs to use Go SDK v1 by @mnafees in [#2313](https://github.com/hatchet-dev/hatchet/pull/2313) ## v0.73.14 - 2025-09-22 ### Added -- Show statuses of run filters with colors by @mrkaye97 in [#2325](https://github.com/hatchet-dev/hatchet/pull/2325) +- Show statuses of run filters with colors by @mrkaye97 in [#2325](https://github.com/hatchet-dev/hatchet/pull/2325) ## v0.73.13 - 2025-09-21 ### Added + - Support dynamic rate limit durations by @abelanger5 in [#2320](https://github.com/hatchet-dev/hatchet/pull/2320) ### Changed + - Compact toolbar, refetch improvements, table improvements by @mrkaye97 in [#2292](https://github.com/hatchet-dev/hatchet/pull/2292) ### Fixed -- Skip locked on queue updates by @abelanger5 in [#2321](https://github.com/hatchet-dev/hatchet/pull/2321) +- Skip locked on queue updates by @abelanger5 in [#2321](https://github.com/hatchet-dev/hatchet/pull/2321) ## v0.73.12 - 2025-09-19 ### Fixed -- Payload WAL dupes by @mrkaye97 in [#2319](https://github.com/hatchet-dev/hatchet/pull/2319) +- Payload WAL dupes by @mrkaye97 in [#2319](https://github.com/hatchet-dev/hatchet/pull/2319) ## v0.73.11 - 2025-09-19 ### Fixed -- Update payload properly on replay by @mrkaye97 in [#2317](https://github.com/hatchet-dev/hatchet/pull/2317) +- Update payload properly on replay by @mrkaye97 in [#2317](https://github.com/hatchet-dev/hatchet/pull/2317) ## v0.73.10 - 2025-09-18 ### Fixed -- Payloads OLAP backwards compat by @mrkaye97 in [#2316](https://github.com/hatchet-dev/hatchet/pull/2316) +- Payloads OLAP backwards compat by @mrkaye97 in [#2316](https://github.com/hatchet-dev/hatchet/pull/2316) ## v0.73.9 - 2025-09-18 ### Fixed -- Event filtering edge case by @mrkaye97 in [#2311](https://github.com/hatchet-dev/hatchet/pull/2311) +- Event filtering edge case by @mrkaye97 in [#2311](https://github.com/hatchet-dev/hatchet/pull/2311) ## v0.73.8 - 2025-09-18 ### Fixed -- Fix seed default tenant slug by @mnafees in [#2315](https://github.com/hatchet-dev/hatchet/pull/2315) +- Fix seed default tenant slug by @mnafees in [#2315](https://github.com/hatchet-dev/hatchet/pull/2315) ## v0.73.7 - 2025-09-17 ### Fixed -- Empty state by @grutt in [#2310](https://github.com/hatchet-dev/hatchet/pull/2310) +- Empty state by @grutt in [#2310](https://github.com/hatchet-dev/hatchet/pull/2310) ## v0.73.6 - 2025-09-17 ### Changed + - Properly fall back to tenant switcher by @mnafees in [#2307](https://github.com/hatchet-dev/hatchet/pull/2307) ### Fixed -- DAG details rendering in side panel, backwards compatible event list API by @mrkaye97 in [#2309](https://github.com/hatchet-dev/hatchet/pull/2309) +- DAG details rendering in side panel, backwards compatible event list API by @mrkaye97 in [#2309](https://github.com/hatchet-dev/hatchet/pull/2309) ## v0.73.4 - 2025-09-16 ### Changed -- Allow RabbitMQ to be used with Hatchet Lite by @mnafees in [#2128](https://github.com/hatchet-dev/hatchet/pull/2128) +- Allow RabbitMQ to be used with Hatchet Lite by @mnafees in [#2128](https://github.com/hatchet-dev/hatchet/pull/2128) ## v0.73.3 - 2025-09-16 ### Changed + - [hotfix] CLI arg to specify average duration per event threshold for loadtest to succeed by @mnafees in [#2300](https://github.com/hatchet-dev/hatchet/pull/2300) ### Fixed + - Fix `GetDetails` in `Runs` feature client of Go SDK v1 by @mnafees in [#2297](https://github.com/hatchet-dev/hatchet/pull/2297) - WAL partition poll function type by @mrkaye97 in [#2301](https://github.com/hatchet-dev/hatchet/pull/2301) - ## v0.73.2 - 2025-09-15 ### Added -- Add organization by @mnafees in [#2299](https://github.com/hatchet-dev/hatchet/pull/2299) +- Add organization by @mnafees in [#2299](https://github.com/hatchet-dev/hatchet/pull/2299) ## v0.73.1 - 2025-09-12 ### Fixed -- Revert partition pruning by @mrkaye97 in [#2295](https://github.com/hatchet-dev/hatchet/pull/2295) +- Revert partition pruning by @mrkaye97 in [#2295](https://github.com/hatchet-dev/hatchet/pull/2295) ## v0.73.0 - 2025-09-12 ### Added + - Add panic handler to Go SDK by @mnafees in [#2293](https://github.com/hatchet-dev/hatchet/pull/2293) - Partition pruning for `ListTaskParentOutputs`, lookup index for `v1_payload_wal` by @mrkaye97 in [#2294](https://github.com/hatchet-dev/hatchet/pull/2294) - Payload Store Repository by @mrkaye97 in [#2047](https://github.com/hatchet-dev/hatchet/pull/2047) ### Removed + - Remove `nginx` and use custom static fileservers by @mnafees in [#1928](https://github.com/hatchet-dev/hatchet/pull/1928) ### Fixed -- Scheduled runs race w/ idempotency key check by @mrkaye97 in [#2077](https://github.com/hatchet-dev/hatchet/pull/2077) +- Scheduled runs race w/ idempotency key check by @mrkaye97 in [#2077](https://github.com/hatchet-dev/hatchet/pull/2077) ## v0.72.8 - 2025-09-11 ### Added -- Filters UI, Events page refactor, Misc. other fixes by @mrkaye97 in [#2276](https://github.com/hatchet-dev/hatchet/pull/2276) -- Feat improve auth error handling by @grutt in [#1893](https://github.com/hatchet-dev/hatchet/pull/1893) +- Filters UI, Events page refactor, Misc. other fixes by @mrkaye97 in [#2276](https://github.com/hatchet-dev/hatchet/pull/2276) +- Feat improve auth error handling by @grutt in [#1893](https://github.com/hatchet-dev/hatchet/pull/1893) ## v0.72.4 - 2025-09-10 ### Changed + - Do not show archived tenants anywhere by @mnafees in [#2280](https://github.com/hatchet-dev/hatchet/pull/2280) - Docs-in-app, Part I by @mrkaye97 in [#2183](https://github.com/hatchet-dev/hatchet/pull/2183) - ## v0.72.3 - 2025-09-09 ### Changed + - Org UI feedback improvements by @mnafees in [#2275](https://github.com/hatchet-dev/hatchet/pull/2275) - Error out instead of panic by @mnafees in [#2274](https://github.com/hatchet-dev/hatchet/pull/2274) - ## v0.72.2 - 2025-09-09 ### Added + - Worker slot Prom metrics by @mrkaye97 in [#2195](https://github.com/hatchet-dev/hatchet/pull/2195) ### Changed + - Go SDK v1 feature client changes by @mnafees in [#2160](https://github.com/hatchet-dev/hatchet/pull/2160) ### Fixed -- Fix custom auth casing by @mnafees in [#2268](https://github.com/hatchet-dev/hatchet/pull/2268) +- Fix custom auth casing by @mnafees in [#2268](https://github.com/hatchet-dev/hatchet/pull/2268) ## v0.72.1 - 2025-09-08 ### Changed + - Expired invite status by @mnafees in [#2266](https://github.com/hatchet-dev/hatchet/pull/2266) ### Fixed -- Fix type names by @mnafees in [#2264](https://github.com/hatchet-dev/hatchet/pull/2264) +- Fix type names by @mnafees in [#2264](https://github.com/hatchet-dev/hatchet/pull/2264) ## v0.72.0 - 2025-09-05 ### Changed + - Introduce UI for Organizations by @mnafees in [#2247](https://github.com/hatchet-dev/hatchet/pull/2247) - Make sure to case on err properly by @mnafees in [#2248](https://github.com/hatchet-dev/hatchet/pull/2248) - Properly handle 404s from populator middleware to avoid panics by @mnafees in [#2238](https://github.com/hatchet-dev/hatchet/pull/2238) ### Fixed -- Fixes for organization selector by @mnafees in [#2257](https://github.com/hatchet-dev/hatchet/pull/2257) +- Fixes for organization selector by @mnafees in [#2257](https://github.com/hatchet-dev/hatchet/pull/2257) ## v0.71.13 - 2025-09-02 ### Changed + - Periodically run `ANALYZE` on `v1_task` and `v1_task_event` by @mnafees in [#2236](https://github.com/hatchet-dev/hatchet/pull/2236) - Guard cleanAdditionalMetadata against JSON null; client: avoid null AdditionalMetadata in BulkPush; add regression test by @xcono in [#2191](https://github.com/hatchet-dev/hatchet/pull/2191) - Inject into context by @mnafees in [#2213](https://github.com/hatchet-dev/hatchet/pull/2213) - Onboarding key by @grutt in [#2212](https://github.com/hatchet-dev/hatchet/pull/2212) ### Fixed + - Rm annoying loki logs by @mrkaye97 in [#2224](https://github.com/hatchet-dev/hatchet/pull/2224) - Remove rate limited items from in memory buffer by @abelanger5 in [#2207](https://github.com/hatchet-dev/hatchet/pull/2207) - ## v0.71.10 - 2025-08-26 ### Fixed -- Remove `custom auth` by @mrkaye97 in [#2203](https://github.com/hatchet-dev/hatchet/pull/2203) +- Remove `custom auth` by @mrkaye97 in [#2203](https://github.com/hatchet-dev/hatchet/pull/2203) ## v0.71.9 - 2025-08-26 ### Fixed + - Explicit ordering in ReleaseTasks and lock parent slots by @abelanger5 in [#2201](https://github.com/hatchet-dev/hatchet/pull/2201) - Don't query database when flush is called concurrently by @abelanger5 in [#2202](https://github.com/hatchet-dev/hatchet/pull/2202) - Confusing error message by @abelanger5 in [#2199](https://github.com/hatchet-dev/hatchet/pull/2199) - ## v0.71.8 - 2025-08-25 ### Fixed -- Child runs not rendering after one day, empty worker ids, additional meta filters not being applied to counts by @mrkaye97 in [#2196](https://github.com/hatchet-dev/hatchet/pull/2196) +- Child runs not rendering after one day, empty worker ids, additional meta filters not being applied to counts by @mrkaye97 in [#2196](https://github.com/hatchet-dev/hatchet/pull/2196) ## [0.71.8-alpha.0] - 2025-08-25 ### Added + - Improved onboarding part 1 by @grutt in [#2186](https://github.com/hatchet-dev/hatchet/pull/2186) ### Fixed -- Match and cancel newest/in progress deadlocks by @abelanger5 in [#2190](https://github.com/hatchet-dev/hatchet/pull/2190) +- Match and cancel newest/in progress deadlocks by @abelanger5 in [#2190](https://github.com/hatchet-dev/hatchet/pull/2190) ## v0.71.7 - 2025-08-22 ### Added + - Add visibility to stream send event by @abelanger5 in [#2174](https://github.com/hatchet-dev/hatchet/pull/2174) - Analytics events by @grutt in [#2171](https://github.com/hatchet-dev/hatchet/pull/2171) ### Changed + - Re-enable refetch queue metrics, fix action button / dropdown state by @mrkaye97 in [#2182](https://github.com/hatchet-dev/hatchet/pull/2182) - Limit frequency of updates to rate limits by @abelanger5 in [#2173](https://github.com/hatchet-dev/hatchet/pull/2173) - ## v0.71.4 - 2025-08-20 ### Added + - Run `ANALYZE` on a few tables once a day by @mrkaye97 in [#2163](https://github.com/hatchet-dev/hatchet/pull/2163) - Add Linear to preconfigured webhooks by @mrkaye97 in [#2157](https://github.com/hatchet-dev/hatchet/pull/2157) ### Changed + - Introduce `customAuth` to the OpenAPI spec by @mnafees in [#2168](https://github.com/hatchet-dev/hatchet/pull/2168) ### Fixed + - Move rate limited queue items off the main queue by @abelanger5 in [#2155](https://github.com/hatchet-dev/hatchet/pull/2155) - Populate DAG Metadata Sequentially by @mrkaye97 in [#2156](https://github.com/hatchet-dev/hatchet/pull/2156) - Auto-generate docs snippets and examples by @mrkaye97 in [#2139](https://github.com/hatchet-dev/hatchet/pull/2139) - Deadlocking on DAG concurrency by @mrkaye97 in [#2111](https://github.com/hatchet-dev/hatchet/pull/2111) - ## v0.70.7 - 2025-08-14 ### Added + - Webhook fixes / improvements by @mrkaye97 in [#2131](https://github.com/hatchet-dev/hatchet/pull/2131) ### Changed + - Runs list state management + bug fixes part I by @mrkaye97 in [#2114](https://github.com/hatchet-dev/hatchet/pull/2114) - [hotfix] Better messaging around tenant prometheus metrics empty state by @mnafees in [#2124](https://github.com/hatchet-dev/hatchet/pull/2124) - Workflow combobox search functionality by @mnafees in [#2118](https://github.com/hatchet-dev/hatchet/pull/2118) ### Fixed -- Add back sync docs script by @mrkaye97 in [#2123](https://github.com/hatchet-dev/hatchet/pull/2123) +- Add back sync docs script by @mrkaye97 in [#2123](https://github.com/hatchet-dev/hatchet/pull/2123) ## v0.70.6 - 2025-08-12 ### Added + - Add k8s pod info to traces by @mnafees in [#2109](https://github.com/hatchet-dev/hatchet/pull/2109) ### Changed + - [Python] Feat: Dependency Injection, Improved error handling by @mrkaye97 in [#2067](https://github.com/hatchet-dev/hatchet/pull/2067) - [HAT-432] Enforce task priorities to be between 1 and 3 by @mnafees in [#2110](https://github.com/hatchet-dev/hatchet/pull/2110) ### Fixed + - Optimize DAG timing query for Prom by @mrkaye97 in [#2102](https://github.com/hatchet-dev/hatchet/pull/2102) - Waterfall panic + query simplification by @mrkaye97 in [#2116](https://github.com/hatchet-dev/hatchet/pull/2116) - Don't wait for grpc stream send on rabbitmq loop by @abelanger5 in [#2115](https://github.com/hatchet-dev/hatchet/pull/2115) - ## v0.70.5 - 2025-08-07 ### Changed -- Fail task tracing by @mrkaye97 in [#2101](https://github.com/hatchet-dev/hatchet/pull/2101) +- Fail task tracing by @mrkaye97 in [#2101](https://github.com/hatchet-dev/hatchet/pull/2101) ## v0.70.4 - 2025-08-06 ### Added + - Add contextual data for trigger via events by @mnafees in [#2092](https://github.com/hatchet-dev/hatchet/pull/2092) ### Fixed -- Call `PopulateTaskRunData` sequentially by @mrkaye97 in [#2097](https://github.com/hatchet-dev/hatchet/pull/2097) +- Call `PopulateTaskRunData` sequentially by @mrkaye97 in [#2097](https://github.com/hatchet-dev/hatchet/pull/2097) ## v0.70.3 - 2025-08-06 ### Added + - Add telemetry to task status repo methods by @mnafees in [#2091](https://github.com/hatchet-dev/hatchet/pull/2091) ### Fixed -- Improve performance of `UpdateTasksToAssigned` by @mrkaye97 in [#2094](https://github.com/hatchet-dev/hatchet/pull/2094) +- Improve performance of `UpdateTasksToAssigned` by @mrkaye97 in [#2094](https://github.com/hatchet-dev/hatchet/pull/2094) ## v0.70.2 - 2025-08-06 ### Added + - Add telemetry around task statuses in controller by @mnafees in [#2090](https://github.com/hatchet-dev/hatchet/pull/2090) ### Fixed + - ProcessTaskTimeouts limit and timeout by @grutt in [#2087](https://github.com/hatchet-dev/hatchet/pull/2087) - Webhook copy improvements by @mrkaye97 in [#2081](https://github.com/hatchet-dev/hatchet/pull/2081) From f399e4e67fbfdd34a98a4d3df7a20893411ddd71 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Thu, 4 Jun 2026 10:00:29 -0400 Subject: [PATCH 65/69] chore: appease the cop --- sdks/ruby/src/hatchet-sdk.gemspec | 2 +- sdks/ruby/src/lib/hatchet-sdk.rb | 131 ++++++++++++++---------------- 2 files changed, 62 insertions(+), 71 deletions(-) diff --git a/sdks/ruby/src/hatchet-sdk.gemspec b/sdks/ruby/src/hatchet-sdk.gemspec index 46246efb89..f45d02dfe4 100644 --- a/sdks/ruby/src/hatchet-sdk.gemspec +++ b/sdks/ruby/src/hatchet-sdk.gemspec @@ -57,8 +57,8 @@ Gem::Specification.new do |spec| # Runtime dependencies for gRPC spec.add_dependency "concurrent-ruby", ">= 1.1" - spec.add_dependency "google-protobuf", "~> 4.0" spec.add_dependency "googleapis-common-protos-types", "~> 1.0" + spec.add_dependency "google-protobuf", "~> 4.0" spec.add_dependency "grpc", "~> 1.60" # Development dependencies diff --git a/sdks/ruby/src/lib/hatchet-sdk.rb b/sdks/ruby/src/lib/hatchet-sdk.rb index f1651e5005..2b91f4b3a8 100644 --- a/sdks/ruby/src/lib/hatchet-sdk.rb +++ b/sdks/ruby/src/lib/hatchet-sdk.rb @@ -3,80 +3,71 @@ require_relative "hatchet/version" require_relative "hatchet/config" -# Define base error class before loading submodules that depend on it -module Hatchet - # Base error class for all Hatchet-related errors - class Error < StandardError; end -end - -require_relative "hatchet/clients" -require_relative "hatchet/features/events" -require_relative "hatchet/features/runs" -require_relative "hatchet/features/tenant" -require_relative "hatchet/features/logs" -require_relative "hatchet/features/workers" -require_relative "hatchet/features/cel" -require_relative "hatchet/features/workflows" -require_relative "hatchet/features/filters" -require_relative "hatchet/features/metrics" -require_relative "hatchet/features/rate_limits" -require_relative "hatchet/features/cron" -require_relative "hatchet/features/scheduled" - -# Core classes -require_relative "hatchet/exceptions" -require_relative "hatchet/engine_version" -require_relative "hatchet/eviction_policy" -require_relative "hatchet/concurrency" -require_relative "hatchet/conditions" -require_relative "hatchet/condition_converter" -require_relative "hatchet/rate_limit" -require_relative "hatchet/labels" -require_relative "hatchet/trigger_options" -require_relative "hatchet/default_filter" -require_relative "hatchet/workflow_run" -require_relative "hatchet/context" -require_relative "hatchet/durable_context" -require_relative "hatchet/task" -require_relative "hatchet/workflow" -require_relative "hatchet/context_vars" -require_relative "hatchet/worker_obj" - -# gRPC connection and client infrastructure -require_relative "hatchet/connection" - -# Generated protobuf contracts (add contracts directory to load path for internal requires) -$LOAD_PATH.unshift(File.join(__dir__, "hatchet", "contracts")) unless $LOAD_PATH.include?(File.join(__dir__, "hatchet", "contracts")) -require_relative "hatchet/contracts/dispatcher/dispatcher_pb" -require_relative "hatchet/contracts/dispatcher/dispatcher_services_pb" -require_relative "hatchet/contracts/events/events_pb" -require_relative "hatchet/contracts/events/events_services_pb" -require_relative "hatchet/contracts/workflows/workflows_pb" -require_relative "hatchet/contracts/workflows/workflows_services_pb" -require_relative "hatchet/contracts/v1/shared/condition_pb" -require_relative "hatchet/contracts/v1/shared/trigger_pb" -require_relative "hatchet/contracts/v1/dispatcher_pb" -require_relative "hatchet/contracts/v1/dispatcher_services_pb" -require_relative "hatchet/contracts/v1/workflows_pb" -require_relative "hatchet/contracts/v1/workflows_services_pb" - -# gRPC client wrappers -require_relative "hatchet/clients/grpc/dispatcher" -require_relative "hatchet/clients/grpc/admin" -require_relative "hatchet/clients/grpc/event_client" - -# Worker runtime -require_relative "hatchet/worker/action_listener" -require_relative "hatchet/worker/workflow_run_listener" -require_relative "hatchet/worker/durable_eviction/cache" -require_relative "hatchet/worker/durable_eviction/manager" -require_relative "hatchet/worker/durable_event_listener" -require_relative "hatchet/worker/runner" - # Ruby SDK for Hatchet workflow engine # # @see https://docs.hatchet.run for Hatchet documentation module Hatchet + class Error < StandardError; end + + require_relative "hatchet/clients" + require_relative "hatchet/features/events" + require_relative "hatchet/features/runs" + require_relative "hatchet/features/tenant" + require_relative "hatchet/features/logs" + require_relative "hatchet/features/workers" + require_relative "hatchet/features/cel" + require_relative "hatchet/features/workflows" + require_relative "hatchet/features/filters" + require_relative "hatchet/features/metrics" + require_relative "hatchet/features/rate_limits" + require_relative "hatchet/features/cron" + require_relative "hatchet/features/scheduled" + + require_relative "hatchet/exceptions" + require_relative "hatchet/engine_version" + require_relative "hatchet/eviction_policy" + require_relative "hatchet/concurrency" + require_relative "hatchet/conditions" + require_relative "hatchet/condition_converter" + require_relative "hatchet/rate_limit" + require_relative "hatchet/labels" + require_relative "hatchet/trigger_options" + require_relative "hatchet/default_filter" + require_relative "hatchet/workflow_run" + require_relative "hatchet/context" + require_relative "hatchet/durable_context" + require_relative "hatchet/task" + require_relative "hatchet/workflow" + require_relative "hatchet/context_vars" + require_relative "hatchet/worker_obj" + + require_relative "hatchet/connection" + + $LOAD_PATH.unshift(File.join(__dir__, "hatchet", "contracts")) unless $LOAD_PATH.include?(File.join(__dir__, "hatchet", "contracts")) + require_relative "hatchet/contracts/dispatcher/dispatcher_pb" + require_relative "hatchet/contracts/dispatcher/dispatcher_services_pb" + require_relative "hatchet/contracts/events/events_pb" + require_relative "hatchet/contracts/events/events_services_pb" + require_relative "hatchet/contracts/workflows/workflows_pb" + require_relative "hatchet/contracts/workflows/workflows_services_pb" + require_relative "hatchet/contracts/v1/shared/condition_pb" + require_relative "hatchet/contracts/v1/shared/trigger_pb" + require_relative "hatchet/contracts/v1/dispatcher_pb" + require_relative "hatchet/contracts/v1/dispatcher_services_pb" + require_relative "hatchet/contracts/v1/workflows_pb" + require_relative "hatchet/contracts/v1/workflows_services_pb" + + require_relative "hatchet/clients/grpc/dispatcher" + require_relative "hatchet/clients/grpc/admin" + require_relative "hatchet/clients/grpc/event_client" + + require_relative "hatchet/worker/action_listener" + require_relative "hatchet/worker/workflow_run_listener" + require_relative "hatchet/worker/durable_eviction/cache" + require_relative "hatchet/worker/durable_eviction/manager" + require_relative "hatchet/worker/durable_event_listener" + require_relative "hatchet/worker/runner" + # The main client for interacting with Hatchet services. # # @example Basic usage with API token From 21756f7a0d8a74786426778f775a2adf3848462e Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Thu, 4 Jun 2026 11:04:24 -0400 Subject: [PATCH 66/69] chore: fix copilot comments --- frontend/docs/pages/v1/idempotency.mdx | 4 ++-- pkg/repository/sqlcv1/idempotency-keys.sql | 4 ++++ pkg/repository/sqlcv1/idempotency-keys.sql.go | 4 ++++ sdks/python/hatchet_sdk/clients/rest/tenacity_utils.py | 4 ++-- sdks/python/hatchet_sdk/runnables/workflow.py | 2 -- sdks/ruby/src/lib/hatchet/clients/grpc/admin.rb | 4 +++- 6 files changed, 15 insertions(+), 7 deletions(-) diff --git a/frontend/docs/pages/v1/idempotency.mdx b/frontend/docs/pages/v1/idempotency.mdx index ed7d4aab57..b86b1afbbb 100644 --- a/frontend/docs/pages/v1/idempotency.mdx +++ b/frontend/docs/pages/v1/idempotency.mdx @@ -30,7 +30,7 @@ Configuring idempotency on a workflow or standalone task requires two parameters -When idempotency is configured, only one run of a workflow will occur in the time window from when the first trigger comes in until the TTL expires. For instance, if you trigger a workflow at `00:00:00 UTC` (midnight) with a TTL of five minutes, and then you the same workflow is triggered again with the same inputs and metadata at `00:02:00 UTC`, `00:04:00 UTC`, and `00:06:00 UTC`, only the first one (at midnight) and the final one (at `00:06:00 UTC`) will run, and then after the second run occurs, the lock on the key will be held until `00:11:00 UTC` (five minutes after the final run was triggered). +When idempotency is configured, only one run of a workflow will occur in the time window from when the first trigger comes in until the TTL expires. For instance, if you trigger a workflow at `00:00:00 UTC` (midnight) with a TTL of five minutes, and then the same workflow is triggered again with the same inputs and metadata at `00:02:00 UTC`, `00:04:00 UTC`, and `00:06:00 UTC`, only the first one (at midnight) and the final one (at `00:06:00 UTC`) will run, and then after the second run occurs, the lock on the key will be held until `00:11:00 UTC` (five minutes after the final run was triggered). ## Handling Collisions @@ -51,4 +51,4 @@ When a collision occurs, the engine will reject the workflow run, and, if the ru -In other cases, such as triggering by events, the idempotency collision will be swallowed, and no additional runs will be created by the event will still be ingested correctly without an error being raised. +In other cases, such as triggering by events, the idempotency collision will be swallowed, and no additional runs will be created, but the event will still be ingested correctly without an error being raised. diff --git a/pkg/repository/sqlcv1/idempotency-keys.sql b/pkg/repository/sqlcv1/idempotency-keys.sql index a0cb23b88f..f4196f262d 100644 --- a/pkg/repository/sqlcv1/idempotency-keys.sql +++ b/pkg/repository/sqlcv1/idempotency-keys.sql @@ -45,6 +45,10 @@ WITH inputs AS ( claimed_by_external_id = CASE WHEN (v1_idempotency_key.tenant_id, v1_idempotency_key.key) IN (SELECT tenant_id, key FROM claimable_keys) THEN EXCLUDED.claimed_by_external_id ELSE v1_idempotency_key.claimed_by_external_id + END, + updated_at = CASE + WHEN (v1_idempotency_key.tenant_id, v1_idempotency_key.key) IN (SELECT tenant_id, key FROM claimable_keys) THEN NOW() + ELSE v1_idempotency_key.updated_at END RETURNING * ) diff --git a/pkg/repository/sqlcv1/idempotency-keys.sql.go b/pkg/repository/sqlcv1/idempotency-keys.sql.go index df5329d16c..e807e954eb 100644 --- a/pkg/repository/sqlcv1/idempotency-keys.sql.go +++ b/pkg/repository/sqlcv1/idempotency-keys.sql.go @@ -52,6 +52,10 @@ WITH inputs AS ( claimed_by_external_id = CASE WHEN (v1_idempotency_key.tenant_id, v1_idempotency_key.key) IN (SELECT tenant_id, key FROM claimable_keys) THEN EXCLUDED.claimed_by_external_id ELSE v1_idempotency_key.claimed_by_external_id + END, + updated_at = CASE + WHEN (v1_idempotency_key.tenant_id, v1_idempotency_key.key) IN (SELECT tenant_id, key FROM claimable_keys) THEN NOW() + ELSE v1_idempotency_key.updated_at END RETURNING tenant_id, key, expires_at, claimed_by_external_id, inserted_at, updated_at ) diff --git a/sdks/python/hatchet_sdk/clients/rest/tenacity_utils.py b/sdks/python/hatchet_sdk/clients/rest/tenacity_utils.py index cfefcfe72a..2add2d11cb 100644 --- a/sdks/python/hatchet_sdk/clients/rest/tenacity_utils.py +++ b/sdks/python/hatchet_sdk/clients/rest/tenacity_utils.py @@ -40,14 +40,14 @@ def tenacity_should_retry( ex: BaseException, config: TenacityConfig | None = None ) -> bool: """Return True when the exception should be retried.""" - if isinstance(ex, ServiceException | NotFoundException): + if isinstance(ex, (ServiceException, NotFoundException)): return True if isinstance(ex, TooManyRequestsException): return bool(config and config.retry_429) # gRPC errors: retry most, except specific permanent failure codes - if isinstance(ex, grpc.aio.AioRpcError | grpc.RpcError): + if isinstance(ex, (grpc.aio.AioRpcError, grpc.RpcError)): non_retryable = [ grpc.StatusCode.UNIMPLEMENTED, grpc.StatusCode.INVALID_ARGUMENT, diff --git a/sdks/python/hatchet_sdk/runnables/workflow.py b/sdks/python/hatchet_sdk/runnables/workflow.py index 5118275549..da9eae1c3a 100644 --- a/sdks/python/hatchet_sdk/runnables/workflow.py +++ b/sdks/python/hatchet_sdk/runnables/workflow.py @@ -246,8 +246,6 @@ def to_proto(self) -> CreateWorkflowVersionRequest: except Exception: json_schema = None - # print("idempotency key expression:", self._config.idempotency_key_expression) - return CreateWorkflowVersionRequest( name=name, description=self._config.description, diff --git a/sdks/ruby/src/lib/hatchet/clients/grpc/admin.rb b/sdks/ruby/src/lib/hatchet/clients/grpc/admin.rb index f2b5fcdd39..daead5c810 100644 --- a/sdks/ruby/src/lib/hatchet/clients/grpc/admin.rb +++ b/sdks/ruby/src/lib/hatchet/clients/grpc/admin.rb @@ -95,7 +95,9 @@ def trigger_workflow(workflow_name, input: {}, options: {}) response.workflow_run_id rescue ::GRPC::AlreadyExists => e run_id = extract_idempotency_run_id(e) - raise(run_id ? IdempotencyCollisionError.new(run_id) : DedupeViolationError, "Deduplication violation: #{e.message}") + raise(IdempotencyCollisionError, run_id) if run_id + + raise DedupeViolationError, "Deduplication violation: #{e.message}" rescue ::GRPC::ResourceExhausted => e raise ResourceExhaustedError, e.message rescue ::GRPC::BadStatus => e From 1362a026c3cc19c8f869b101a91985f6d4a44c5a Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Thu, 2 Jul 2026 17:46:44 -0400 Subject: [PATCH 67/69] chore: gen --- frontend/docs/pages/reference/changelog/python.mdx | 6 ++++++ frontend/docs/pages/reference/changelog/ruby.mdx | 6 ++++++ frontend/docs/pages/reference/changelog/typescript.mdx | 6 ++++++ 3 files changed, 18 insertions(+) diff --git a/frontend/docs/pages/reference/changelog/python.mdx b/frontend/docs/pages/reference/changelog/python.mdx index bfeebd872e..cd9be74e68 100644 --- a/frontend/docs/pages/reference/changelog/python.mdx +++ b/frontend/docs/pages/reference/changelog/python.mdx @@ -1,5 +1,11 @@ {/* AUTOGENERATED — do not edit. Run `task sync-changelog` to regenerate from sdks/python/CHANGELOG.md */} +## v1.34.0 + +### Added + +- Adds support for defining **idempotency keys** on workflows and standalone tasks, which ensures that they're only run once in a provided time window, based on a CEL expression. + ## v1.33.15 - 2026-07-02 ### Added diff --git a/frontend/docs/pages/reference/changelog/ruby.mdx b/frontend/docs/pages/reference/changelog/ruby.mdx index 041a020037..96ddeab82f 100644 --- a/frontend/docs/pages/reference/changelog/ruby.mdx +++ b/frontend/docs/pages/reference/changelog/ruby.mdx @@ -1,5 +1,11 @@ {/* AUTOGENERATED — do not edit. Run `task sync-changelog` to regenerate from sdks/ruby/src/CHANGELOG.md */} +## v0.4.0 - 2026-06-03 + +### Added + +- Adds support for defining **idempotency keys** on workflows and standalone tasks via an `idempotency` option, which ensures that they're only run once in a provided time window, based on a CEL expression. Triggers that collide with an existing run raise an `IdempotencyCollisionError` containing the existing run's ID. + ## v0.3.1 - 2026-06-12 ### Fixed diff --git a/frontend/docs/pages/reference/changelog/typescript.mdx b/frontend/docs/pages/reference/changelog/typescript.mdx index d6c8ac4ee8..8f8752deb1 100644 --- a/frontend/docs/pages/reference/changelog/typescript.mdx +++ b/frontend/docs/pages/reference/changelog/typescript.mdx @@ -1,5 +1,11 @@ {/* AUTOGENERATED — do not edit. Run `task sync-changelog` to regenerate from sdks/typescript/CHANGELOG.md */} +## v1.24.0 - 2026-06-03 + +### Added + +- Adds support for defining **idempotency keys** on workflows and standalone tasks via an `idempotency` option, which ensures that they're only run once in a provided time window, based on a CEL expression. Triggers that collide with an existing run throw an `IdempotencyCollisionError` containing the existing run's ID. + ## v1.24.3 - 2026-06-17 ### Removed From 550a28388a85fc3f952a8d34384a809a21895960 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Thu, 2 Jul 2026 17:47:12 -0400 Subject: [PATCH 68/69] chore: migration version --- .../{20260529180517_v1_0_116.sql => 20260702180517_v1_0_124.sql} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename cmd/hatchet-migrate/migrate/migrations/{20260529180517_v1_0_116.sql => 20260702180517_v1_0_124.sql} (100%) diff --git a/cmd/hatchet-migrate/migrate/migrations/20260529180517_v1_0_116.sql b/cmd/hatchet-migrate/migrate/migrations/20260702180517_v1_0_124.sql similarity index 100% rename from cmd/hatchet-migrate/migrate/migrations/20260529180517_v1_0_116.sql rename to cmd/hatchet-migrate/migrate/migrations/20260702180517_v1_0_124.sql From 77f8518415e3a5a58e8457b0d42f92530b6dfd40 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Wed, 8 Jul 2026 15:40:21 -0400 Subject: [PATCH 69/69] chore: migration version --- .../{20260702180517_v1_0_124.sql => 20260708180517_v1_0_126.sql} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename cmd/hatchet-migrate/migrate/migrations/{20260702180517_v1_0_124.sql => 20260708180517_v1_0_126.sql} (100%) diff --git a/cmd/hatchet-migrate/migrate/migrations/20260702180517_v1_0_124.sql b/cmd/hatchet-migrate/migrate/migrations/20260708180517_v1_0_126.sql similarity index 100% rename from cmd/hatchet-migrate/migrate/migrations/20260702180517_v1_0_124.sql rename to cmd/hatchet-migrate/migrate/migrations/20260708180517_v1_0_126.sql