diff --git a/api-contracts/v1/workflows.proto b/api-contracts/v1/workflows.proto index 5fad08f5d3..851e5f0b34 100644 --- a/api-contracts/v1/workflows.proto +++ b/api-contracts/v1/workflows.proto @@ -47,6 +47,8 @@ message TriggerWorkflowRunRequest { bytes input = 2; bytes additional_metadata = 3; optional int32 priority = 4; + // (optional) idempotency key for deduplicating workflow runs + optional string idempotency_key = 5; } message TriggerWorkflowRunResponse { diff --git a/api-contracts/workflows/workflows.proto b/api-contracts/workflows/workflows.proto index 20106468ca..796b80b7fc 100644 --- a/api-contracts/workflows/workflows.proto +++ b/api-contracts/workflows/workflows.proto @@ -230,6 +230,9 @@ message TriggerWorkflowRequest { // (optional) override for the priority of the workflow tasks, will set all tasks to this priority optional int32 priority = 9; + + // (optional) idempotency key for deduplicating workflow runs + optional string idempotency_key = 10; } message TriggerWorkflowResponse { diff --git a/cmd/hatchet-migrate/migrate/migrations/20260205120000_v1_0_76.sql b/cmd/hatchet-migrate/migrate/migrations/20260205120000_v1_0_76.sql new file mode 100644 index 0000000000..a40f499ca0 --- /dev/null +++ b/cmd/hatchet-migrate/migrate/migrations/20260205120000_v1_0_76.sql @@ -0,0 +1,11 @@ +-- +goose Up +-- +goose StatementBegin +ALTER TABLE v1_idempotency_key + ADD COLUMN last_denied_at TIMESTAMPTZ; +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +ALTER TABLE v1_idempotency_key + DROP COLUMN last_denied_at; +-- +goose StatementEnd diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000000..462577601f --- /dev/null +++ b/flake.lock @@ -0,0 +1,78 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1751274312, + "narHash": "sha256-/bVBlRpECLVzjV19t5KMdMFWSwKLtb5RyXdjz3LJT+g=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "50ab793786d9de88ee30ec4e4c24fb4236fc2674", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-24.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-unstable": { + "locked": { + "lastModified": 1770197578, + "narHash": "sha256-AYqlWrX09+HvGs8zM6ebZ1pwUqjkfpnv8mewYwAo+iM=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "00c21e4c93d963c50d4c0c89bfa84ed6e0694df2", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "nixpkgs-unstable": "nixpkgs-unstable" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000000..ba3f49cdb6 --- /dev/null +++ b/flake.nix @@ -0,0 +1,38 @@ +{ + description = "Hatchet development shell"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11"; + nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, nixpkgs-unstable, flake-utils }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { inherit system; }; + pkgsUnstable = import nixpkgs-unstable { inherit system; }; + go = pkgs.go; + in + { + devShells.default = pkgs.mkShell { + packages = [ + go + pkgs.gopls + pkgs.gotools + pkgsUnstable.golangci-lint + pkgs.go-task + pkgs.protobuf + pkgs.protoc-gen-go + pkgs.protoc-gen-go-grpc + pkgs.nodejs_20 + pkgs.pnpm + pkgs.python312 + pkgs.poetry + pkgs.gcc + pkgs.pkg-config + pkgs.git + ]; + }; + }); +} diff --git a/internal/services/admin/contracts/workflows.pb.go b/internal/services/admin/contracts/workflows.pb.go index 88c022d1a1..5d77d49480 100644 --- a/internal/services/admin/contracts/workflows.pb.go +++ b/internal/services/admin/contracts/workflows.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.28.1 -// protoc v5.29.3 +// protoc v6.32.1 // source: workflows.proto package contracts @@ -1474,6 +1474,8 @@ type TriggerWorkflowRequest struct { DesiredWorkerId *string `protobuf:"bytes,8,opt,name=desired_worker_id,json=desiredWorkerId,proto3,oneof" json:"desired_worker_id,omitempty"` // (optional) override for the priority of the workflow tasks, will set all tasks to this priority Priority *int32 `protobuf:"varint,9,opt,name=priority,proto3,oneof" json:"priority,omitempty"` + // (optional) idempotency key for deduplicating workflow runs + IdempotencyKey *string `protobuf:"bytes,10,opt,name=idempotency_key,json=idempotencyKey,proto3,oneof" json:"idempotency_key,omitempty"` } func (x *TriggerWorkflowRequest) Reset() { @@ -1571,6 +1573,13 @@ func (x *TriggerWorkflowRequest) GetPriority() int32 { return 0 } +func (x *TriggerWorkflowRequest) GetIdempotencyKey() string { + if x != nil && x.IdempotencyKey != nil { + return *x.IdempotencyKey + } + return "" +} + type TriggerWorkflowResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1954,7 +1963,7 @@ var file_workflows_proto_rawDesc = []byte{ 0x6f, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x75, - 0x6e, 0x49, 0x64, 0x73, 0x22, 0xfe, 0x03, 0x0a, 0x16, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, + 0x6e, 0x49, 0x64, 0x73, 0x22, 0xc0, 0x04, 0x0a, 0x16, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 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, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, @@ -1977,83 +1986,87 @@ var file_workflows_proto_rawDesc = []byte{ 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x48, 0x05, 0x52, 0x0f, 0x64, 0x65, 0x73, 0x69, 0x72, 0x65, 0x64, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, 0x64, 0x88, 0x01, 0x01, 0x12, 0x1f, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x48, - 0x06, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x88, 0x01, 0x01, 0x42, 0x0c, - 0x0a, 0x0a, 0x5f, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x42, 0x1e, 0x0a, 0x1c, - 0x5f, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x72, 0x75, 0x6e, - 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x42, 0x0e, 0x0a, 0x0c, - 0x5f, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x42, 0x0c, 0x0a, 0x0a, - 0x5f, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x42, 0x16, 0x0a, 0x14, 0x5f, 0x61, - 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x42, 0x14, 0x0a, 0x12, 0x5f, 0x64, 0x65, 0x73, 0x69, 0x72, 0x65, 0x64, 0x5f, 0x77, - 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x70, 0x72, 0x69, - 0x6f, 0x72, 0x69, 0x74, 0x79, 0x22, 0x41, 0x0a, 0x17, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, - 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x26, 0x0a, 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x72, 0x75, 0x6e, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x66, - 0x6c, 0x6f, 0x77, 0x52, 0x75, 0x6e, 0x49, 0x64, 0x22, 0x6d, 0x0a, 0x13, 0x50, 0x75, 0x74, 0x52, - 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, - 0x79, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x2e, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x12, 0x2e, 0x52, 0x61, 0x74, 0x65, - 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x64, - 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x16, 0x0a, 0x14, 0x50, 0x75, 0x74, 0x52, 0x61, - 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 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, 0x32, 0x0a, 0x0c, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, - 0x77, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x0c, 0x0a, 0x08, 0x46, 0x55, 0x4e, 0x43, 0x54, 0x49, 0x4f, - 0x4e, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x55, 0x52, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x01, - 0x12, 0x07, 0x0a, 0x03, 0x44, 0x41, 0x47, 0x10, 0x02, 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, 0x2a, 0x85, 0x01, 0x0a, 0x15, 0x57, - 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, - 0x61, 0x74, 0x6f, 0x72, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x51, 0x55, 0x41, 0x4c, 0x10, 0x00, 0x12, - 0x0d, 0x0a, 0x09, 0x4e, 0x4f, 0x54, 0x5f, 0x45, 0x51, 0x55, 0x41, 0x4c, 0x10, 0x01, 0x12, 0x10, - 0x0a, 0x0c, 0x47, 0x52, 0x45, 0x41, 0x54, 0x45, 0x52, 0x5f, 0x54, 0x48, 0x41, 0x4e, 0x10, 0x02, - 0x12, 0x19, 0x0a, 0x15, 0x47, 0x52, 0x45, 0x41, 0x54, 0x45, 0x52, 0x5f, 0x54, 0x48, 0x41, 0x4e, - 0x5f, 0x4f, 0x52, 0x5f, 0x45, 0x51, 0x55, 0x41, 0x4c, 0x10, 0x03, 0x12, 0x0d, 0x0a, 0x09, 0x4c, - 0x45, 0x53, 0x53, 0x5f, 0x54, 0x48, 0x41, 0x4e, 0x10, 0x04, 0x12, 0x16, 0x0a, 0x12, 0x4c, 0x45, - 0x53, 0x53, 0x5f, 0x54, 0x48, 0x41, 0x4e, 0x5f, 0x4f, 0x52, 0x5f, 0x45, 0x51, 0x55, 0x41, 0x4c, - 0x10, 0x05, 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, 0x32, 0xdc, 0x02, 0x0a, 0x0f, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x53, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x0b, 0x50, 0x75, 0x74, 0x57, 0x6f, 0x72, 0x6b, - 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x13, 0x2e, 0x50, 0x75, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, - 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e, 0x57, 0x6f, 0x72, 0x6b, - 0x66, 0x6c, 0x6f, 0x77, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x3e, 0x0a, 0x10, 0x53, - 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x12, - 0x18, 0x2e, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, - 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e, 0x57, 0x6f, 0x72, 0x6b, - 0x66, 0x6c, 0x6f, 0x77, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x44, 0x0a, 0x0f, 0x54, - 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x17, - 0x2e, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, - 0x72, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x50, 0x0a, 0x13, 0x42, 0x75, 0x6c, 0x6b, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, - 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x1b, 0x2e, 0x42, 0x75, 0x6c, 0x6b, 0x54, - 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x42, 0x75, 0x6c, 0x6b, 0x54, 0x72, 0x69, 0x67, + 0x06, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x88, 0x01, 0x01, 0x12, 0x2c, + 0x0a, 0x0f, 0x69, 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6b, 0x65, + 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x48, 0x07, 0x52, 0x0e, 0x69, 0x64, 0x65, 0x6d, 0x70, + 0x6f, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x4b, 0x65, 0x79, 0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, + 0x5f, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x42, 0x1e, 0x0a, 0x1c, 0x5f, 0x70, + 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x65, + 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x63, + 0x68, 0x69, 0x6c, 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x63, + 0x68, 0x69, 0x6c, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x42, 0x16, 0x0a, 0x14, 0x5f, 0x61, 0x64, 0x64, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x42, 0x14, 0x0a, 0x12, 0x5f, 0x64, 0x65, 0x73, 0x69, 0x72, 0x65, 0x64, 0x5f, 0x77, 0x6f, 0x72, + 0x6b, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x70, 0x72, 0x69, 0x6f, 0x72, + 0x69, 0x74, 0x79, 0x42, 0x12, 0x0a, 0x10, 0x5f, 0x69, 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, 0x65, + 0x6e, 0x63, 0x79, 0x5f, 0x6b, 0x65, 0x79, 0x22, 0x41, 0x0a, 0x17, 0x54, 0x72, 0x69, 0x67, 0x67, + 0x65, 0x72, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x72, + 0x75, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x77, 0x6f, 0x72, + 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x75, 0x6e, 0x49, 0x64, 0x22, 0x6d, 0x0a, 0x13, 0x50, 0x75, + 0x74, 0x52, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x2e, 0x0a, 0x08, 0x64, 0x75, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x12, 0x2e, 0x52, 0x61, + 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x16, 0x0a, 0x14, 0x50, 0x75, 0x74, + 0x52, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 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, 0x32, 0x0a, 0x0c, 0x57, 0x6f, 0x72, 0x6b, 0x66, + 0x6c, 0x6f, 0x77, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x0c, 0x0a, 0x08, 0x46, 0x55, 0x4e, 0x43, 0x54, + 0x49, 0x4f, 0x4e, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x55, 0x52, 0x41, 0x42, 0x4c, 0x45, + 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x44, 0x41, 0x47, 0x10, 0x02, 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, 0x2a, 0x85, 0x01, 0x0a, + 0x15, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x43, 0x6f, 0x6d, 0x70, + 0x61, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x51, 0x55, 0x41, 0x4c, 0x10, + 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x4e, 0x4f, 0x54, 0x5f, 0x45, 0x51, 0x55, 0x41, 0x4c, 0x10, 0x01, + 0x12, 0x10, 0x0a, 0x0c, 0x47, 0x52, 0x45, 0x41, 0x54, 0x45, 0x52, 0x5f, 0x54, 0x48, 0x41, 0x4e, + 0x10, 0x02, 0x12, 0x19, 0x0a, 0x15, 0x47, 0x52, 0x45, 0x41, 0x54, 0x45, 0x52, 0x5f, 0x54, 0x48, + 0x41, 0x4e, 0x5f, 0x4f, 0x52, 0x5f, 0x45, 0x51, 0x55, 0x41, 0x4c, 0x10, 0x03, 0x12, 0x0d, 0x0a, + 0x09, 0x4c, 0x45, 0x53, 0x53, 0x5f, 0x54, 0x48, 0x41, 0x4e, 0x10, 0x04, 0x12, 0x16, 0x0a, 0x12, + 0x4c, 0x45, 0x53, 0x53, 0x5f, 0x54, 0x48, 0x41, 0x4e, 0x5f, 0x4f, 0x52, 0x5f, 0x45, 0x51, 0x55, + 0x41, 0x4c, 0x10, 0x05, 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, 0x32, 0xdc, 0x02, 0x0a, 0x0f, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x0b, 0x50, 0x75, 0x74, 0x57, 0x6f, + 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x13, 0x2e, 0x50, 0x75, 0x74, 0x57, 0x6f, 0x72, 0x6b, + 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e, 0x57, 0x6f, + 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x3e, 0x0a, + 0x10, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, + 0x77, 0x12, 0x18, 0x2e, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x57, 0x6f, 0x72, 0x6b, + 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e, 0x57, 0x6f, + 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x44, 0x0a, + 0x0f, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, + 0x12, 0x17, 0x2e, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, + 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x0c, 0x50, 0x75, 0x74, 0x52, 0x61, 0x74, 0x65, 0x4c, 0x69, - 0x6d, 0x69, 0x74, 0x12, 0x14, 0x2e, 0x50, 0x75, 0x74, 0x52, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, - 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x50, 0x75, 0x74, 0x52, - 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 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, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, - 0x61, 0x63, 0x74, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x13, 0x42, 0x75, 0x6c, 0x6b, 0x54, 0x72, 0x69, 0x67, 0x67, + 0x65, 0x72, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x1b, 0x2e, 0x42, 0x75, 0x6c, + 0x6b, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x42, 0x75, 0x6c, 0x6b, 0x54, 0x72, + 0x69, 0x67, 0x67, 0x65, 0x72, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x0c, 0x50, 0x75, 0x74, 0x52, 0x61, 0x74, 0x65, + 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x14, 0x2e, 0x50, 0x75, 0x74, 0x52, 0x61, 0x74, 0x65, 0x4c, + 0x69, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x50, 0x75, + 0x74, 0x52, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 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, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, + 0x74, 0x72, 0x61, 0x63, 0x74, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/internal/services/admin/contracts/workflows_grpc.pb.go b/internal/services/admin/contracts/workflows_grpc.pb.go index 932e7ec85a..9855f32786 100644 --- a/internal/services/admin/contracts/workflows_grpc.pb.go +++ b/internal/services/admin/contracts/workflows_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.2.0 -// - protoc v5.29.3 +// - protoc v6.32.1 // source: workflows.proto package contracts diff --git a/internal/services/admin/server_v1.go b/internal/services/admin/server_v1.go index 844b604128..879fb2d82d 100644 --- a/internal/services/admin/server_v1.go +++ b/internal/services/admin/server_v1.go @@ -239,9 +239,16 @@ func (i *AdminServiceImpl) newTriggerOpt( t.ChildKey = req.ChildKey } - return &v1.WorkflowNameTriggerOpts{ + opts := &v1.WorkflowNameTriggerOpts{ TriggerTaskData: t, - }, nil + } + + if req.IdempotencyKey != nil { + key := v1.IdempotencyKey(*req.IdempotencyKey) + opts.IdempotencyKey = &key + } + + return opts, nil } func (i *AdminServiceImpl) generateExternalIds(ctx context.Context, tenantId uuid.UUID, opts []*v1.WorkflowNameTriggerOpts) error { @@ -274,6 +281,10 @@ func (i *AdminServiceImpl) ingest(ctx context.Context, tenantId uuid.UUID, opts // if we have a scheduling error, we'll fall back to normal ingestion if schedulingErr != nil { + if errors.Is(schedulingErr, v1.ErrIdempotencyKeyAlreadyClaimed) { + return status.Error(codes.AlreadyExists, schedulingErr.Error()) + } + if !errors.Is(schedulingErr, schedulingv1.ErrTenantNotFound) && !errors.Is(schedulingErr, schedulingv1.ErrNoOptimisticSlots) { i.l.Error().Err(schedulingErr).Msg("could not run optimistic scheduling") } @@ -313,8 +324,14 @@ func (i *AdminServiceImpl) ingest(ctx context.Context, tenantId uuid.UUID, opts 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().Err(triggerErr).Msg("could not trigger workflow runs via gRPC") + if triggerErr != nil { + if errors.Is(triggerErr, v1.ErrIdempotencyKeyAlreadyClaimed) { + return status.Error(codes.AlreadyExists, triggerErr.Error()) + } + + if !errors.Is(triggerErr, trigger.ErrNoTriggerSlots) { + i.l.Error().Err(triggerErr).Msg("could not trigger workflow runs via gRPC") + } } else if triggerErr == nil { return nil } diff --git a/internal/services/admin/v1/server.go b/internal/services/admin/v1/server.go index e7487491ef..a36f739eb8 100644 --- a/internal/services/admin/v1/server.go +++ b/internal/services/admin/v1/server.go @@ -504,9 +504,16 @@ func (i *AdminServiceImpl) newTriggerOpt( t.Priority = req.Priority } - return &v1.WorkflowNameTriggerOpts{ + opts := &v1.WorkflowNameTriggerOpts{ TriggerTaskData: t, - }, nil + } + + if req.IdempotencyKey != nil { + key := v1.IdempotencyKey(*req.IdempotencyKey) + opts.IdempotencyKey = &key + } + + return opts, nil } func (i *AdminServiceImpl) generateExternalIds(ctx context.Context, tenantId uuid.UUID, opts []*v1.WorkflowNameTriggerOpts) error { @@ -539,6 +546,10 @@ func (i *AdminServiceImpl) ingest(ctx context.Context, tenantId uuid.UUID, opts // if we have a scheduling error, we'll fall back to normal ingestion if schedulingErr != nil { + if errors.Is(schedulingErr, v1.ErrIdempotencyKeyAlreadyClaimed) { + return status.Error(codes.AlreadyExists, schedulingErr.Error()) + } + if !errors.Is(schedulingErr, schedulingv1.ErrTenantNotFound) && !errors.Is(schedulingErr, schedulingv1.ErrNoOptimisticSlots) { i.l.Error().Err(schedulingErr).Msg("could not run optimistic scheduling") } @@ -578,8 +589,14 @@ func (i *AdminServiceImpl) ingest(ctx context.Context, tenantId uuid.UUID, opts 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().Err(triggerErr).Msg("could not trigger workflow runs via gRPC") + if triggerErr != nil { + if errors.Is(triggerErr, v1.ErrIdempotencyKeyAlreadyClaimed) { + return status.Error(codes.AlreadyExists, triggerErr.Error()) + } + + if !errors.Is(triggerErr, trigger.ErrNoTriggerSlots) { + i.l.Error().Err(triggerErr).Msg("could not trigger workflow runs via gRPC") + } } else if triggerErr == nil { return nil } diff --git a/internal/services/controllers/olap/process_dag_status_updates.go b/internal/services/controllers/olap/process_dag_status_updates.go index 5ed405dbd6..f62117f247 100644 --- a/internal/services/controllers/olap/process_dag_status_updates.go +++ b/internal/services/controllers/olap/process_dag_status_updates.go @@ -60,6 +60,14 @@ func (o *OLAPControllerImpl) notifyDAGsUpdated(ctx context.Context, rows []v1.Up tenantIdToPayloads := make(map[uuid.UUID][]tasktypes.NotifyFinalizedPayload) for _, row := range rows { + if row.ReadableStatus == sqlcv1.V1ReadableStatusOlapCOMPLETED || + row.ReadableStatus == sqlcv1.V1ReadableStatusOlapCANCELLED || + row.ReadableStatus == sqlcv1.V1ReadableStatusOlapFAILED { + if err := o.repo.Idempotency().DeleteIdempotencyKeysByExternalId(ctx, row.TenantId, row.ExternalId); err != nil { + o.l.Error().Err(err).Str("dagExternalId", row.ExternalId.String()).Msg("failed to delete idempotency key for dag") + } + } + tenantIdToPayloads[row.TenantId] = append(tenantIdToPayloads[row.TenantId], tasktypes.NotifyFinalizedPayload{ ExternalId: row.ExternalId, Status: row.ReadableStatus, diff --git a/internal/services/controllers/olap/process_task_status_updates.go b/internal/services/controllers/olap/process_task_status_updates.go index 71b5b7cc59..b30cefbde6 100644 --- a/internal/services/controllers/olap/process_task_status_updates.go +++ b/internal/services/controllers/olap/process_task_status_updates.go @@ -63,6 +63,12 @@ func (o *OLAPControllerImpl) notifyTasksUpdated(ctx context.Context, rows []v1.U continue } + if !row.IsDAGTask { + if err := o.repo.Idempotency().DeleteIdempotencyKeysByExternalId(ctx, row.TenantId, row.ExternalId); err != nil { + o.l.Error().Err(err).Str("taskExternalId", row.ExternalId.String()).Msg("failed to delete idempotency key for task") + } + } + tenantIdToPayloads[row.TenantId] = append(tenantIdToPayloads[row.TenantId], tasktypes.NotifyFinalizedPayload{ ExternalId: row.ExternalId, Status: row.ReadableStatus, diff --git a/internal/services/controllers/task/controller.go b/internal/services/controllers/task/controller.go index 4a9aef6cbd..11148b4f81 100644 --- a/internal/services/controllers/task/controller.go +++ b/internal/services/controllers/task/controller.go @@ -1047,7 +1047,14 @@ 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)) + + if errors.Is(err, v1.ErrIdempotencyKeyAlreadyClaimed) { + tc.l.Debug().Err(err).Msg("skipping workflow trigger with duplicate idempotency key") + return nil + } + + return err } // processUserEventMatches looks for user event matches diff --git a/internal/services/shared/proto/v1/workflows.pb.go b/internal/services/shared/proto/v1/workflows.pb.go index 8644e08398..7409060820 100644 --- a/internal/services/shared/proto/v1/workflows.pb.go +++ b/internal/services/shared/proto/v1/workflows.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.28.1 -// protoc v5.29.3 +// protoc v6.32.1 // source: v1/workflows.proto package v1 @@ -588,6 +588,8 @@ type TriggerWorkflowRunRequest struct { Input []byte `protobuf:"bytes,2,opt,name=input,proto3" json:"input,omitempty"` AdditionalMetadata []byte `protobuf:"bytes,3,opt,name=additional_metadata,json=additionalMetadata,proto3" json:"additional_metadata,omitempty"` Priority *int32 `protobuf:"varint,4,opt,name=priority,proto3,oneof" json:"priority,omitempty"` + // (optional) idempotency key for deduplicating workflow runs + IdempotencyKey *string `protobuf:"bytes,5,opt,name=idempotency_key,json=idempotencyKey,proto3,oneof" json:"idempotency_key,omitempty"` } func (x *TriggerWorkflowRunRequest) Reset() { @@ -650,6 +652,13 @@ func (x *TriggerWorkflowRunRequest) GetPriority() int32 { return 0 } +func (x *TriggerWorkflowRunRequest) GetIdempotencyKey() string { + if x != nil && x.IdempotencyKey != nil { + return *x.IdempotencyKey + } + return "" +} + type TriggerWorkflowRunResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1604,7 +1613,7 @@ var file_v1_workflows_proto_rawDesc = []byte{ 0x65, 0x70, 0x6c, 0x61, 0x79, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x64, 0x5f, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x70, 0x6c, - 0x61, 0x79, 0x65, 0x64, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x22, 0xb5, 0x01, 0x0a, 0x19, 0x54, 0x72, + 0x61, 0x79, 0x65, 0x64, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x22, 0xf7, 0x01, 0x0a, 0x19, 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, 0x12, 0x23, 0x0a, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, @@ -1615,258 +1624,263 @@ var file_v1_workflows_proto_rawDesc = []byte{ 0x12, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1f, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, - 0x79, 0x88, 0x01, 0x01, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, - 0x79, 0x22, 0x3d, 0x0a, 0x1a, 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, - 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, 0xdd, 0x05, 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, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x72, 0x69, 0x67, 0x67, - 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x65, 0x76, 0x65, 0x6e, 0x74, - 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x72, 0x6f, 0x6e, - 0x5f, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x0c, 0x63, 0x72, 0x6f, 0x6e, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x73, 0x12, 0x28, 0x0a, - 0x05, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x76, - 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x4f, 0x70, 0x74, 0x73, - 0x52, 0x05, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x12, 0x31, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, - 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x07, 0x20, 0x01, 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, 0x22, 0x0a, 0x0a, 0x63, 0x72, - 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, - 0x52, 0x09, 0x63, 0x72, 0x6f, 0x6e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x88, 0x01, 0x01, 0x12, 0x3f, - 0x0a, 0x0f, 0x6f, 0x6e, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x74, 0x61, 0x73, - 0x6b, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x4f, 0x70, 0x74, 0x73, 0x48, 0x01, 0x52, 0x0d, 0x6f, - 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x88, 0x01, 0x01, 0x12, - 0x2f, 0x0a, 0x06, 0x73, 0x74, 0x69, 0x63, 0x6b, 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0e, 0x32, - 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x69, 0x63, 0x6b, 0x79, 0x53, 0x74, 0x72, 0x61, 0x74, - 0x65, 0x67, 0x79, 0x48, 0x02, 0x52, 0x06, 0x73, 0x74, 0x69, 0x63, 0x6b, 0x79, 0x88, 0x01, 0x01, - 0x12, 0x2e, 0x0a, 0x10, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x70, 0x72, 0x69, 0x6f, - 0x72, 0x69, 0x74, 0x79, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x48, 0x03, 0x52, 0x0f, 0x64, 0x65, - 0x66, 0x61, 0x75, 0x6c, 0x74, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x88, 0x01, 0x01, - 0x12, 0x38, 0x0a, 0x0f, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, - 0x61, 0x72, 0x72, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x76, 0x31, 0x2e, 0x43, - 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x0e, 0x63, 0x6f, 0x6e, 0x63, - 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x41, 0x72, 0x72, 0x12, 0x3a, 0x0a, 0x0f, 0x64, 0x65, - 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x18, 0x0d, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, - 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x0e, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x46, - 0x69, 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, 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, 0x9a, 0x02, 0x0a, - 0x13, 0x44, 0x65, 0x73, 0x69, 0x72, 0x65, 0x64, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4c, 0x61, - 0x62, 0x65, 0x6c, 0x73, 0x12, 0x20, 0x0a, 0x09, 0x73, 0x74, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x08, 0x73, 0x74, 0x72, 0x56, 0x61, - 0x6c, 0x75, 0x65, 0x88, 0x01, 0x01, 0x12, 0x20, 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x5f, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x48, 0x01, 0x52, 0x08, 0x69, 0x6e, 0x74, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x88, 0x01, 0x01, 0x12, 0x1f, 0x0a, 0x08, 0x72, 0x65, 0x71, 0x75, - 0x69, 0x72, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x48, 0x02, 0x52, 0x08, 0x72, 0x65, - 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x88, 0x01, 0x01, 0x12, 0x3e, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, - 0x70, 0x61, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, - 0x76, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x43, 0x6f, - 0x6d, 0x70, 0x61, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x48, 0x03, 0x52, 0x0a, 0x63, 0x6f, 0x6d, 0x70, - 0x61, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x88, 0x01, 0x01, 0x12, 0x1b, 0x0a, 0x06, 0x77, 0x65, 0x69, - 0x67, 0x68, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x48, 0x04, 0x52, 0x06, 0x77, 0x65, 0x69, - 0x67, 0x68, 0x74, 0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x73, 0x74, 0x72, 0x5f, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x69, 0x6e, 0x74, 0x5f, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x42, - 0x0d, 0x0a, 0x0b, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x42, 0x09, - 0x0a, 0x07, 0x5f, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0xda, 0x05, 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, 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, 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, 0xc5, 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, 0x42, - 0x08, 0x0a, 0x06, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x6f, 0x75, - 0x74, 0x70, 0x75, 0x74, 0x22, 0xaf, 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, 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, 0x4e, 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, 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, 0x2a, 0x85, 0x01, 0x0a, - 0x15, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x43, 0x6f, 0x6d, 0x70, - 0x61, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x51, 0x55, 0x41, 0x4c, 0x10, - 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x4e, 0x4f, 0x54, 0x5f, 0x45, 0x51, 0x55, 0x41, 0x4c, 0x10, 0x01, - 0x12, 0x10, 0x0a, 0x0c, 0x47, 0x52, 0x45, 0x41, 0x54, 0x45, 0x52, 0x5f, 0x54, 0x48, 0x41, 0x4e, - 0x10, 0x02, 0x12, 0x19, 0x0a, 0x15, 0x47, 0x52, 0x45, 0x41, 0x54, 0x45, 0x52, 0x5f, 0x54, 0x48, - 0x41, 0x4e, 0x5f, 0x4f, 0x52, 0x5f, 0x45, 0x51, 0x55, 0x41, 0x4c, 0x10, 0x03, 0x12, 0x0d, 0x0a, - 0x09, 0x4c, 0x45, 0x53, 0x53, 0x5f, 0x54, 0x48, 0x41, 0x4e, 0x10, 0x04, 0x12, 0x16, 0x0a, 0x12, - 0x4c, 0x45, 0x53, 0x53, 0x5f, 0x54, 0x48, 0x41, 0x4e, 0x5f, 0x4f, 0x52, 0x5f, 0x45, 0x51, 0x55, - 0x41, 0x4c, 0x10, 0x05, 0x32, 0xfd, 0x02, 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, + 0x79, 0x88, 0x01, 0x01, 0x12, 0x2c, 0x0a, 0x0f, 0x69, 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, 0x65, + 0x6e, 0x63, 0x79, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, + 0x0e, 0x69, 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x4b, 0x65, 0x79, 0x88, + 0x01, 0x01, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x42, + 0x12, 0x0a, 0x10, 0x5f, 0x69, 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x5f, + 0x6b, 0x65, 0x79, 0x22, 0x3d, 0x0a, 0x1a, 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, 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, 0xdd, 0x05, 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, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x72, 0x69, + 0x67, 0x67, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x65, 0x76, 0x65, + 0x6e, 0x74, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x72, + 0x6f, 0x6e, 0x5f, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x0c, 0x63, 0x72, 0x6f, 0x6e, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x73, 0x12, + 0x28, 0x0a, 0x05, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, + 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x4f, 0x70, + 0x74, 0x73, 0x52, 0x05, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x12, 0x31, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, + 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x07, 0x20, 0x01, 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, 0x22, 0x0a, 0x0a, + 0x63, 0x72, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, + 0x48, 0x00, 0x52, 0x09, 0x63, 0x72, 0x6f, 0x6e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x88, 0x01, 0x01, + 0x12, 0x3f, 0x0a, 0x0f, 0x6f, 0x6e, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x74, + 0x61, 0x73, 0x6b, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x4f, 0x70, 0x74, 0x73, 0x48, 0x01, 0x52, + 0x0d, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x88, 0x01, + 0x01, 0x12, 0x2f, 0x0a, 0x06, 0x73, 0x74, 0x69, 0x63, 0x6b, 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28, + 0x0e, 0x32, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x69, 0x63, 0x6b, 0x79, 0x53, 0x74, 0x72, + 0x61, 0x74, 0x65, 0x67, 0x79, 0x48, 0x02, 0x52, 0x06, 0x73, 0x74, 0x69, 0x63, 0x6b, 0x79, 0x88, + 0x01, 0x01, 0x12, 0x2e, 0x0a, 0x10, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x70, 0x72, + 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x48, 0x03, 0x52, 0x0f, + 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x88, + 0x01, 0x01, 0x12, 0x38, 0x0a, 0x0f, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, + 0x79, 0x5f, 0x61, 0x72, 0x72, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x76, 0x31, + 0x2e, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x0e, 0x63, 0x6f, + 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x41, 0x72, 0x72, 0x12, 0x3a, 0x0a, 0x0f, + 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x18, + 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x66, 0x61, 0x75, + 0x6c, 0x74, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x0e, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, + 0x74, 0x46, 0x69, 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, 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, 0x9a, + 0x02, 0x0a, 0x13, 0x44, 0x65, 0x73, 0x69, 0x72, 0x65, 0x64, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, + 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x20, 0x0a, 0x09, 0x73, 0x74, 0x72, 0x5f, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x08, 0x73, 0x74, 0x72, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x88, 0x01, 0x01, 0x12, 0x20, 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x5f, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x48, 0x01, 0x52, 0x08, 0x69, + 0x6e, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x88, 0x01, 0x01, 0x12, 0x1f, 0x0a, 0x08, 0x72, 0x65, + 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x48, 0x02, 0x52, 0x08, + 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x88, 0x01, 0x01, 0x12, 0x3e, 0x0a, 0x0a, 0x63, + 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x19, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4c, 0x61, 0x62, 0x65, 0x6c, + 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x48, 0x03, 0x52, 0x0a, 0x63, 0x6f, + 0x6d, 0x70, 0x61, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x88, 0x01, 0x01, 0x12, 0x1b, 0x0a, 0x06, 0x77, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x48, 0x04, 0x52, 0x06, 0x77, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x73, 0x74, 0x72, + 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x69, 0x6e, 0x74, 0x5f, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, + 0x64, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x61, 0x74, 0x6f, 0x72, + 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0xda, 0x05, 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, 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, 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, 0xc5, + 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, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x42, 0x09, 0x0a, 0x07, 0x5f, + 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0xaf, 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, 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, 0x4e, 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, 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, 0x2a, 0x85, + 0x01, 0x0a, 0x15, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x43, 0x6f, + 0x6d, 0x70, 0x61, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x51, 0x55, 0x41, + 0x4c, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x4e, 0x4f, 0x54, 0x5f, 0x45, 0x51, 0x55, 0x41, 0x4c, + 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x47, 0x52, 0x45, 0x41, 0x54, 0x45, 0x52, 0x5f, 0x54, 0x48, + 0x41, 0x4e, 0x10, 0x02, 0x12, 0x19, 0x0a, 0x15, 0x47, 0x52, 0x45, 0x41, 0x54, 0x45, 0x52, 0x5f, + 0x54, 0x48, 0x41, 0x4e, 0x5f, 0x4f, 0x52, 0x5f, 0x45, 0x51, 0x55, 0x41, 0x4c, 0x10, 0x03, 0x12, + 0x0d, 0x0a, 0x09, 0x4c, 0x45, 0x53, 0x53, 0x5f, 0x54, 0x48, 0x41, 0x4e, 0x10, 0x04, 0x12, 0x16, + 0x0a, 0x12, 0x4c, 0x45, 0x53, 0x53, 0x5f, 0x54, 0x48, 0x41, 0x4e, 0x5f, 0x4f, 0x52, 0x5f, 0x45, + 0x51, 0x55, 0x41, 0x4c, 0x10, 0x05, 0x32, 0xfd, 0x02, 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, 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, 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, + 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, 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 ( diff --git a/internal/services/shared/proto/v1/workflows_grpc.pb.go b/internal/services/shared/proto/v1/workflows_grpc.pb.go index 7fa7b84eca..f62291aae9 100644 --- a/internal/services/shared/proto/v1/workflows_grpc.pb.go +++ b/internal/services/shared/proto/v1/workflows_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.2.0 -// - protoc v5.29.3 +// - protoc v6.32.1 // source: v1/workflows.proto package v1 diff --git a/pkg/client/admin.go b/pkg/client/admin.go index 1a5fa58682..08efa687a8 100644 --- a/pkg/client/admin.go +++ b/pkg/client/admin.go @@ -231,6 +231,14 @@ func WithPriority(priority int32) RunOptFunc { } } +func WithIdempotencyKey(key string) RunOptFunc { + return func(r *admincontracts.TriggerWorkflowRequest) error { + r.IdempotencyKey = &key + + return nil + } +} + // func WithSticky(sticky bool) RunOptFunc { // return func(r *admincontracts.TriggerWorkflowRequest) error { // r.Sticky = &sticky diff --git a/pkg/config/loader/loader.go b/pkg/config/loader/loader.go index 54729dcaf4..ccbb80d7d4 100644 --- a/pkg/config/loader/loader.go +++ b/pkg/config/loader/loader.go @@ -301,6 +301,8 @@ func (c *ConfigLoader) InitDataLayer() (res *database.Layer, err error) { scf.Runtime.EnforceLimits, scf.Runtime.EnforceLimitsFunc, scf.Runtime.EnableDurableUserEventLog, + scf.Runtime.IdempotencyKeyTTL, + scf.Runtime.IdempotencyKeyDenyRecheckInterval, ) if readReplicaPool != nil { diff --git a/pkg/config/server/server.go b/pkg/config/server/server.go index c580df2a06..05e360642d 100644 --- a/pkg/config/server/server.go +++ b/pkg/config/server/server.go @@ -301,6 +301,14 @@ type ConfigFileRuntime struct { // to the core database, we only use them to trigger workflows. Enabling this will persist them to the core database. EnableDurableUserEventLog bool `mapstructure:"enableDurableUserEventLog" json:"enableDurableUserEventLog,omitempty" default:"false"` + // IdempotencyKeyTTL controls how long idempotency keys are retained as a safety net. Keys are deleted on completion, + // but this TTL limits how long a stuck key can block requeues. + IdempotencyKeyTTL time.Duration `mapstructure:"idempotencyKeyTTL" json:"idempotencyKeyTTL,omitempty" default:"24h"` + + // IdempotencyKeyDenyRecheckInterval controls how often we recheck terminal status for a claimed key + // before denying a duplicate enqueue. + IdempotencyKeyDenyRecheckInterval time.Duration `mapstructure:"idempotencyKeyDenyRecheckInterval" json:"idempotencyKeyDenyRecheckInterval,omitempty" default:"5m"` + // WorkflowRunBufferSize is the buffer size for workflow run event batching in the dispatcher WorkflowRunBufferSize int `mapstructure:"workflowRunBufferSize" json:"workflowRunBufferSize,omitempty" default:"1000"` } @@ -812,6 +820,8 @@ func BindAllEnv(v *viper.Viper) { // enable durable user event log _ = v.BindEnv("runtime.enableDurableUserEventLog", "SERVER_ENABLE_DURABLE_USER_EVENT_LOG") + _ = v.BindEnv("runtime.idempotencyKeyTTL", "SERVER_IDEMPOTENCY_KEY_TTL") + _ = v.BindEnv("runtime.idempotencyKeyDenyRecheckInterval", "SERVER_IDEMPOTENCY_KEY_DENY_RECHECK_INTERVAL") // internal client options _ = v.BindEnv("internalClient.base.tlsStrategy", "SERVER_INTERNAL_CLIENT_BASE_STRATEGY") diff --git a/pkg/repository/idempotency.go b/pkg/repository/idempotency.go index 6323986037..3c6dc0f3c4 100644 --- a/pkg/repository/idempotency.go +++ b/pkg/repository/idempotency.go @@ -14,6 +14,8 @@ type IdempotencyKey string type IdempotencyRepository interface { CreateIdempotencyKey(context context.Context, tenantId uuid.UUID, key string, expiresAt pgtype.Timestamptz) error + CreateIdempotencyKeys(context context.Context, tenantId uuid.UUID, keys []string, expiresAt pgtype.Timestamptz) error + DeleteIdempotencyKeysByExternalId(context context.Context, tenantId uuid.UUID, externalId uuid.UUID) error EvictExpiredIdempotencyKeys(context context.Context, tenantId uuid.UUID) error } @@ -35,6 +37,25 @@ func (r *idempotencyRepository) CreateIdempotencyKey(context context.Context, te }) } +func (r *idempotencyRepository) CreateIdempotencyKeys(context context.Context, tenantId uuid.UUID, keys []string, expiresAt pgtype.Timestamptz) error { + if len(keys) == 0 { + return nil + } + + return r.queries.CreateIdempotencyKeys(context, r.pool, sqlcv1.CreateIdempotencyKeysParams{ + Tenantid: tenantId, + Keys: keys, + Expiresat: expiresAt, + }) +} + +func (r *idempotencyRepository) DeleteIdempotencyKeysByExternalId(context context.Context, tenantId uuid.UUID, externalId uuid.UUID) error { + return r.queries.DeleteIdempotencyKeysByExternalId(context, r.pool, sqlcv1.DeleteIdempotencyKeysByExternalIdParams{ + Tenantid: tenantId, + Externalid: externalId, + }) +} + func (r *idempotencyRepository) EvictExpiredIdempotencyKeys(context context.Context, tenantId uuid.UUID) error { return r.queries.CleanUpExpiredIdempotencyKeys(context, r.pool, tenantId) } diff --git a/pkg/repository/idempotency_trigger_test.go b/pkg/repository/idempotency_trigger_test.go new file mode 100644 index 0000000000..6ce03f242e --- /dev/null +++ b/pkg/repository/idempotency_trigger_test.go @@ -0,0 +1,851 @@ +//go:build !e2e && !load && !rampup && !integration + +package repository + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/google/uuid" + "github.com/jackc/pgx/v5/pgtype" + "github.com/jackc/pgx/v5/pgxpool" + "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/hatchet-dev/hatchet/pkg/config/limits" + "github.com/hatchet-dev/hatchet/pkg/repository/sqlcv1" +) + +func setupRepositoryWithTTL(t *testing.T, pool *pgxpool.Pool, ttl time.Duration, recheckInterval time.Duration) (Repository, func()) { + t.Helper() + + logger := zerolog.Nop() + inlineStoreTTL := 24 * time.Hour + payloadStoreOpts := PayloadStoreRepositoryOpts{ + InlineStoreTTL: &inlineStoreTTL, + } + statusUpdateLimits := StatusUpdateBatchSizeLimits{ + Task: 1000, + DAG: 1000, + } + + repo, cleanup := NewRepository( + pool, + &logger, + 0, + 24*time.Hour, + 24*time.Hour, + 10, + TaskOperationLimits{}, + payloadStoreOpts, + statusUpdateLimits, + limits.LimitConfigFile{}, + false, + nil, + false, + ttl, + recheckInterval, + ) + + return repo, func() { + _ = cleanup() + } +} + +func setupTenant(t *testing.T, repo Repository, pool *pgxpool.Pool) uuid.UUID { + t.Helper() + ctx := context.Background() + queries := sqlcv1.New() + + _, err := queries.CreateControllerPartition(ctx, pool, pgtype.Text{String: "test", Valid: true}) + require.NoError(t, err) + + tenantId := uuid.New() + _, err = repo.Tenant().CreateTenant(ctx, &CreateTenantOpts{ + ID: &tenantId, + Name: "test-tenant", + Slug: "test-tenant-" + tenantId.String(), + }) + require.NoError(t, err) + + return tenantId +} + +type minimalWorkflow struct { + WorkflowID uuid.UUID + WorkflowVersionID uuid.UUID +} + +func createMinimalWorkflow(t *testing.T, pool *pgxpool.Pool, tenantId uuid.UUID, workflowName string) minimalWorkflow { + t.Helper() + ctx := context.Background() + queries := sqlcv1.New() + + workflowId := uuid.New() + _, err := queries.CreateWorkflow(ctx, pool, sqlcv1.CreateWorkflowParams{ + ID: workflowId, + CreatedAt: pgtype.Timestamp{}, + UpdatedAt: pgtype.Timestamp{}, + Deletedat: pgtype.Timestamp{}, + Tenantid: tenantId, + Name: workflowName, + Description: "test workflow", + }) + require.NoError(t, err) + + workflowVersionId := uuid.New() + _, err = queries.CreateWorkflowVersion(ctx, pool, sqlcv1.CreateWorkflowVersionParams{ + ID: workflowVersionId, + CreatedAt: pgtype.Timestamp{}, + UpdatedAt: pgtype.Timestamp{}, + Deletedat: pgtype.Timestamp{}, + Checksum: "test-checksum", + Version: pgtype.Text{String: "v1", Valid: true}, + Workflowid: workflowId, + Sticky: sqlcv1.NullStickyStrategy{}, + Kind: sqlcv1.NullWorkflowKind{}, + DefaultPriority: pgtype.Int4{}, + CreateWorkflowVersionOpts: []byte("{}"), + InputJsonSchema: []byte("{}"), + }) + require.NoError(t, err) + + jobId := uuid.New() + _, err = queries.CreateJob(ctx, pool, sqlcv1.CreateJobParams{ + ID: jobId, + CreatedAt: pgtype.Timestamp{}, + UpdatedAt: pgtype.Timestamp{}, + Deletedat: pgtype.Timestamp{}, + Tenantid: tenantId, + Workflowversionid: workflowVersionId, + Name: "job", + Description: "job description", + Kind: sqlcv1.NullJobKind{}, + }) + require.NoError(t, err) + + _, err = queries.UpsertAction(ctx, pool, sqlcv1.UpsertActionParams{ + Action: "test.action", + Tenantid: tenantId, + }) + require.NoError(t, err) + + _, err = queries.CreateStep(ctx, pool, sqlcv1.CreateStepParams{ + ID: uuid.New(), + CreatedAt: pgtype.Timestamp{}, + UpdatedAt: pgtype.Timestamp{}, + Deletedat: pgtype.Timestamp{}, + Readableid: "step-1", + Tenantid: tenantId, + Jobid: jobId, + Actionid: "test.action", + Timeout: pgtype.Text{}, + CustomUserData: []byte("{}"), + Retries: pgtype.Int4{}, + ScheduleTimeout: pgtype.Text{}, + }) + require.NoError(t, err) + + return minimalWorkflow{ + WorkflowID: workflowId, + WorkflowVersionID: workflowVersionId, + } +} + +func insertRunStatus(t *testing.T, pool *pgxpool.Pool, tenantId uuid.UUID, wf minimalWorkflow, externalId uuid.UUID, status sqlcv1.V1ReadableStatusOlap) { + t.Helper() + ctx := context.Background() + + insertedAt := time.Now().UTC() + runId := time.Now().UnixNano() + + _, err := pool.Exec(ctx, ` + INSERT INTO v1_runs_olap ( + tenant_id, + id, + inserted_at, + external_id, + readable_status, + kind, + workflow_id, + workflow_version_id, + additional_metadata, + parent_task_external_id + ) VALUES ($1, $2, $3, $4, $5, 'TASK', $6, $7, NULL, NULL) + `, tenantId, runId, insertedAt, externalId, status, wf.WorkflowID, wf.WorkflowVersionID) + require.NoError(t, err) + + _, err = pool.Exec(ctx, ` + INSERT INTO v1_lookup_table_olap ( + tenant_id, + external_id, + task_id, + dag_id, + inserted_at + ) VALUES ($1, $2, $3, NULL, $4) + `, tenantId, externalId, runId, insertedAt) + require.NoError(t, err) +} + +func insertTerminalTaskEventsForRun(t *testing.T, pool *pgxpool.Pool, tenantId uuid.UUID, externalId uuid.UUID) { + t.Helper() + ctx := context.Background() + + rows, err := pool.Query(ctx, ` + WITH lookup AS ( + SELECT external_id, dag_id, task_id, inserted_at + FROM v1_lookup_table + WHERE tenant_id = $1 AND external_id = $2 + ), dag_tasks AS ( + SELECT t.id, t.inserted_at, t.retry_count + FROM lookup l + JOIN v1_dag_to_task dt ON dt.dag_id = l.dag_id AND dt.dag_inserted_at = l.inserted_at + JOIN v1_task t ON t.id = dt.task_id AND t.inserted_at = dt.task_inserted_at + WHERE l.dag_id IS NOT NULL + ), task_only AS ( + SELECT t.id, t.inserted_at, t.retry_count + FROM lookup l + JOIN v1_task t ON t.id = l.task_id AND t.inserted_at = l.inserted_at + WHERE l.task_id IS NOT NULL + ) + SELECT id, inserted_at, retry_count FROM dag_tasks + UNION ALL + SELECT id, inserted_at, retry_count FROM task_only + `, tenantId, externalId) + require.NoError(t, err) + defer rows.Close() + + taskCount := 0 + for rows.Next() { + var ( + taskId int64 + taskInserted time.Time + retryCount int32 + ) + require.NoError(t, rows.Scan(&taskId, &taskInserted, &retryCount)) + taskCount++ + + _, err = pool.Exec(ctx, ` + INSERT INTO v1_task_event (tenant_id, task_id, task_inserted_at, retry_count, event_type) + VALUES ($1, $2, $3, $4, 'COMPLETED') + `, tenantId, taskId, taskInserted, retryCount) + require.NoError(t, err) + } + require.NoError(t, rows.Err()) + require.Greater(t, taskCount, 0) +} + +func getLastDeniedAt(t *testing.T, pool *pgxpool.Pool, tenantId uuid.UUID, key IdempotencyKey) pgtype.Timestamptz { + t.Helper() + ctx := context.Background() + + var lastDeniedAt pgtype.Timestamptz + err := pool.QueryRow(ctx, ` + SELECT last_denied_at + FROM v1_idempotency_key + WHERE tenant_id = $1 AND key = $2 + `, tenantId, string(key)).Scan(&lastDeniedAt) + require.NoError(t, err) + return lastDeniedAt +} + +func getClaimedByExternalId(t *testing.T, pool *pgxpool.Pool, tenantId uuid.UUID, key IdempotencyKey) *uuid.UUID { + t.Helper() + ctx := context.Background() + + var claimedByExternalId *uuid.UUID + err := pool.QueryRow(ctx, ` + SELECT claimed_by_external_id + FROM v1_idempotency_key + WHERE tenant_id = $1 AND key = $2 + `, tenantId, string(key)).Scan(&claimedByExternalId) + require.NoError(t, err) + return claimedByExternalId +} + +func TestTriggerWorkflowIdempotency_DedupesActiveRun(t *testing.T) { + // Ensures a duplicate idempotency key is rejected while an active run holds the claim. + pool, cleanup := setupPostgresWithMigration(t) + defer cleanup() + + repo, cleanupRepo := setupRepositoryWithTTL(t, pool, 2*time.Hour, 5*time.Minute) + defer cleanupRepo() + + tenantId := setupTenant(t, repo, pool) + _ = createMinimalWorkflow(t, pool, tenantId, "test-workflow") + + ctx := context.Background() + key := IdempotencyKey("idem-key") + + opts1 := &WorkflowNameTriggerOpts{ + TriggerTaskData: &TriggerTaskData{ + WorkflowName: "test-workflow", + }, + ExternalId: uuid.New(), + IdempotencyKey: &key, + } + + _, _, err := repo.Triggers().TriggerFromWorkflowNames(ctx, tenantId, []*WorkflowNameTriggerOpts{opts1}) + require.NoError(t, err) + + opts2 := &WorkflowNameTriggerOpts{ + TriggerTaskData: &TriggerTaskData{ + WorkflowName: "test-workflow", + }, + ExternalId: uuid.New(), + IdempotencyKey: &key, + } + + _, _, err = repo.Triggers().TriggerFromWorkflowNames(ctx, tenantId, []*WorkflowNameTriggerOpts{opts2}) + require.Error(t, err) + assert.ErrorIs(t, err, ErrIdempotencyKeyAlreadyClaimed) +} + +func TestTriggerWorkflowIdempotency_AllowsAfterRelease(t *testing.T) { + // Shows that once a key is released (deleted), the same key can be used again. + pool, cleanup := setupPostgresWithMigration(t) + defer cleanup() + + repo, cleanupRepo := setupRepositoryWithTTL(t, pool, 30*time.Minute, 5*time.Minute) + defer cleanupRepo() + + tenantId := setupTenant(t, repo, pool) + _ = createMinimalWorkflow(t, pool, tenantId, "test-workflow") + + ctx := context.Background() + key := IdempotencyKey("idem-key-release") + + first := &WorkflowNameTriggerOpts{ + TriggerTaskData: &TriggerTaskData{ + WorkflowName: "test-workflow", + }, + ExternalId: uuid.New(), + IdempotencyKey: &key, + } + + _, _, err := repo.Triggers().TriggerFromWorkflowNames(ctx, tenantId, []*WorkflowNameTriggerOpts{first}) + require.NoError(t, err) + + err = repo.Idempotency().DeleteIdempotencyKeysByExternalId(ctx, tenantId, first.ExternalId) + require.NoError(t, err) + + second := &WorkflowNameTriggerOpts{ + TriggerTaskData: &TriggerTaskData{ + WorkflowName: "test-workflow", + }, + ExternalId: uuid.New(), + IdempotencyKey: &key, + } + + _, _, err = repo.Triggers().TriggerFromWorkflowNames(ctx, tenantId, []*WorkflowNameTriggerOpts{second}) + require.NoError(t, err) +} + +func TestTriggerWorkflowIdempotency_UsesConfiguredTTL(t *testing.T) { + // Validates that the configured TTL is applied to the stored idempotency key. + pool, cleanup := setupPostgresWithMigration(t) + defer cleanup() + + ttl := 90 * time.Minute + repo, cleanupRepo := setupRepositoryWithTTL(t, pool, ttl, 5*time.Minute) + defer cleanupRepo() + + tenantId := setupTenant(t, repo, pool) + _ = createMinimalWorkflow(t, pool, tenantId, "test-workflow") + + ctx := context.Background() + key := IdempotencyKey("idem-key-ttl") + + opts := &WorkflowNameTriggerOpts{ + TriggerTaskData: &TriggerTaskData{ + WorkflowName: "test-workflow", + }, + ExternalId: uuid.New(), + IdempotencyKey: &key, + } + + _, _, err := repo.Triggers().TriggerFromWorkflowNames(ctx, tenantId, []*WorkflowNameTriggerOpts{opts}) + require.NoError(t, err) + + var expiresAt time.Time + err = pool.QueryRow(ctx, + `SELECT expires_at FROM v1_idempotency_key WHERE tenant_id = $1 AND key = $2`, + tenantId, + string(key), + ).Scan(&expiresAt) + require.NoError(t, err) + + expected := time.Now().Add(ttl) + assert.WithinDuration(t, expected, expiresAt, 2*time.Minute) +} + +func TestTriggerWorkflowIdempotency_ExpiredUnclaimedKeyIsReusable(t *testing.T) { + // An expired, unclaimed key should be refreshed and claimed instead of blocking. + pool, cleanup := setupPostgresWithMigration(t) + defer cleanup() + + repo, cleanupRepo := setupRepositoryWithTTL(t, pool, 30*time.Minute, 1*time.Minute) + defer cleanupRepo() + + tenantId := setupTenant(t, repo, pool) + _ = createMinimalWorkflow(t, pool, tenantId, "test-workflow") + + ctx := context.Background() + key := IdempotencyKey("idem-key-expired") + + _, err := pool.Exec(ctx, + `INSERT INTO v1_idempotency_key (tenant_id, key, expires_at) VALUES ($1, $2, $3)`, + tenantId, + string(key), + time.Now().Add(-1*time.Hour), + ) + require.NoError(t, err) + + opts := &WorkflowNameTriggerOpts{ + TriggerTaskData: &TriggerTaskData{ + WorkflowName: "test-workflow", + }, + ExternalId: uuid.New(), + IdempotencyKey: &key, + } + + _, _, err = repo.Triggers().TriggerFromWorkflowNames(ctx, tenantId, []*WorkflowNameTriggerOpts{opts}) + require.NoError(t, err) + + claimed := getClaimedByExternalId(t, pool, tenantId, key) + require.NotNil(t, claimed) + assert.Equal(t, opts.ExternalId, *claimed) + + var expiresAt time.Time + err = pool.QueryRow(ctx, + `SELECT expires_at FROM v1_idempotency_key WHERE tenant_id = $1 AND key = $2`, + tenantId, + string(key), + ).Scan(&expiresAt) + require.NoError(t, err) + assert.True(t, expiresAt.After(time.Now())) +} + +func TestTriggerWorkflowIdempotency_ReclaimsAfterTerminalStatus(t *testing.T) { + // When OLAP reports terminal status, a previously claimed key is reclaimed and a new run is allowed. + pool, cleanup := setupPostgresWithMigration(t) + defer cleanup() + + repo, cleanupRepo := setupRepositoryWithTTL(t, pool, 30*time.Minute, 1*time.Minute) + defer cleanupRepo() + + tenantId := setupTenant(t, repo, pool) + wf := createMinimalWorkflow(t, pool, tenantId, "test-workflow") + + ctx := context.Background() + key := IdempotencyKey("idem-key-reclaim") + + first := &WorkflowNameTriggerOpts{ + TriggerTaskData: &TriggerTaskData{ + WorkflowName: "test-workflow", + }, + ExternalId: uuid.New(), + IdempotencyKey: &key, + } + + _, _, err := repo.Triggers().TriggerFromWorkflowNames(ctx, tenantId, []*WorkflowNameTriggerOpts{first}) + require.NoError(t, err) + + insertRunStatus(t, pool, tenantId, wf, first.ExternalId, sqlcv1.V1ReadableStatusOlapFAILED) + + second := &WorkflowNameTriggerOpts{ + TriggerTaskData: &TriggerTaskData{ + WorkflowName: "test-workflow", + }, + ExternalId: uuid.New(), + IdempotencyKey: &key, + } + + _, _, err = repo.Triggers().TriggerFromWorkflowNames(ctx, tenantId, []*WorkflowNameTriggerOpts{second}) + require.NoError(t, err) +} + +func TestTriggerWorkflowIdempotency_ReclaimDoesNotUpdateLastDeniedAt(t *testing.T) { + // Reclaimed keys should not have last_denied_at set since the enqueue succeeds. + pool, cleanup := setupPostgresWithMigration(t) + defer cleanup() + + repo, cleanupRepo := setupRepositoryWithTTL(t, pool, 30*time.Minute, 1*time.Minute) + defer cleanupRepo() + + tenantId := setupTenant(t, repo, pool) + wf := createMinimalWorkflow(t, pool, tenantId, "test-workflow") + + ctx := context.Background() + key := IdempotencyKey("idem-key-reclaim-denied") + + first := &WorkflowNameTriggerOpts{ + TriggerTaskData: &TriggerTaskData{ + WorkflowName: "test-workflow", + }, + ExternalId: uuid.New(), + IdempotencyKey: &key, + } + + _, _, err := repo.Triggers().TriggerFromWorkflowNames(ctx, tenantId, []*WorkflowNameTriggerOpts{first}) + require.NoError(t, err) + + insertRunStatus(t, pool, tenantId, wf, first.ExternalId, sqlcv1.V1ReadableStatusOlapFAILED) + + second := &WorkflowNameTriggerOpts{ + TriggerTaskData: &TriggerTaskData{ + WorkflowName: "test-workflow", + }, + ExternalId: uuid.New(), + IdempotencyKey: &key, + } + + _, _, err = repo.Triggers().TriggerFromWorkflowNames(ctx, tenantId, []*WorkflowNameTriggerOpts{second}) + require.NoError(t, err) + + lastDeniedAt := getLastDeniedAt(t, pool, tenantId, key) + assert.False(t, lastDeniedAt.Valid) +} + +func TestTriggerWorkflowIdempotency_MixedKeysAllowsPartial(t *testing.T) { + // Mixed batch: one reclaimable, one running. We should allow the reclaimable run and skip the duplicate. + pool, cleanup := setupPostgresWithMigration(t) + defer cleanup() + + repo, cleanupRepo := setupRepositoryWithTTL(t, pool, 30*time.Minute, 1*time.Minute) + defer cleanupRepo() + + tenantId := setupTenant(t, repo, pool) + wf := createMinimalWorkflow(t, pool, tenantId, "test-workflow") + + ctx := context.Background() + keyTerminal := IdempotencyKey("idem-key-terminal") + keyRunning := IdempotencyKey("idem-key-running") + + terminalFirst := &WorkflowNameTriggerOpts{ + TriggerTaskData: &TriggerTaskData{ + WorkflowName: "test-workflow", + }, + ExternalId: uuid.New(), + IdempotencyKey: &keyTerminal, + } + _, _, err := repo.Triggers().TriggerFromWorkflowNames(ctx, tenantId, []*WorkflowNameTriggerOpts{terminalFirst}) + require.NoError(t, err) + insertRunStatus(t, pool, tenantId, wf, terminalFirst.ExternalId, sqlcv1.V1ReadableStatusOlapFAILED) + + runningFirst := &WorkflowNameTriggerOpts{ + TriggerTaskData: &TriggerTaskData{ + WorkflowName: "test-workflow", + }, + ExternalId: uuid.New(), + IdempotencyKey: &keyRunning, + } + _, _, err = repo.Triggers().TriggerFromWorkflowNames(ctx, tenantId, []*WorkflowNameTriggerOpts{runningFirst}) + require.NoError(t, err) + insertRunStatus(t, pool, tenantId, wf, runningFirst.ExternalId, sqlcv1.V1ReadableStatusOlapRUNNING) + + terminalSecond := &WorkflowNameTriggerOpts{ + TriggerTaskData: &TriggerTaskData{ + WorkflowName: "test-workflow", + }, + ExternalId: uuid.New(), + IdempotencyKey: &keyTerminal, + } + runningSecond := &WorkflowNameTriggerOpts{ + TriggerTaskData: &TriggerTaskData{ + WorkflowName: "test-workflow", + }, + ExternalId: uuid.New(), + IdempotencyKey: &keyRunning, + } + + _, _, err = repo.Triggers().TriggerFromWorkflowNames(ctx, tenantId, []*WorkflowNameTriggerOpts{terminalSecond, runningSecond}) + require.NoError(t, err) + + claimedTerminal := getClaimedByExternalId(t, pool, tenantId, keyTerminal) + require.NotNil(t, claimedTerminal) + assert.Equal(t, terminalSecond.ExternalId, *claimedTerminal) + + claimedRunning := getClaimedByExternalId(t, pool, tenantId, keyRunning) + require.NotNil(t, claimedRunning) + assert.Equal(t, runningFirst.ExternalId, *claimedRunning) + + lastDeniedAt := getLastDeniedAt(t, pool, tenantId, keyRunning) + assert.True(t, lastDeniedAt.Valid) + assert.WithinDuration(t, time.Now(), lastDeniedAt.Time, 2*time.Minute) +} + +func TestTriggerWorkflowIdempotency_ConcurrentDuplicateKey(t *testing.T) { + // Two concurrent triggers with the same key should yield one success and one duplicate error. + pool, cleanup := setupPostgresWithMigration(t) + defer cleanup() + + repo, cleanupRepo := setupRepositoryWithTTL(t, pool, 30*time.Minute, 1*time.Minute) + defer cleanupRepo() + + tenantId := setupTenant(t, repo, pool) + _ = createMinimalWorkflow(t, pool, tenantId, "test-workflow") + + key := IdempotencyKey("idem-key-concurrent") + + start := make(chan struct{}) + errs := make(chan error, 2) + + run := func(externalId uuid.UUID) { + <-start + opts := &WorkflowNameTriggerOpts{ + TriggerTaskData: &TriggerTaskData{ + WorkflowName: "test-workflow", + }, + ExternalId: externalId, + IdempotencyKey: &key, + } + _, _, err := repo.Triggers().TriggerFromWorkflowNames(context.Background(), tenantId, []*WorkflowNameTriggerOpts{opts}) + errs <- err + } + + go run(uuid.New()) + go run(uuid.New()) + + close(start) + + var ( + successCount int + dupCount int + ) + + for i := 0; i < 2; i++ { + err := <-errs + if err == nil { + successCount++ + continue + } + if errors.Is(err, ErrIdempotencyKeyAlreadyClaimed) { + dupCount++ + } else { + t.Fatalf("unexpected error: %v", err) + } + } + + assert.Equal(t, 1, successCount) + assert.Equal(t, 1, dupCount) +} + +func TestTriggerWorkflowIdempotency_RecheckThrottled(t *testing.T) { + // If last_denied_at is recent, recheck is skipped and the duplicate is denied. + pool, cleanup := setupPostgresWithMigration(t) + defer cleanup() + + recheckInterval := 2 * time.Hour + repo, cleanupRepo := setupRepositoryWithTTL(t, pool, 30*time.Minute, recheckInterval) + defer cleanupRepo() + + tenantId := setupTenant(t, repo, pool) + wf := createMinimalWorkflow(t, pool, tenantId, "test-workflow") + + ctx := context.Background() + key := IdempotencyKey("idem-key-throttle") + + first := &WorkflowNameTriggerOpts{ + TriggerTaskData: &TriggerTaskData{ + WorkflowName: "test-workflow", + }, + ExternalId: uuid.New(), + IdempotencyKey: &key, + } + + _, _, err := repo.Triggers().TriggerFromWorkflowNames(ctx, tenantId, []*WorkflowNameTriggerOpts{first}) + require.NoError(t, err) + + insertRunStatus(t, pool, tenantId, wf, first.ExternalId, sqlcv1.V1ReadableStatusOlapFAILED) + + _, err = pool.Exec(ctx, `UPDATE v1_idempotency_key SET last_denied_at = NOW() WHERE tenant_id = $1 AND key = $2`, tenantId, string(key)) + require.NoError(t, err) + + second := &WorkflowNameTriggerOpts{ + TriggerTaskData: &TriggerTaskData{ + WorkflowName: "test-workflow", + }, + ExternalId: uuid.New(), + IdempotencyKey: &key, + } + + _, _, err = repo.Triggers().TriggerFromWorkflowNames(ctx, tenantId, []*WorkflowNameTriggerOpts{second}) + require.Error(t, err) + assert.ErrorIs(t, err, ErrIdempotencyKeyAlreadyClaimed) +} + +func TestTriggerWorkflowIdempotency_RecheckDisabled(t *testing.T) { + // With recheck disabled, duplicates are denied and last_denied_at remains unset. + pool, cleanup := setupPostgresWithMigration(t) + defer cleanup() + + repo, cleanupRepo := setupRepositoryWithTTL(t, pool, 30*time.Minute, 0) + defer cleanupRepo() + + tenantId := setupTenant(t, repo, pool) + wf := createMinimalWorkflow(t, pool, tenantId, "test-workflow") + + ctx := context.Background() + key := IdempotencyKey("idem-key-no-recheck") + + first := &WorkflowNameTriggerOpts{ + TriggerTaskData: &TriggerTaskData{ + WorkflowName: "test-workflow", + }, + ExternalId: uuid.New(), + IdempotencyKey: &key, + } + + _, _, err := repo.Triggers().TriggerFromWorkflowNames(ctx, tenantId, []*WorkflowNameTriggerOpts{first}) + require.NoError(t, err) + + insertRunStatus(t, pool, tenantId, wf, first.ExternalId, sqlcv1.V1ReadableStatusOlapFAILED) + + second := &WorkflowNameTriggerOpts{ + TriggerTaskData: &TriggerTaskData{ + WorkflowName: "test-workflow", + }, + ExternalId: uuid.New(), + IdempotencyKey: &key, + } + + _, _, err = repo.Triggers().TriggerFromWorkflowNames(ctx, tenantId, []*WorkflowNameTriggerOpts{second}) + require.Error(t, err) + assert.ErrorIs(t, err, ErrIdempotencyKeyAlreadyClaimed) + + lastDeniedAt := getLastDeniedAt(t, pool, tenantId, key) + assert.False(t, lastDeniedAt.Valid) +} + +func TestTriggerWorkflowIdempotency_RecheckMissingOlapUpdatesLastDeniedAt(t *testing.T) { + // If OLAP has no status row, duplicates should be denied and last_denied_at updated. + pool, cleanup := setupPostgresWithMigration(t) + defer cleanup() + + repo, cleanupRepo := setupRepositoryWithTTL(t, pool, 30*time.Minute, 1*time.Minute) + defer cleanupRepo() + + tenantId := setupTenant(t, repo, pool) + _ = createMinimalWorkflow(t, pool, tenantId, "test-workflow") + + ctx := context.Background() + key := IdempotencyKey("idem-key-missing-olap") + + first := &WorkflowNameTriggerOpts{ + TriggerTaskData: &TriggerTaskData{ + WorkflowName: "test-workflow", + }, + ExternalId: uuid.New(), + IdempotencyKey: &key, + } + + _, _, err := repo.Triggers().TriggerFromWorkflowNames(ctx, tenantId, []*WorkflowNameTriggerOpts{first}) + require.NoError(t, err) + + second := &WorkflowNameTriggerOpts{ + TriggerTaskData: &TriggerTaskData{ + WorkflowName: "test-workflow", + }, + ExternalId: uuid.New(), + IdempotencyKey: &key, + } + + _, _, err = repo.Triggers().TriggerFromWorkflowNames(ctx, tenantId, []*WorkflowNameTriggerOpts{second}) + require.Error(t, err) + assert.ErrorIs(t, err, ErrIdempotencyKeyAlreadyClaimed) + + lastDeniedAt := getLastDeniedAt(t, pool, tenantId, key) + assert.True(t, lastDeniedAt.Valid) + assert.WithinDuration(t, time.Now(), lastDeniedAt.Time, 2*time.Minute) +} + +func TestTriggerWorkflowIdempotency_RecheckUsesCoreWhenOlapMissing(t *testing.T) { + // If OLAP is missing but core task events are terminal, we should reclaim and allow a new run. + pool, cleanup := setupPostgresWithMigration(t) + defer cleanup() + + repo, cleanupRepo := setupRepositoryWithTTL(t, pool, 30*time.Minute, 1*time.Minute) + defer cleanupRepo() + + tenantId := setupTenant(t, repo, pool) + _ = createMinimalWorkflow(t, pool, tenantId, "test-workflow") + + ctx := context.Background() + key := IdempotencyKey("idem-key-core-fallback") + + first := &WorkflowNameTriggerOpts{ + TriggerTaskData: &TriggerTaskData{ + WorkflowName: "test-workflow", + }, + ExternalId: uuid.New(), + IdempotencyKey: &key, + } + + _, _, err := repo.Triggers().TriggerFromWorkflowNames(ctx, tenantId, []*WorkflowNameTriggerOpts{first}) + require.NoError(t, err) + + insertTerminalTaskEventsForRun(t, pool, tenantId, first.ExternalId) + + second := &WorkflowNameTriggerOpts{ + TriggerTaskData: &TriggerTaskData{ + WorkflowName: "test-workflow", + }, + ExternalId: uuid.New(), + IdempotencyKey: &key, + } + + _, _, err = repo.Triggers().TriggerFromWorkflowNames(ctx, tenantId, []*WorkflowNameTriggerOpts{second}) + require.NoError(t, err) + + claimed := getClaimedByExternalId(t, pool, tenantId, key) + require.NotNil(t, claimed) + assert.Equal(t, second.ExternalId, *claimed) +} + +func TestTriggerWorkflowIdempotency_RecheckUpdatesLastDeniedAtWhenNonTerminal(t *testing.T) { + // If the run is non-terminal, we deny and update last_denied_at for throttling. + pool, cleanup := setupPostgresWithMigration(t) + defer cleanup() + + repo, cleanupRepo := setupRepositoryWithTTL(t, pool, 30*time.Minute, 1*time.Minute) + defer cleanupRepo() + + tenantId := setupTenant(t, repo, pool) + wf := createMinimalWorkflow(t, pool, tenantId, "test-workflow") + + ctx := context.Background() + key := IdempotencyKey("idem-key-non-terminal") + + first := &WorkflowNameTriggerOpts{ + TriggerTaskData: &TriggerTaskData{ + WorkflowName: "test-workflow", + }, + ExternalId: uuid.New(), + IdempotencyKey: &key, + } + + _, _, err := repo.Triggers().TriggerFromWorkflowNames(ctx, tenantId, []*WorkflowNameTriggerOpts{first}) + require.NoError(t, err) + + insertRunStatus(t, pool, tenantId, wf, first.ExternalId, sqlcv1.V1ReadableStatusOlapRUNNING) + + second := &WorkflowNameTriggerOpts{ + TriggerTaskData: &TriggerTaskData{ + WorkflowName: "test-workflow", + }, + ExternalId: uuid.New(), + IdempotencyKey: &key, + } + + _, _, err = repo.Triggers().TriggerFromWorkflowNames(ctx, tenantId, []*WorkflowNameTriggerOpts{second}) + require.Error(t, err) + assert.ErrorIs(t, err, ErrIdempotencyKeyAlreadyClaimed) + + lastDeniedAt := getLastDeniedAt(t, pool, tenantId, key) + assert.True(t, lastDeniedAt.Valid) + assert.WithinDuration(t, time.Now(), lastDeniedAt.Time, 2*time.Minute) +} diff --git a/pkg/repository/main_test.go b/pkg/repository/main_test.go new file mode 100644 index 0000000000..1f57a53d66 --- /dev/null +++ b/pkg/repository/main_test.go @@ -0,0 +1,29 @@ +//go:build !e2e && !load && !rampup && !integration + +package repository + +import ( + "context" + "os" + "testing" +) + +func TestMain(m *testing.M) { + code := m.Run() + + if testPool != nil { + testPool.Close() + } + + if testContainer != nil { + _ = testContainer.Terminate(context.Background()) + } + + if originalDatabaseURL != "" { + _ = os.Setenv("DATABASE_URL", originalDatabaseURL) + } else { + _ = os.Unsetenv("DATABASE_URL") + } + + os.Exit(code) +} diff --git a/pkg/repository/olap.go b/pkg/repository/olap.go index 35184951ce..49a50f3de2 100644 --- a/pkg/repository/olap.go +++ b/pkg/repository/olap.go @@ -309,7 +309,7 @@ func NewOLAPRepositoryFromPool( ) (OLAPRepository, func() error) { v := validator.NewDefaultValidator() - shared, cleanupShared := newSharedRepository(pool, v, l, payloadStoreOpts, tenantLimitConfig, enforceLimits, enforceLimitsFunc, cacheDuration, enableDurableUserEventLog) + shared, cleanupShared := newSharedRepository(pool, v, l, payloadStoreOpts, tenantLimitConfig, enforceLimits, enforceLimitsFunc, cacheDuration, enableDurableUserEventLog, 0, 0) return newOLAPRepository(shared, olapRetentionPeriod, shouldPartitionEventsTables, statusUpdateBatchSizeLimits), cleanupShared } diff --git a/pkg/repository/repository.go b/pkg/repository/repository.go index 502a373e9c..723eb18b80 100644 --- a/pkg/repository/repository.go +++ b/pkg/repository/repository.go @@ -104,10 +104,12 @@ func NewRepository( enforceLimits bool, enforceLimitsFunc func(ctx context.Context, tenantId string) (bool, error), enableDurableUserEventLog bool, + idempotencyKeyTTL time.Duration, + idempotencyKeyDenyRecheckInterval time.Duration, ) (Repository, func() error) { v := validator.NewDefaultValidator() - shared, cleanupShared := newSharedRepository(pool, v, l, payloadStoreOpts, tenantLimitConfig, enforceLimits, enforceLimitsFunc, cacheDuration, enableDurableUserEventLog) + shared, cleanupShared := newSharedRepository(pool, v, l, payloadStoreOpts, tenantLimitConfig, enforceLimits, enforceLimitsFunc, cacheDuration, enableDurableUserEventLog, idempotencyKeyTTL, idempotencyKeyDenyRecheckInterval) mq, cleanupMq := newMessageQueueRepository(shared) diff --git a/pkg/repository/scheduler_optimistic.go b/pkg/repository/scheduler_optimistic.go index 08ed9b2724..f12b241759 100644 --- a/pkg/repository/scheduler_optimistic.go +++ b/pkg/repository/scheduler_optimistic.go @@ -73,12 +73,46 @@ func (r *optimisticSchedulingRepositoryImpl) TriggerFromEvents(ctx context.Conte } func (r *optimisticSchedulingRepositoryImpl) TriggerFromNames(ctx context.Context, tx *OptimisticTx, tenantId uuid.UUID, opts []*WorkflowNameTriggerOpts) ([]*sqlcv1.V1QueueItem, []*V1TaskWithPayload, []*DAGWithData, error) { - triggerOpts, err := r.prepareTriggerFromWorkflowNames(ctx, tx.tx, tenantId, opts) + triggerOpts, denyUpdateKeys, duplicateKeys, err := r.prepareTriggerFromWorkflowNames(ctx, tx.tx, tenantId, opts) if err != nil { + if errors.Is(err, ErrIdempotencyKeyAlreadyClaimed) && len(denyUpdateKeys) > 0 { + tx.Rollback() + updateErr := r.queries.UpdateIdempotencyKeysLastDeniedAt(ctx, r.pool, sqlcv1.UpdateIdempotencyKeysLastDeniedAtParams{ + Tenantid: tenantId, + Keys: denyUpdateKeys, + }) + if updateErr != nil { + err = errors.Join(err, fmt.Errorf("failed to update idempotency key deny timestamps: %w", updateErr)) + } + } + return nil, nil, nil, fmt.Errorf("failed to prepare trigger from workflow names: %w", err) } + if len(duplicateKeys) > 0 && len(triggerOpts) > 0 { + r.l.Warn(). + Str("tenantId", tenantId.String()). + Int("duplicateKeyCount", len(duplicateKeys)). + Msg("partial idempotency duplicates skipped during optimistic trigger") + } + + if len(denyUpdateKeys) > 0 { + tx.AddPostCommit(func() { + updateErr := r.queries.UpdateIdempotencyKeysLastDeniedAt(ctx, r.pool, sqlcv1.UpdateIdempotencyKeysLastDeniedAtParams{ + Tenantid: tenantId, + Keys: denyUpdateKeys, + }) + if updateErr != nil { + r.l.Error(). + Err(updateErr). + Str("tenantId", tenantId.String()). + Int("keyCount", len(denyUpdateKeys)). + Msg("failed to update idempotency key deny timestamps after optimistic trigger") + } + }) + } + tasks, dags, err := r.triggerWorkflows(ctx, tx, tenantId, triggerOpts, nil) if err != nil { diff --git a/pkg/repository/shared.go b/pkg/repository/shared.go index 7a9d6661bf..7d9caca8da 100644 --- a/pkg/repository/shared.go +++ b/pkg/repository/shared.go @@ -46,7 +46,9 @@ type sharedRepository struct { payloadStore PayloadStoreRepository m TenantLimitRepository - enableDurableUserEventLog bool + enableDurableUserEventLog bool + idempotencyKeyTTL time.Duration + idempotencyKeyDenyRecheckInterval time.Duration } func newSharedRepository( @@ -59,6 +61,8 @@ func newSharedRepository( enforceLimitsFunc func(ctx context.Context, tenantId string) (bool, error), cacheDuration time.Duration, enableDurableUserEventLog bool, + idempotencyKeyTTL time.Duration, + idempotencyKeyDenyRecheckInterval time.Duration, ) (*sharedRepository, func() error) { queries := sqlcv1.New() queueCache := cache.New(5 * time.Minute) @@ -89,21 +93,23 @@ func newSharedRepository( } s := &sharedRepository{ - pool: pool, - v: v, - l: l, - queries: queries, - queueCache: queueCache, - stepExpressionCache: stepExpressionCache, - concurrencyStrategyCache: concurrencyStrategyCache, - tenantIdWorkflowNameCache: tenantIdWorkflowNameCache, - stepsInWorkflowVersionCache: stepsInWorkflowVersionCache, - stepIdLabelsCache: stepIdLabelsCache, - celParser: celParser, - env: env, - taskLookupCache: lookupCache, - payloadStore: payloadStore, - enableDurableUserEventLog: enableDurableUserEventLog, + pool: pool, + v: v, + l: l, + queries: queries, + queueCache: queueCache, + stepExpressionCache: stepExpressionCache, + concurrencyStrategyCache: concurrencyStrategyCache, + tenantIdWorkflowNameCache: tenantIdWorkflowNameCache, + stepsInWorkflowVersionCache: stepsInWorkflowVersionCache, + stepIdLabelsCache: stepIdLabelsCache, + celParser: celParser, + env: env, + taskLookupCache: lookupCache, + payloadStore: payloadStore, + enableDurableUserEventLog: enableDurableUserEventLog, + idempotencyKeyTTL: idempotencyKeyTTL, + idempotencyKeyDenyRecheckInterval: idempotencyKeyDenyRecheckInterval, } tenantLimitRepository := newTenantLimitRepository(s, c, shouldEnforceLimits, enforceLimitsFunc, cacheDuration) diff --git a/pkg/repository/sqlcv1/idempotency-keys.sql b/pkg/repository/sqlcv1/idempotency-keys.sql index d86b710abd..10966e2c7f 100644 --- a/pkg/repository/sqlcv1/idempotency-keys.sql +++ b/pkg/repository/sqlcv1/idempotency-keys.sql @@ -8,7 +8,33 @@ VALUES ( @tenantId::UUID, @key::TEXT, @expiresAt::TIMESTAMPTZ -); +) +ON CONFLICT (tenant_id, key) DO UPDATE +SET + expires_at = EXCLUDED.expires_at, + updated_at = NOW() +WHERE + v1_idempotency_key.claimed_by_external_id IS NULL + AND v1_idempotency_key.expires_at < NOW(); + +-- name: CreateIdempotencyKeys :exec +INSERT INTO v1_idempotency_key ( + tenant_id, + key, + expires_at +) +SELECT + @tenantId::UUID, + k, + @expiresAt::TIMESTAMPTZ +FROM UNNEST(@keys::TEXT[]) AS k +ON CONFLICT (tenant_id, key) DO UPDATE +SET + expires_at = EXCLUDED.expires_at, + updated_at = NOW() +WHERE + v1_idempotency_key.claimed_by_external_id IS NULL + AND v1_idempotency_key.expires_at < NOW(); -- name: CleanUpExpiredIdempotencyKeys :exec DELETE FROM v1_idempotency_key @@ -17,6 +43,59 @@ WHERE AND expires_at < NOW() ; +-- name: DeleteIdempotencyKeysByExternalId :exec +DELETE FROM v1_idempotency_key +WHERE + tenant_id = @tenantId::UUID + AND claimed_by_external_id = @externalId::UUID +; + +-- name: ListIdempotencyKeysByKeys :many +SELECT + tenant_id, + key, + expires_at, + claimed_by_external_id, + last_denied_at, + inserted_at, + updated_at +FROM v1_idempotency_key +WHERE + tenant_id = @tenantId::UUID + AND key = ANY(@keys::TEXT[]) +; + +-- name: UpdateIdempotencyKeysLastDeniedAt :exec +UPDATE v1_idempotency_key +SET + last_denied_at = NOW(), + updated_at = NOW() +WHERE + tenant_id = @tenantId::UUID + AND key = ANY(@keys::TEXT[]) +; + +-- name: UpdateIdempotencyKeysLastDeniedAtSkipLocked :exec +WITH target AS ( + SELECT + tenant_id, + key + FROM v1_idempotency_key + WHERE + tenant_id = @tenantId::UUID + AND key = ANY(@keys::TEXT[]) + FOR UPDATE SKIP LOCKED +) +UPDATE v1_idempotency_key k +SET + last_denied_at = NOW(), + updated_at = NOW() +FROM target t +WHERE + k.tenant_id = t.tenant_id + AND k.key = t.key +; + -- name: ClaimIdempotencyKeys :many WITH inputs AS ( SELECT DISTINCT diff --git a/pkg/repository/sqlcv1/idempotency-keys.sql.go b/pkg/repository/sqlcv1/idempotency-keys.sql.go index 1ef63348e1..e9f4ea36c9 100644 --- a/pkg/repository/sqlcv1/idempotency-keys.sql.go +++ b/pkg/repository/sqlcv1/idempotency-keys.sql.go @@ -55,7 +55,7 @@ WITH inputs AS ( 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 + RETURNING k.tenant_id, k.key, k.expires_at, k.claimed_by_external_id, k.last_denied_at, k.inserted_at, k.updated_at ) SELECT @@ -128,6 +128,13 @@ VALUES ( $2::TEXT, $3::TIMESTAMPTZ ) +ON CONFLICT (tenant_id, key) DO UPDATE +SET + expires_at = EXCLUDED.expires_at, + updated_at = NOW() +WHERE + v1_idempotency_key.claimed_by_external_id IS NULL + AND v1_idempotency_key.expires_at < NOW() ` type CreateIdempotencyKeyParams struct { @@ -140,3 +147,150 @@ func (q *Queries) CreateIdempotencyKey(ctx context.Context, db DBTX, arg CreateI _, err := db.Exec(ctx, createIdempotencyKey, arg.Tenantid, arg.Key, arg.Expiresat) return err } + +const createIdempotencyKeys = `-- name: CreateIdempotencyKeys :exec +INSERT INTO v1_idempotency_key ( + tenant_id, + key, + expires_at +) +SELECT + $1::UUID, + k, + $2::TIMESTAMPTZ +FROM UNNEST($3::TEXT[]) AS k +ON CONFLICT (tenant_id, key) DO UPDATE +SET + expires_at = EXCLUDED.expires_at, + updated_at = NOW() +WHERE + v1_idempotency_key.claimed_by_external_id IS NULL + AND v1_idempotency_key.expires_at < NOW() +` + +type CreateIdempotencyKeysParams struct { + Tenantid uuid.UUID `json:"tenantid"` + Expiresat pgtype.Timestamptz `json:"expiresat"` + Keys []string `json:"keys"` +} + +func (q *Queries) CreateIdempotencyKeys(ctx context.Context, db DBTX, arg CreateIdempotencyKeysParams) error { + _, err := db.Exec(ctx, createIdempotencyKeys, arg.Tenantid, arg.Expiresat, arg.Keys) + return err +} + +const deleteIdempotencyKeysByExternalId = `-- name: DeleteIdempotencyKeysByExternalId :exec +DELETE FROM v1_idempotency_key +WHERE + tenant_id = $1::UUID + AND claimed_by_external_id = $2::UUID +` + +type DeleteIdempotencyKeysByExternalIdParams struct { + Tenantid uuid.UUID `json:"tenantid"` + Externalid uuid.UUID `json:"externalid"` +} + +func (q *Queries) DeleteIdempotencyKeysByExternalId(ctx context.Context, db DBTX, arg DeleteIdempotencyKeysByExternalIdParams) error { + _, err := db.Exec(ctx, deleteIdempotencyKeysByExternalId, arg.Tenantid, arg.Externalid) + return err +} + +const listIdempotencyKeysByKeys = `-- name: ListIdempotencyKeysByKeys :many +SELECT + tenant_id, + key, + expires_at, + claimed_by_external_id, + last_denied_at, + inserted_at, + updated_at +FROM v1_idempotency_key +WHERE + tenant_id = $1::UUID + AND key = ANY($2::TEXT[]) +` + +type ListIdempotencyKeysByKeysParams struct { + Tenantid uuid.UUID `json:"tenantid"` + Keys []string `json:"keys"` +} + +func (q *Queries) ListIdempotencyKeysByKeys(ctx context.Context, db DBTX, arg ListIdempotencyKeysByKeysParams) ([]*V1IdempotencyKey, error) { + rows, err := db.Query(ctx, listIdempotencyKeysByKeys, arg.Tenantid, arg.Keys) + if err != nil { + return nil, err + } + defer rows.Close() + var items []*V1IdempotencyKey + for rows.Next() { + var i V1IdempotencyKey + if err := rows.Scan( + &i.TenantID, + &i.Key, + &i.ExpiresAt, + &i.ClaimedByExternalID, + &i.LastDeniedAt, + &i.InsertedAt, + &i.UpdatedAt, + ); err != nil { + return nil, err + } + items = append(items, &i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const updateIdempotencyKeysLastDeniedAt = `-- name: UpdateIdempotencyKeysLastDeniedAt :exec +UPDATE v1_idempotency_key +SET + last_denied_at = NOW(), + updated_at = NOW() +WHERE + tenant_id = $1::UUID + AND key = ANY($2::TEXT[]) +` + +type UpdateIdempotencyKeysLastDeniedAtParams struct { + Tenantid uuid.UUID `json:"tenantid"` + Keys []string `json:"keys"` +} + +func (q *Queries) UpdateIdempotencyKeysLastDeniedAt(ctx context.Context, db DBTX, arg UpdateIdempotencyKeysLastDeniedAtParams) error { + _, err := db.Exec(ctx, updateIdempotencyKeysLastDeniedAt, arg.Tenantid, arg.Keys) + return err +} + +const updateIdempotencyKeysLastDeniedAtSkipLocked = `-- name: UpdateIdempotencyKeysLastDeniedAtSkipLocked :exec +WITH target AS ( + SELECT + tenant_id, + key + FROM v1_idempotency_key + WHERE + tenant_id = $1::UUID + AND key = ANY($2::TEXT[]) + FOR UPDATE SKIP LOCKED +) +UPDATE v1_idempotency_key k +SET + last_denied_at = NOW(), + updated_at = NOW() +FROM target t +WHERE + k.tenant_id = t.tenant_id + AND k.key = t.key +` + +type UpdateIdempotencyKeysLastDeniedAtSkipLockedParams struct { + Tenantid uuid.UUID `json:"tenantid"` + Keys []string `json:"keys"` +} + +func (q *Queries) UpdateIdempotencyKeysLastDeniedAtSkipLocked(ctx context.Context, db DBTX, arg UpdateIdempotencyKeysLastDeniedAtSkipLockedParams) error { + _, err := db.Exec(ctx, updateIdempotencyKeysLastDeniedAtSkipLocked, arg.Tenantid, arg.Keys) + return err +} diff --git a/pkg/repository/sqlcv1/models.go b/pkg/repository/sqlcv1/models.go index da428ec19a..491176a29a 100644 --- a/pkg/repository/sqlcv1/models.go +++ b/pkg/repository/sqlcv1/models.go @@ -3091,6 +3091,7 @@ type V1IdempotencyKey struct { Key string `json:"key"` ExpiresAt pgtype.Timestamptz `json:"expires_at"` ClaimedByExternalID *uuid.UUID `json:"claimed_by_external_id"` + LastDeniedAt pgtype.Timestamptz `json:"last_denied_at"` InsertedAt pgtype.Timestamptz `json:"inserted_at"` UpdatedAt pgtype.Timestamptz `json:"updated_at"` } diff --git a/pkg/repository/sqlcv1/olap.sql b/pkg/repository/sqlcv1/olap.sql index f67f937547..06c74b3aab 100644 --- a/pkg/repository/sqlcv1/olap.sql +++ b/pkg/repository/sqlcv1/olap.sql @@ -1489,6 +1489,41 @@ LEFT JOIN metadata m ON true LEFT JOIN error_message e ON true ORDER BY r.inserted_at DESC; +-- name: ReadWorkflowRunStatusByExternalId :one +SELECT + r.tenant_id, + r.external_id, + r.readable_status +FROM + v1_lookup_table_olap lt +JOIN + v1_runs_olap r ON r.inserted_at = lt.inserted_at + AND ( + (lt.dag_id IS NOT NULL AND r.id = lt.dag_id) + OR (lt.task_id IS NOT NULL AND r.id = lt.task_id) + ) +WHERE + lt.external_id = @workflowRunExternalId::uuid +; + +-- name: ReadWorkflowRunStatusesByExternalIds :many +SELECT + r.tenant_id, + r.external_id, + r.readable_status +FROM + v1_lookup_table_olap lt +JOIN + v1_runs_olap r ON r.inserted_at = lt.inserted_at + AND ( + (lt.dag_id IS NOT NULL AND r.id = lt.dag_id) + OR (lt.task_id IS NOT NULL AND r.id = lt.task_id) + ) +WHERE + lt.external_id = ANY(@workflowRunExternalIds::uuid[]) + AND lt.tenant_id = @tenantId::uuid +; + -- name: GetWorkflowRunIdFromDagIdInsertedAt :one SELECT external_id FROM v1_dags_olap diff --git a/pkg/repository/sqlcv1/olap.sql.go b/pkg/repository/sqlcv1/olap.sql.go index b7a1feba4f..42b66165fd 100644 --- a/pkg/repository/sqlcv1/olap.sql.go +++ b/pkg/repository/sqlcv1/olap.sql.go @@ -3202,6 +3202,85 @@ func (q *Queries) ReadWorkflowRunByExternalId(ctx context.Context, db DBTX, work return &i, err } +const readWorkflowRunStatusByExternalId = `-- name: ReadWorkflowRunStatusByExternalId :one +SELECT + r.tenant_id, + r.external_id, + r.readable_status +FROM + v1_lookup_table_olap lt +JOIN + v1_runs_olap r ON r.inserted_at = lt.inserted_at + AND ( + (lt.dag_id IS NOT NULL AND r.id = lt.dag_id) + OR (lt.task_id IS NOT NULL AND r.id = lt.task_id) + ) +WHERE + lt.external_id = $1::uuid +` + +type ReadWorkflowRunStatusByExternalIdRow struct { + TenantID uuid.UUID `json:"tenant_id"` + ExternalID uuid.UUID `json:"external_id"` + ReadableStatus V1ReadableStatusOlap `json:"readable_status"` +} + +func (q *Queries) ReadWorkflowRunStatusByExternalId(ctx context.Context, db DBTX, workflowrunexternalid uuid.UUID) (*ReadWorkflowRunStatusByExternalIdRow, error) { + row := db.QueryRow(ctx, readWorkflowRunStatusByExternalId, workflowrunexternalid) + var i ReadWorkflowRunStatusByExternalIdRow + err := row.Scan(&i.TenantID, &i.ExternalID, &i.ReadableStatus) + return &i, err +} + +const readWorkflowRunStatusesByExternalIds = `-- name: ReadWorkflowRunStatusesByExternalIds :many +SELECT + r.tenant_id, + r.external_id, + r.readable_status +FROM + v1_lookup_table_olap lt +JOIN + v1_runs_olap r ON r.inserted_at = lt.inserted_at + AND ( + (lt.dag_id IS NOT NULL AND r.id = lt.dag_id) + OR (lt.task_id IS NOT NULL AND r.id = lt.task_id) + ) +WHERE + lt.external_id = ANY($1::uuid[]) + AND lt.tenant_id = $2::uuid +` + +type ReadWorkflowRunStatusesByExternalIdsParams struct { + Workflowrunexternalids []uuid.UUID `json:"workflowrunexternalids"` + Tenantid uuid.UUID `json:"tenantid"` +} + +type ReadWorkflowRunStatusesByExternalIdsRow struct { + TenantID uuid.UUID `json:"tenant_id"` + ExternalID uuid.UUID `json:"external_id"` + ReadableStatus V1ReadableStatusOlap `json:"readable_status"` +} + +func (q *Queries) ReadWorkflowRunStatusesByExternalIds(ctx context.Context, db DBTX, arg ReadWorkflowRunStatusesByExternalIdsParams) ([]*ReadWorkflowRunStatusesByExternalIdsRow, error) { + rows, err := db.Query(ctx, readWorkflowRunStatusesByExternalIds, arg.Workflowrunexternalids, arg.Tenantid) + if err != nil { + return nil, err + } + defer rows.Close() + var items []*ReadWorkflowRunStatusesByExternalIdsRow + for rows.Next() { + var i ReadWorkflowRunStatusesByExternalIdsRow + if err := rows.Scan(&i.TenantID, &i.ExternalID, &i.ReadableStatus); err != nil { + return nil, err + } + items = append(items, &i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const storeCELEvaluationFailures = `-- name: StoreCELEvaluationFailures :exec WITH inputs AS ( SELECT diff --git a/pkg/repository/sqlcv1/tasks.sql b/pkg/repository/sqlcv1/tasks.sql index 9c7a818462..8969639238 100644 --- a/pkg/repository/sqlcv1/tasks.sql +++ b/pkg/repository/sqlcv1/tasks.sql @@ -822,6 +822,63 @@ WHERE t.tenant_id = @tenantId::uuid AND dt.dag_id = ANY(@dagIds::bigint[]); +-- name: ReadWorkflowRunTerminalStatesByExternalIds :many +WITH input AS ( + SELECT + UNNEST(@externalIds::uuid[]) AS external_id +), lookup AS ( + SELECT + lt.external_id, + lt.dag_id, + lt.task_id, + lt.inserted_at + FROM v1_lookup_table lt + JOIN input i ON i.external_id = lt.external_id + WHERE + lt.tenant_id = @tenantId::uuid +), dag_tasks AS ( + SELECT + l.external_id, + t.id, + t.inserted_at, + t.retry_count + FROM lookup l + JOIN v1_dag_to_task dt ON dt.dag_id = l.dag_id AND dt.dag_inserted_at = l.inserted_at + JOIN v1_task t ON t.id = dt.task_id AND t.inserted_at = dt.task_inserted_at + WHERE l.dag_id IS NOT NULL +), task_only AS ( + SELECT + l.external_id, + t.id, + t.inserted_at, + t.retry_count + FROM lookup l + JOIN v1_task t ON t.id = l.task_id AND t.inserted_at = l.inserted_at + WHERE l.task_id IS NOT NULL +), all_tasks AS ( + SELECT * FROM dag_tasks + UNION ALL + SELECT * FROM task_only +), terminal_tasks AS ( + SELECT + at.external_id, + EXISTS ( + SELECT 1 + FROM v1_task_event e + WHERE + e.tenant_id = @tenantId::uuid + AND (e.task_id, e.task_inserted_at, e.retry_count) = (at.id, at.inserted_at, at.retry_count) + AND e.event_type = ANY('{COMPLETED, FAILED, CANCELLED}'::v1_task_event_type[]) + ) AS is_terminal + FROM all_tasks at +) +SELECT + external_id, + BOOL_AND(is_terminal) AS all_terminal, + COUNT(*) AS task_count +FROM terminal_tasks +GROUP BY external_id; + -- name: ListTaskExpressionEvals :many WITH input AS ( SELECT diff --git a/pkg/repository/sqlcv1/tasks.sql.go b/pkg/repository/sqlcv1/tasks.sql.go index a03202a061..f071a31820 100644 --- a/pkg/repository/sqlcv1/tasks.sql.go +++ b/pkg/repository/sqlcv1/tasks.sql.go @@ -2379,6 +2379,95 @@ func (q *Queries) ProcessRetryQueueItems(ctx context.Context, db DBTX, arg Proce return items, nil } +const readWorkflowRunTerminalStatesByExternalIds = `-- name: ReadWorkflowRunTerminalStatesByExternalIds :many +WITH input AS ( + SELECT + UNNEST($1::uuid[]) AS external_id +), lookup AS ( + SELECT + lt.external_id, + lt.dag_id, + lt.task_id, + lt.inserted_at + FROM v1_lookup_table lt + JOIN input i ON i.external_id = lt.external_id + WHERE + lt.tenant_id = $2::uuid +), dag_tasks AS ( + SELECT + l.external_id, + t.id, + t.inserted_at, + t.retry_count + FROM lookup l + JOIN v1_dag_to_task dt ON dt.dag_id = l.dag_id AND dt.dag_inserted_at = l.inserted_at + JOIN v1_task t ON t.id = dt.task_id AND t.inserted_at = dt.task_inserted_at + WHERE l.dag_id IS NOT NULL +), task_only AS ( + SELECT + l.external_id, + t.id, + t.inserted_at, + t.retry_count + FROM lookup l + JOIN v1_task t ON t.id = l.task_id AND t.inserted_at = l.inserted_at + WHERE l.task_id IS NOT NULL +), all_tasks AS ( + SELECT external_id, id, inserted_at, retry_count FROM dag_tasks + UNION ALL + SELECT external_id, id, inserted_at, retry_count FROM task_only +), terminal_tasks AS ( + SELECT + at.external_id, + EXISTS ( + SELECT 1 + FROM v1_task_event e + WHERE + e.tenant_id = $2::uuid + AND (e.task_id, e.task_inserted_at, e.retry_count) = (at.id, at.inserted_at, at.retry_count) + AND e.event_type = ANY('{COMPLETED, FAILED, CANCELLED}'::v1_task_event_type[]) + ) AS is_terminal + FROM all_tasks at +) +SELECT + external_id, + BOOL_AND(is_terminal) AS all_terminal, + COUNT(*) AS task_count +FROM terminal_tasks +GROUP BY external_id +` + +type ReadWorkflowRunTerminalStatesByExternalIdsParams struct { + Externalids []uuid.UUID `json:"externalids"` + Tenantid uuid.UUID `json:"tenantid"` +} + +type ReadWorkflowRunTerminalStatesByExternalIdsRow struct { + ExternalID uuid.UUID `json:"external_id"` + AllTerminal bool `json:"all_terminal"` + TaskCount int64 `json:"task_count"` +} + +func (q *Queries) ReadWorkflowRunTerminalStatesByExternalIds(ctx context.Context, db DBTX, arg ReadWorkflowRunTerminalStatesByExternalIdsParams) ([]*ReadWorkflowRunTerminalStatesByExternalIdsRow, error) { + rows, err := db.Query(ctx, readWorkflowRunTerminalStatesByExternalIds, arg.Externalids, arg.Tenantid) + if err != nil { + return nil, err + } + defer rows.Close() + var items []*ReadWorkflowRunTerminalStatesByExternalIdsRow + for rows.Next() { + var i ReadWorkflowRunTerminalStatesByExternalIdsRow + if err := rows.Scan(&i.ExternalID, &i.AllTerminal, &i.TaskCount); err != nil { + return nil, err + } + items = append(items, &i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const refreshTimeoutBy = `-- name: RefreshTimeoutBy :one WITH task AS ( SELECT diff --git a/pkg/repository/task_partition_test.go b/pkg/repository/task_partition_test.go index 358de6ec1c..ec046cc1a9 100644 --- a/pkg/repository/task_partition_test.go +++ b/pkg/repository/task_partition_test.go @@ -4,6 +4,7 @@ package repository import ( "context" + "fmt" "os" "strings" "sync" @@ -12,6 +13,7 @@ import ( "time" "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgxpool" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" @@ -25,6 +27,36 @@ import ( ) func setupPostgresWithMigration(t *testing.T) (*pgxpool.Pool, func()) { + t.Helper() + + testPoolOnce.Do(func() { + testPoolErr = initTestPool(t) + }) + + require.NoError(t, testPoolErr) + + testPoolMu.Lock() + t.Cleanup(func() { + testPoolMu.Unlock() + }) + + err := resetDatabase(context.Background(), testPool) + require.NoError(t, err) + + return testPool, func() {} +} + +var ( + testPoolOnce sync.Once + testPoolMu sync.Mutex + testPool *pgxpool.Pool + testPoolErr error + + testContainer *postgres.PostgresContainer + originalDatabaseURL string +) + +func initTestPool(t *testing.T) error { ctx := context.Background() postgresContainer, err := postgres.Run(ctx, @@ -38,23 +70,30 @@ func setupPostgresWithMigration(t *testing.T) (*pgxpool.Pool, func()) { WithStartupTimeout(30*time.Second), ), ) - require.NoError(t, err) + if err != nil { + return err + } connStr, err := postgresContainer.ConnectionString(ctx, "sslmode=disable") - require.NoError(t, err) + if err != nil { + return err + } t.Logf("PostgreSQL container started with connection string: %s", connStr) - originalDatabaseURL := os.Getenv("DATABASE_URL") - err = os.Setenv("DATABASE_URL", connStr) - require.NoError(t, err) + originalDatabaseURL = os.Getenv("DATABASE_URL") + if err := os.Setenv("DATABASE_URL", connStr); err != nil { + return err + } t.Log("Running database migration...") migrate.RunMigrations(ctx) t.Log("Migration completed successfully") config, err := pgxpool.ParseConfig(connStr) - require.NoError(t, err) + if err != nil { + return err + } // Set timezone to UTC for all test connections config.AfterConnect = func(ctx context.Context, conn *pgx.Conn) error { @@ -63,22 +102,107 @@ func setupPostgresWithMigration(t *testing.T) (*pgxpool.Pool, func()) { } pool, err := pgxpool.NewWithConfig(ctx, config) - require.NoError(t, err) + if err != nil { + return err + } - err = pool.Ping(ctx) - require.NoError(t, err) + if err := pool.Ping(ctx); err != nil { + return err + } - cleanup := func() { - pool.Close() - postgresContainer.Terminate(ctx) - if originalDatabaseURL != "" { - os.Setenv("DATABASE_URL", originalDatabaseURL) - } else { - os.Unsetenv("DATABASE_URL") + testPool = pool + testContainer = postgresContainer + + return nil +} + +func resetDatabase(ctx context.Context, pool *pgxpool.Pool) error { + if err := dropPartitionTables(ctx, pool); err != nil { + return err + } + + rows, err := pool.Query(ctx, ` + SELECT tablename + FROM pg_tables + WHERE schemaname = 'public' + `) + if err != nil { + return err + } + defer rows.Close() + + var tables []string + for rows.Next() { + var table string + if err := rows.Scan(&table); err != nil { + return err } + if table == "goose_db_version" { + continue + } + tables = append(tables, pgx.Identifier{table}.Sanitize()) + } + if rows.Err() != nil { + return rows.Err() + } + + if len(tables) == 0 { + return nil + } + + stmt := fmt.Sprintf("TRUNCATE TABLE %s RESTART IDENTITY CASCADE", strings.Join(tables, ", ")) + if _, err = pool.Exec(ctx, stmt); err != nil { + return err + } + + queries := sqlcv1.New() + today := time.Now().UTC() + return queries.CreatePartitions(ctx, pool, pgtype.Date{ + Time: today, + Valid: true, + }) +} + +func dropPartitionTables(ctx context.Context, pool *pgxpool.Pool) error { + rows, err := pool.Query(ctx, ` + SELECT tablename + FROM pg_tables + WHERE schemaname = 'public' + AND ( + tablename ~ '^v1_task_[0-9]{8}$' + OR tablename ~ '^v1_dag_[0-9]{8}$' + OR tablename ~ '^v1_task_event_[0-9]{8}$' + OR tablename ~ '^v1_log_line_[0-9]{8}$' + OR tablename ~ '^v1_payload_[0-9]{8}$' + OR tablename ~ '^v1_event_[0-9]{8}$' + OR tablename ~ '^v1_event_lookup_table_[0-9]{8}$' + OR tablename ~ '^v1_event_to_run_[0-9]{8}$' + ) + `) + if err != nil { + return err + } + defer rows.Close() + + var tables []string + for rows.Next() { + var table string + if err := rows.Scan(&table); err != nil { + return err + } + tables = append(tables, pgx.Identifier{table}.Sanitize()) + } + if rows.Err() != nil { + return rows.Err() + } + + if len(tables) == 0 { + return nil } - return pool, cleanup + stmt := fmt.Sprintf("DROP TABLE IF EXISTS %s CASCADE", strings.Join(tables, ", ")) + _, err = pool.Exec(ctx, stmt) + return err } func createTaskRepository(pool *pgxpool.Pool) *TaskRepositoryImpl { diff --git a/pkg/repository/trigger.go b/pkg/repository/trigger.go index d6df4cafcb..6bb4cfbd70 100644 --- a/pkg/repository/trigger.go +++ b/pkg/repository/trigger.go @@ -3,6 +3,7 @@ package repository import ( "context" "encoding/json" + "errors" "fmt" "slices" "strings" @@ -326,13 +327,71 @@ func getEventExternalIdToRuns(opts []EventTriggerOpts, externalIdToEventIdAndFil } func (r *TriggerRepositoryImpl) TriggerFromWorkflowNames(ctx context.Context, tenantId uuid.UUID, opts []*WorkflowNameTriggerOpts) ([]*V1TaskWithPayload, []*DAGWithData, error) { - triggerOpts, err := r.prepareTriggerFromWorkflowNames(ctx, r.pool, tenantId, opts) + tx, err := r.PrepareOptimisticTx(ctx) if err != nil { + return nil, nil, err + } + + rolledBack := false + defer func() { + if !rolledBack { + tx.Rollback() + } + }() + + triggerOpts, denyUpdateKeys, duplicateKeys, err := r.prepareTriggerFromWorkflowNames(ctx, tx.tx, tenantId, opts) + + if err != nil { + tx.Rollback() + rolledBack = true + if errors.Is(err, ErrIdempotencyKeyAlreadyClaimed) && len(denyUpdateKeys) > 0 { + updateErr := r.queries.UpdateIdempotencyKeysLastDeniedAt(ctx, r.pool, sqlcv1.UpdateIdempotencyKeysLastDeniedAtParams{ + Tenantid: tenantId, + Keys: denyUpdateKeys, + }) + if updateErr != nil { + err = errors.Join(err, fmt.Errorf("failed to update idempotency key deny timestamps: %w", updateErr)) + } + } + return nil, nil, fmt.Errorf("failed to prepare trigger from workflow names: %w", err) } - return r.triggerWorkflows(ctx, nil, tenantId, triggerOpts, nil) + if len(duplicateKeys) > 0 && len(triggerOpts) > 0 { + r.l.Warn(). + Str("tenantId", tenantId.String()). + Int("duplicateKeyCount", len(duplicateKeys)). + Msg("partial idempotency duplicates skipped during trigger") + } + + tasks, dags, err := r.triggerWorkflows(ctx, tx, tenantId, triggerOpts, nil) + + if err != nil { + return nil, nil, fmt.Errorf("failed to trigger workflows: %w", err) + } + + if err := tx.Commit(ctx); err != nil { + return nil, nil, err + } + + rolledBack = true + + if len(denyUpdateKeys) > 0 { + updateErr := r.queries.UpdateIdempotencyKeysLastDeniedAt(ctx, r.pool, sqlcv1.UpdateIdempotencyKeysLastDeniedAtParams{ + Tenantid: tenantId, + Keys: denyUpdateKeys, + }) + if updateErr != nil { + r.l.Error(). + Err(updateErr). + Str("tenantId", tenantId.String()). + Int("keyCount", len(denyUpdateKeys)). + Msg("failed to update idempotency key deny timestamps") + } + } + + return tasks, dags, nil } type ErrNamesNotFound struct { @@ -343,6 +402,31 @@ func (e *ErrNamesNotFound) Error() string { return fmt.Sprintf("workflow names not found: %s", strings.Join(e.Names, ", ")) } +var ErrIdempotencyKeyAlreadyClaimed = errors.New("idempotency key already claimed") + +type IdempotencyKeyAlreadyClaimedError struct { + Keys []string +} + +func (e *IdempotencyKeyAlreadyClaimedError) Error() string { + return fmt.Sprintf("idempotency key already claimed: %s", strings.Join(e.Keys, ", ")) +} + +func (e *IdempotencyKeyAlreadyClaimedError) Is(target error) bool { + return target == ErrIdempotencyKeyAlreadyClaimed +} + +func isTerminalReadableStatus(status sqlcv1.V1ReadableStatusOlap) bool { + switch status { + case sqlcv1.V1ReadableStatusOlapCOMPLETED, + sqlcv1.V1ReadableStatusOlapFAILED, + sqlcv1.V1ReadableStatusOlapCANCELLED: + return true + default: + return false + } +} + func (r *TriggerRepositoryImpl) PreflightVerifyWorkflowNameOpts(ctx context.Context, tenantId uuid.UUID, opts []*WorkflowNameTriggerOpts) error { // get a list of workflow names workflowNamesFound := make(map[string]bool) @@ -2078,12 +2162,17 @@ func (r *sharedRepository) prepareTriggerFromEvents(ctx context.Context, tx sqlc func (r *sharedRepository) prepareTriggerFromWorkflowNames(ctx context.Context, tx sqlcv1.DBTX, tenantId uuid.UUID, opts []*WorkflowNameTriggerOpts) ( []triggerTuple, + []string, + []string, error, ) { workflowNames := make([]string, 0, len(opts)) uniqueNames := make(map[string]struct{}) namesToOpts := make(map[string][]*WorkflowNameTriggerOpts) idempotencyKeyToExternalIds := make(map[IdempotencyKey]uuid.UUID) + var err error + var denyUpdateKeys []string + var duplicateKeys []string for _, opt := range opts { if opt.IdempotencyKey != nil { @@ -2109,16 +2198,72 @@ func (r *sharedRepository) prepareTriggerFromWorkflowNames(ctx context.Context, }) } - keyClaimantPairToWasClaimed, err := claimIdempotencyKeys(ctx, r.queries, tx, tenantId, keyClaimantPairs) + keyClaimantPairToWasClaimed := make(map[KeyClaimantPair]WasSuccessfullyClaimed) - if err != nil { - return nil, fmt.Errorf("failed to claim idempotency keys: %w", err) + if len(keyClaimantPairs) > 0 { + keys := make([]string, 0, len(keyClaimantPairs)) + + for _, pair := range keyClaimantPairs { + keys = append(keys, string(pair.IdempotencyKey)) + } + + ttl := r.idempotencyKeyTTL + if ttl <= 0 { + ttl = 24 * time.Hour + } + expiresAt := sqlchelpers.TimestamptzFromTime(time.Now().Add(ttl)) + + err = r.queries.CreateIdempotencyKeys(ctx, tx, sqlcv1.CreateIdempotencyKeysParams{ + Tenantid: tenantId, + Keys: keys, + Expiresat: expiresAt, + }) + + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to create idempotency keys: %w", err) + } + + keyClaimantPairToWasClaimed, err = claimIdempotencyKeys(ctx, r.queries, tx, tenantId, keyClaimantPairs) + + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to claim idempotency keys: %w", err) + } + } + + if len(keyClaimantPairs) > 0 { + unclaimedKeys := make([]string, 0) + + for _, pair := range keyClaimantPairs { + if !keyClaimantPairToWasClaimed[pair] { + unclaimedKeys = append(unclaimedKeys, string(pair.IdempotencyKey)) + } + } + + if len(unclaimedKeys) > 0 { + denyUpdateKeys, err = r.tryReclaimIdempotencyKeys(ctx, tx, tenantId, keyClaimantPairs, keyClaimantPairToWasClaimed, unclaimedKeys) + + if err != nil { + return nil, nil, nil, err + } + + unclaimedKeys = unclaimedKeys[:0] + + for _, pair := range keyClaimantPairs { + if !keyClaimantPairToWasClaimed[pair] { + unclaimedKeys = append(unclaimedKeys, string(pair.IdempotencyKey)) + } + } + + if len(unclaimedKeys) > 0 { + duplicateKeys = append(duplicateKeys, unclaimedKeys...) + } + } } workflowVersionsByNames, err := r.listWorkflowsByNames(ctx, tx, tenantId, workflowNames) if err != nil { - return nil, fmt.Errorf("failed to list workflows for names: %w", err) + return nil, nil, nil, fmt.Errorf("failed to list workflows for names: %w", err) } // each (workflowVersionId, opt) is a separate workflow that we need to create @@ -2164,5 +2309,224 @@ func (r *sharedRepository) prepareTriggerFromWorkflowNames(ctx context.Context, } } - return triggerOpts, nil + if len(triggerOpts) == 0 && len(duplicateKeys) > 0 { + return nil, denyUpdateKeys, duplicateKeys, &IdempotencyKeyAlreadyClaimedError{Keys: duplicateKeys} + } + + return triggerOpts, denyUpdateKeys, duplicateKeys, nil +} + +func (r *sharedRepository) tryReclaimIdempotencyKeys( + ctx context.Context, + tx sqlcv1.DBTX, + tenantId uuid.UUID, + keyClaimantPairs []KeyClaimantPair, + keyClaimantPairToWasClaimed map[KeyClaimantPair]WasSuccessfullyClaimed, + unclaimedKeys []string, +) ([]string, error) { + if len(unclaimedKeys) == 0 { + return nil, nil + } + + if r.idempotencyKeyDenyRecheckInterval <= 0 { + return nil, nil + } + + rows, err := r.queries.ListIdempotencyKeysByKeys(ctx, r.pool, sqlcv1.ListIdempotencyKeysByKeysParams{ + Tenantid: tenantId, + Keys: unclaimedKeys, + }) + + if err != nil { + return nil, fmt.Errorf("failed to list idempotency keys for recheck: %w", err) + } + + if len(rows) == 0 { + return nil, nil + } + + now := time.Now() + throttledCount := 0 + keysToCheck := make([]string, 0, len(rows)) + keysToReclaim := make([]string, 0) + keysToReclaimSet := make(map[string]struct{}) + keyToExternalId := make(map[string]uuid.UUID) + externalIdsToCheck := make(map[uuid.UUID]struct{}) + + for _, row := range rows { + if row.ClaimedByExternalID == nil { + continue + } + + if row.LastDeniedAt.Valid { + if now.Sub(row.LastDeniedAt.Time) < r.idempotencyKeyDenyRecheckInterval { + throttledCount++ + continue + } + } + + keysToCheck = append(keysToCheck, row.Key) + keyToExternalId[row.Key] = *row.ClaimedByExternalID + externalIdsToCheck[*row.ClaimedByExternalID] = struct{}{} + } + + if len(keysToCheck) == 0 { + if throttledCount > 0 { + r.l.Debug(). + Str("tenantId", tenantId.String()). + Int("throttledCount", throttledCount). + Msg("idempotency recheck throttled") + } + return nil, nil + } + + if throttledCount > 0 { + r.l.Debug(). + Str("tenantId", tenantId.String()). + Int("throttledCount", throttledCount). + Msg("idempotency recheck throttled") + } + + externalIdToStatus := make(map[uuid.UUID]sqlcv1.V1ReadableStatusOlap) + + externalIds := make([]uuid.UUID, 0, len(externalIdsToCheck)) + for externalId := range externalIdsToCheck { + externalIds = append(externalIds, externalId) + } + + statusRows, statusErr := r.queries.ReadWorkflowRunStatusesByExternalIds(ctx, r.pool, sqlcv1.ReadWorkflowRunStatusesByExternalIdsParams{ + Workflowrunexternalids: externalIds, + Tenantid: tenantId, + }) + if statusErr != nil { + r.l.Error(). + Err(statusErr). + Str("tenantId", tenantId.String()). + Int("externalIdCount", len(externalIds)). + Msg("failed to read workflow run status for idempotency recheck") + return nil, fmt.Errorf("failed to read workflow run status for idempotency recheck: %w", statusErr) + } + + for _, row := range statusRows { + externalIdToStatus[row.ExternalID] = row.ReadableStatus + } + + missingExternalIds := make([]uuid.UUID, 0) + for externalId := range externalIdsToCheck { + if _, ok := externalIdToStatus[externalId]; !ok { + missingExternalIds = append(missingExternalIds, externalId) + } + } + + terminalFallback := make(map[uuid.UUID]struct{}) + if len(missingExternalIds) > 0 { + fallbackRows, fallbackErr := r.queries.ReadWorkflowRunTerminalStatesByExternalIds(ctx, r.pool, sqlcv1.ReadWorkflowRunTerminalStatesByExternalIdsParams{ + Externalids: missingExternalIds, + Tenantid: tenantId, + }) + if fallbackErr != nil { + r.l.Error(). + Err(fallbackErr). + Str("tenantId", tenantId.String()). + Int("missingOlapCount", len(missingExternalIds)). + Msg("failed to read workflow run status from core tables for idempotency recheck") + } else { + for _, row := range fallbackRows { + if row.TaskCount > 0 && row.AllTerminal { + terminalFallback[row.ExternalID] = struct{}{} + } + } + } + } + + missingDeniedCount := 0 + + for _, key := range keysToCheck { + externalId, ok := keyToExternalId[key] + if !ok { + continue + } + + status, ok := externalIdToStatus[externalId] + terminal := ok && isTerminalReadableStatus(status) + if !ok { + if _, ok := terminalFallback[externalId]; ok { + terminal = true + } else { + missingDeniedCount++ + } + } + + if terminal { + deleteErr := r.queries.DeleteIdempotencyKeysByExternalId(ctx, tx, sqlcv1.DeleteIdempotencyKeysByExternalIdParams{ + Tenantid: tenantId, + Externalid: externalId, + }) + if deleteErr != nil { + return nil, fmt.Errorf("failed to delete idempotency keys for completed workflow run: %w", deleteErr) + } + + keysToReclaim = append(keysToReclaim, key) + keysToReclaimSet[key] = struct{}{} + } + } + + if missingDeniedCount > 0 { + r.l.Warn(). + Str("tenantId", tenantId.String()). + Int("missingStatusDeniedCount", missingDeniedCount). + Msg("idempotency recheck denied due to missing workflow run status") + } + + keysToUpdate := keysToCheck + if len(keysToReclaimSet) > 0 { + keysToUpdate = make([]string, 0, len(keysToCheck)) + for _, key := range keysToCheck { + if _, ok := keysToReclaimSet[key]; !ok { + keysToUpdate = append(keysToUpdate, key) + } + } + } + + if len(keysToReclaim) == 0 { + return keysToUpdate, nil + } + + ttl := r.idempotencyKeyTTL + if ttl <= 0 { + ttl = 24 * time.Hour + } + + expiresAt := sqlchelpers.TimestamptzFromTime(time.Now().Add(ttl)) + + createErr := r.queries.CreateIdempotencyKeys(ctx, tx, sqlcv1.CreateIdempotencyKeysParams{ + Tenantid: tenantId, + Keys: keysToReclaim, + Expiresat: expiresAt, + }) + if createErr != nil { + return nil, fmt.Errorf("failed to recreate idempotency keys after reclaim: %w", createErr) + } + + pairsToReclaim := make([]KeyClaimantPair, 0, len(keysToReclaim)) + for _, pair := range keyClaimantPairs { + if _, ok := keysToReclaimSet[string(pair.IdempotencyKey)]; ok { + pairsToReclaim = append(pairsToReclaim, pair) + } + } + + if len(pairsToReclaim) == 0 { + return keysToUpdate, nil + } + + claimResults, err := claimIdempotencyKeys(ctx, r.queries, tx, tenantId, pairsToReclaim) + if err != nil { + return nil, fmt.Errorf("failed to reclaim idempotency keys: %w", err) + } + + for pair, claimed := range claimResults { + keyClaimantPairToWasClaimed[pair] = claimed + } + + return keysToUpdate, nil } diff --git a/sdks/python/hatchet_sdk/clients/admin.py b/sdks/python/hatchet_sdk/clients/admin.py index 4cb7dc49ac..4335137301 100644 --- a/sdks/python/hatchet_sdk/clients/admin.py +++ b/sdks/python/hatchet_sdk/clients/admin.py @@ -7,7 +7,7 @@ import grpc from google.protobuf import timestamp_pb2 -from pydantic import BaseModel, ConfigDict, Field, field_validator +from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator from hatchet_sdk.clients.listeners.run_event_listener import RunEventListenerClient from hatchet_sdk.clients.listeners.workflow_listener import PooledWorkflowRunListener @@ -107,6 +107,13 @@ class TriggerWorkflowOptions(ScheduleTriggerWorkflowOptions): desired_worker_id: str | None = None sticky: bool = False key: str | None = None + idempotency_key: str | None = None + + @model_validator(mode="after") + def normalize_idempotency_key(self) -> "TriggerWorkflowOptions": + if self.idempotency_key is None and self.key is not None: + self.idempotency_key = self.key + return self class WorkflowRunTriggerConfig(BaseModel): @@ -167,6 +174,7 @@ class TriggerWorkflowRequest(BaseModel): additional_metadata: str | None = None desired_worker_id: str | None = None priority: int | None = None + idempotency_key: str | None = None @field_validator("additional_metadata", mode="before") @classmethod @@ -194,7 +202,7 @@ def _prepare_workflow_request( _options = self.TriggerWorkflowRequest.model_validate(options.model_dump()) - return v0_workflow_protos.TriggerWorkflowRequest( + request = v0_workflow_protos.TriggerWorkflowRequest( name=workflow_name, input=payload_data, parent_id=_options.parent_id, @@ -206,6 +214,14 @@ def _prepare_workflow_request( priority=_options.priority, ) + if ( + _options.idempotency_key + and "idempotency_key" in request.DESCRIPTOR.fields_by_name + ): + request.idempotency_key = _options.idempotency_key + + return request + def _parse_schedule( self, schedule: datetime | timestamp_pb2.Timestamp ) -> timestamp_pb2.Timestamp: @@ -372,6 +388,7 @@ def _create_workflow_run_request( namespace=options.namespace, sticky=options.sticky, key=options.key, + idempotency_key=options.idempotency_key, ) namespace = options.namespace or self.namespace diff --git a/sdks/python/hatchet_sdk/contracts/v1/workflows_pb2.py b/sdks/python/hatchet_sdk/contracts/v1/workflows_pb2.py index 0b655e0780..10780a4952 100644 --- a/sdks/python/hatchet_sdk/contracts/v1/workflows_pb2.py +++ b/sdks/python/hatchet_sdk/contracts/v1/workflows_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: v1/workflows.proto -# Protobuf Python Version: 6.31.1 +# Protobuf Python Version: 6.32.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,7 +12,7 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 31, + 32, 1, '', 'v1/workflows.proto' @@ -26,7 +26,7 @@ from hatchet_sdk.contracts.v1.shared import condition_pb2 as v1_dot_shared_dot_condition__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x12v1/workflows.proto\x12\x02v1\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x19v1/shared/condition.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\"\x82\x01\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\x42\x0b\n\t_priority\"1\n\x1aTriggerWorkflowRunResponse\x12\x13\n\x0b\x65xternal_id\x18\x01 \x01(\t\"\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\"\xe8\x01\n\x13\x44\x65siredWorkerLabels\x12\x16\n\tstr_value\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x16\n\tint_value\x18\x02 \x01(\x05H\x01\x88\x01\x01\x12\x15\n\x08required\x18\x03 \x01(\x08H\x02\x88\x01\x01\x12\x32\n\ncomparator\x18\x04 \x01(\x0e\x32\x19.v1.WorkerLabelComparatorH\x03\x88\x01\x01\x12\x13\n\x06weight\x18\x05 \x01(\x05H\x04\x88\x01\x01\x42\x0c\n\n_str_valueB\x0c\n\n_int_valueB\x0b\n\t_requiredB\r\n\x0b_comparatorB\t\n\x07_weight\"\xb1\x04\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\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\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\"\x96\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(\tB\x08\n\x06_errorB\t\n\x07_output\"\xf0\x01\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\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\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*\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*\x85\x01\n\x15WorkerLabelComparator\x12\t\n\x05\x45QUAL\x10\x00\x12\r\n\tNOT_EQUAL\x10\x01\x12\x10\n\x0cGREATER_THAN\x10\x02\x12\x19\n\x15GREATER_THAN_OR_EQUAL\x10\x03\x12\r\n\tLESS_THAN\x10\x04\x12\x16\n\x12LESS_THAN_OR_EQUAL\x10\x05\x32\xfd\x02\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.GetRunDetailsResponseBBZ@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\"[\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\"\xb4\x01\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\x12\x1c\n\x0fidempotency_key\x18\x05 \x01(\tH\x01\x88\x01\x01\x42\x0b\n\t_priorityB\x12\n\x10_idempotency_key\"1\n\x1aTriggerWorkflowRunResponse\x12\x13\n\x0b\x65xternal_id\x18\x01 \x01(\t\"\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\"\xe8\x01\n\x13\x44\x65siredWorkerLabels\x12\x16\n\tstr_value\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x16\n\tint_value\x18\x02 \x01(\x05H\x01\x88\x01\x01\x12\x15\n\x08required\x18\x03 \x01(\x08H\x02\x88\x01\x01\x12\x32\n\ncomparator\x18\x04 \x01(\x0e\x32\x19.v1.WorkerLabelComparatorH\x03\x88\x01\x01\x12\x13\n\x06weight\x18\x05 \x01(\x05H\x04\x88\x01\x01\x42\x0c\n\n_str_valueB\x0c\n\n_int_valueB\x0b\n\t_requiredB\r\n\x0b_comparatorB\t\n\x07_weight\"\xb1\x04\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\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\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\"\x96\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(\tB\x08\n\x06_errorB\t\n\x07_output\"\xf0\x01\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\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\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*\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*\x85\x01\n\x15WorkerLabelComparator\x12\t\n\x05\x45QUAL\x10\x00\x12\r\n\tNOT_EQUAL\x10\x01\x12\x10\n\x0cGREATER_THAN\x10\x02\x12\x19\n\x15GREATER_THAN_OR_EQUAL\x10\x03\x12\r\n\tLESS_THAN\x10\x04\x12\x16\n\x12LESS_THAN_OR_EQUAL\x10\x05\x32\xfd\x02\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.GetRunDetailsResponseBBZ@github.com/hatchet-dev/hatchet/internal/services/shared/proto/v1b\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -38,16 +38,16 @@ _globals['_CREATETASKOPTS_WORKERLABELSENTRY']._serialized_options = b'8\001' _globals['_GETRUNDETAILSRESPONSE_TASKRUNSENTRY']._loaded_options = None _globals['_GETRUNDETAILSRESPONSE_TASKRUNSENTRY']._serialized_options = b'8\001' - _globals['_STICKYSTRATEGY']._serialized_start=3094 - _globals['_STICKYSTRATEGY']._serialized_end=3130 - _globals['_RATELIMITDURATION']._serialized_start=3132 - _globals['_RATELIMITDURATION']._serialized_end=3225 - _globals['_RUNSTATUS']._serialized_start=3227 - _globals['_RUNSTATUS']._serialized_end=3305 - _globals['_CONCURRENCYLIMITSTRATEGY']._serialized_start=3307 - _globals['_CONCURRENCYLIMITSTRATEGY']._serialized_end=3434 - _globals['_WORKERLABELCOMPARATOR']._serialized_start=3437 - _globals['_WORKERLABELCOMPARATOR']._serialized_end=3570 + _globals['_STICKYSTRATEGY']._serialized_start=3144 + _globals['_STICKYSTRATEGY']._serialized_end=3180 + _globals['_RATELIMITDURATION']._serialized_start=3182 + _globals['_RATELIMITDURATION']._serialized_end=3275 + _globals['_RUNSTATUS']._serialized_start=3277 + _globals['_RUNSTATUS']._serialized_end=3355 + _globals['_CONCURRENCYLIMITSTRATEGY']._serialized_start=3357 + _globals['_CONCURRENCYLIMITSTRATEGY']._serialized_end=3484 + _globals['_WORKERLABELCOMPARATOR']._serialized_start=3487 + _globals['_WORKERLABELCOMPARATOR']._serialized_end=3620 _globals['_CANCELTASKSREQUEST']._serialized_start=86 _globals['_CANCELTASKSREQUEST']._serialized_end=177 _globals['_REPLAYTASKSREQUEST']._serialized_start=179 @@ -59,33 +59,33 @@ _globals['_REPLAYTASKSRESPONSE']._serialized_start=506 _globals['_REPLAYTASKSRESPONSE']._serialized_end=551 _globals['_TRIGGERWORKFLOWRUNREQUEST']._serialized_start=554 - _globals['_TRIGGERWORKFLOWRUNREQUEST']._serialized_end=684 - _globals['_TRIGGERWORKFLOWRUNRESPONSE']._serialized_start=686 - _globals['_TRIGGERWORKFLOWRUNRESPONSE']._serialized_end=735 - _globals['_CREATEWORKFLOWVERSIONREQUEST']._serialized_start=738 - _globals['_CREATEWORKFLOWVERSIONREQUEST']._serialized_end=1294 - _globals['_DEFAULTFILTER']._serialized_start=1296 - _globals['_DEFAULTFILTER']._serialized_end=1380 - _globals['_CONCURRENCY']._serialized_start=1383 - _globals['_CONCURRENCY']._serialized_end=1530 - _globals['_DESIREDWORKERLABELS']._serialized_start=1533 - _globals['_DESIREDWORKERLABELS']._serialized_end=1765 - _globals['_CREATETASKOPTS']._serialized_start=1768 - _globals['_CREATETASKOPTS']._serialized_end=2329 - _globals['_CREATETASKOPTS_WORKERLABELSENTRY']._serialized_start=2174 - _globals['_CREATETASKOPTS_WORKERLABELSENTRY']._serialized_end=2250 - _globals['_CREATETASKRATELIMIT']._serialized_start=2332 - _globals['_CREATETASKRATELIMIT']._serialized_end=2585 - _globals['_CREATEWORKFLOWVERSIONRESPONSE']._serialized_start=2587 - _globals['_CREATEWORKFLOWVERSIONRESPONSE']._serialized_end=2651 - _globals['_GETRUNDETAILSREQUEST']._serialized_start=2653 - _globals['_GETRUNDETAILSREQUEST']._serialized_end=2696 - _globals['_TASKRUNDETAIL']._serialized_start=2699 - _globals['_TASKRUNDETAIL']._serialized_end=2849 - _globals['_GETRUNDETAILSRESPONSE']._serialized_start=2852 - _globals['_GETRUNDETAILSRESPONSE']._serialized_end=3092 - _globals['_GETRUNDETAILSRESPONSE_TASKRUNSENTRY']._serialized_start=3026 - _globals['_GETRUNDETAILSRESPONSE_TASKRUNSENTRY']._serialized_end=3092 - _globals['_ADMINSERVICE']._serialized_start=3573 - _globals['_ADMINSERVICE']._serialized_end=3954 + _globals['_TRIGGERWORKFLOWRUNREQUEST']._serialized_end=734 + _globals['_TRIGGERWORKFLOWRUNRESPONSE']._serialized_start=736 + _globals['_TRIGGERWORKFLOWRUNRESPONSE']._serialized_end=785 + _globals['_CREATEWORKFLOWVERSIONREQUEST']._serialized_start=788 + _globals['_CREATEWORKFLOWVERSIONREQUEST']._serialized_end=1344 + _globals['_DEFAULTFILTER']._serialized_start=1346 + _globals['_DEFAULTFILTER']._serialized_end=1430 + _globals['_CONCURRENCY']._serialized_start=1433 + _globals['_CONCURRENCY']._serialized_end=1580 + _globals['_DESIREDWORKERLABELS']._serialized_start=1583 + _globals['_DESIREDWORKERLABELS']._serialized_end=1815 + _globals['_CREATETASKOPTS']._serialized_start=1818 + _globals['_CREATETASKOPTS']._serialized_end=2379 + _globals['_CREATETASKOPTS_WORKERLABELSENTRY']._serialized_start=2224 + _globals['_CREATETASKOPTS_WORKERLABELSENTRY']._serialized_end=2300 + _globals['_CREATETASKRATELIMIT']._serialized_start=2382 + _globals['_CREATETASKRATELIMIT']._serialized_end=2635 + _globals['_CREATEWORKFLOWVERSIONRESPONSE']._serialized_start=2637 + _globals['_CREATEWORKFLOWVERSIONRESPONSE']._serialized_end=2701 + _globals['_GETRUNDETAILSREQUEST']._serialized_start=2703 + _globals['_GETRUNDETAILSREQUEST']._serialized_end=2746 + _globals['_TASKRUNDETAIL']._serialized_start=2749 + _globals['_TASKRUNDETAIL']._serialized_end=2899 + _globals['_GETRUNDETAILSRESPONSE']._serialized_start=2902 + _globals['_GETRUNDETAILSRESPONSE']._serialized_end=3142 + _globals['_GETRUNDETAILSRESPONSE_TASKRUNSENTRY']._serialized_start=3076 + _globals['_GETRUNDETAILSRESPONSE_TASKRUNSENTRY']._serialized_end=3142 + _globals['_ADMINSERVICE']._serialized_start=3623 + _globals['_ADMINSERVICE']._serialized_end=4004 # @@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 412e0e05ff..5bfae8d14f 100644 --- a/sdks/python/hatchet_sdk/contracts/v1/workflows_pb2.pyi +++ b/sdks/python/hatchet_sdk/contracts/v1/workflows_pb2.pyi @@ -119,16 +119,18 @@ class ReplayTasksResponse(_message.Message): def __init__(self, replayed_tasks: _Optional[_Iterable[str]] = ...) -> None: ... class TriggerWorkflowRunRequest(_message.Message): - __slots__ = ("workflow_name", "input", "additional_metadata", "priority") + __slots__ = ("workflow_name", "input", "additional_metadata", "priority", "idempotency_key") WORKFLOW_NAME_FIELD_NUMBER: _ClassVar[int] INPUT_FIELD_NUMBER: _ClassVar[int] ADDITIONAL_METADATA_FIELD_NUMBER: _ClassVar[int] PRIORITY_FIELD_NUMBER: _ClassVar[int] + IDEMPOTENCY_KEY_FIELD_NUMBER: _ClassVar[int] workflow_name: str input: bytes additional_metadata: bytes priority: int - def __init__(self, workflow_name: _Optional[str] = ..., input: _Optional[bytes] = ..., additional_metadata: _Optional[bytes] = ..., priority: _Optional[int] = ...) -> None: ... + idempotency_key: str + def __init__(self, workflow_name: _Optional[str] = ..., input: _Optional[bytes] = ..., additional_metadata: _Optional[bytes] = ..., priority: _Optional[int] = ..., idempotency_key: _Optional[str] = ...) -> None: ... class TriggerWorkflowRunResponse(_message.Message): __slots__ = ("external_id",) @@ -200,7 +202,7 @@ class DesiredWorkerLabels(_message.Message): required: bool comparator: WorkerLabelComparator weight: int - def __init__(self, str_value: _Optional[str] = ..., int_value: _Optional[int] = ..., required: bool = ..., comparator: _Optional[_Union[WorkerLabelComparator, str]] = ..., weight: _Optional[int] = ...) -> None: ... + def __init__(self, str_value: _Optional[str] = ..., int_value: _Optional[int] = ..., required: _Optional[bool] = ..., comparator: _Optional[_Union[WorkerLabelComparator, str]] = ..., weight: _Optional[int] = ...) -> None: ... class CreateTaskOpts(_message.Message): __slots__ = ("readable_id", "action", "timeout", "inputs", "parents", "retries", "rate_limits", "worker_labels", "backoff_factor", "backoff_max_seconds", "concurrency", "conditions", "schedule_timeout") @@ -302,4 +304,4 @@ class GetRunDetailsResponse(_message.Message): task_runs: _containers.MessageMap[str, TaskRunDetail] done: bool additional_metadata: bytes - def __init__(self, input: _Optional[bytes] = ..., status: _Optional[_Union[RunStatus, str]] = ..., task_runs: _Optional[_Mapping[str, TaskRunDetail]] = ..., done: bool = ..., additional_metadata: _Optional[bytes] = ...) -> None: ... + def __init__(self, input: _Optional[bytes] = ..., status: _Optional[_Union[RunStatus, str]] = ..., task_runs: _Optional[_Mapping[str, TaskRunDetail]] = ..., done: _Optional[bool] = ..., additional_metadata: _Optional[bytes] = ...) -> None: ... diff --git a/sdks/python/hatchet_sdk/contracts/workflows_pb2.py b/sdks/python/hatchet_sdk/contracts/workflows_pb2.py index 0a7e6d53f2..9ca42bff83 100644 --- a/sdks/python/hatchet_sdk/contracts/workflows_pb2.py +++ b/sdks/python/hatchet_sdk/contracts/workflows_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: workflows.proto -# Protobuf Python Version: 6.31.1 +# Protobuf Python Version: 6.32.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,7 +12,7 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 31, + 32, 1, '', 'workflows.proto' @@ -25,7 +25,7 @@ from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0fworkflows.proto\x1a\x1fgoogle/protobuf/timestamp.proto\">\n\x12PutWorkflowRequest\x12(\n\x04opts\x18\x01 \x01(\x0b\x32\x1a.CreateWorkflowVersionOpts\"\xbf\x04\n\x19\x43reateWorkflowVersionOpts\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\x36\n\x12scheduled_triggers\x18\x06 \x03(\x0b\x32\x1a.google.protobuf.Timestamp\x12$\n\x04jobs\x18\x07 \x03(\x0b\x32\x16.CreateWorkflowJobOpts\x12-\n\x0b\x63oncurrency\x18\x08 \x01(\x0b\x32\x18.WorkflowConcurrencyOpts\x12\x1d\n\x10schedule_timeout\x18\t \x01(\tH\x00\x88\x01\x01\x12\x17\n\ncron_input\x18\n \x01(\tH\x01\x88\x01\x01\x12\x33\n\x0eon_failure_job\x18\x0b \x01(\x0b\x32\x16.CreateWorkflowJobOptsH\x02\x88\x01\x01\x12$\n\x06sticky\x18\x0c \x01(\x0e\x32\x0f.StickyStrategyH\x03\x88\x01\x01\x12 \n\x04kind\x18\r \x01(\x0e\x32\r.WorkflowKindH\x04\x88\x01\x01\x12\x1d\n\x10\x64\x65\x66\x61ult_priority\x18\x0e \x01(\x05H\x05\x88\x01\x01\x42\x13\n\x11_schedule_timeoutB\r\n\x0b_cron_inputB\x11\n\x0f_on_failure_jobB\t\n\x07_stickyB\x07\n\x05_kindB\x13\n\x11_default_priority\"\xd0\x01\n\x17WorkflowConcurrencyOpts\x12\x13\n\x06\x61\x63tion\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x15\n\x08max_runs\x18\x02 \x01(\x05H\x01\x88\x01\x01\x12\x36\n\x0elimit_strategy\x18\x03 \x01(\x0e\x32\x19.ConcurrencyLimitStrategyH\x02\x88\x01\x01\x12\x17\n\nexpression\x18\x04 \x01(\tH\x03\x88\x01\x01\x42\t\n\x07_actionB\x0b\n\t_max_runsB\x11\n\x0f_limit_strategyB\r\n\x0b_expression\"h\n\x15\x43reateWorkflowJobOpts\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12&\n\x05steps\x18\x04 \x03(\x0b\x32\x17.CreateWorkflowStepOptsJ\x04\x08\x03\x10\x04\"\xe5\x01\n\x13\x44\x65siredWorkerLabels\x12\x16\n\tstr_value\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x16\n\tint_value\x18\x02 \x01(\x05H\x01\x88\x01\x01\x12\x15\n\x08required\x18\x03 \x01(\x08H\x02\x88\x01\x01\x12/\n\ncomparator\x18\x04 \x01(\x0e\x32\x16.WorkerLabelComparatorH\x03\x88\x01\x01\x12\x13\n\x06weight\x18\x05 \x01(\x05H\x04\x88\x01\x01\x42\x0c\n\n_str_valueB\x0c\n\n_int_valueB\x0b\n\t_requiredB\r\n\x0b_comparatorB\t\n\x07_weight\"\xb5\x03\n\x16\x43reateWorkflowStepOpts\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\x11\n\tuser_data\x18\x06 \x01(\t\x12\x0f\n\x07retries\x18\x07 \x01(\x05\x12)\n\x0brate_limits\x18\x08 \x03(\x0b\x32\x14.CreateStepRateLimit\x12@\n\rworker_labels\x18\t \x03(\x0b\x32).CreateWorkflowStepOpts.WorkerLabelsEntry\x12\x1b\n\x0e\x62\x61\x63koff_factor\x18\n \x01(\x02H\x00\x88\x01\x01\x12 \n\x13\x62\x61\x63koff_max_seconds\x18\x0b \x01(\x05H\x01\x88\x01\x01\x1aI\n\x11WorkerLabelsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12#\n\x05value\x18\x02 \x01(\x0b\x32\x14.DesiredWorkerLabels:\x02\x38\x01\x42\x11\n\x0f_backoff_factorB\x16\n\x14_backoff_max_seconds\"\xfa\x01\n\x13\x43reateStepRateLimit\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\x12.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\"\x16\n\x14ListWorkflowsRequest\"\x83\x03\n\x17ScheduleWorkflowRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12-\n\tschedules\x18\x02 \x03(\x0b\x32\x1a.google.protobuf.Timestamp\x12\r\n\x05input\x18\x03 \x01(\t\x12\x16\n\tparent_id\x18\x04 \x01(\tH\x00\x88\x01\x01\x12(\n\x1bparent_task_run_external_id\x18\x05 \x01(\tH\x01\x88\x01\x01\x12\x18\n\x0b\x63hild_index\x18\x06 \x01(\x05H\x02\x88\x01\x01\x12\x16\n\tchild_key\x18\x07 \x01(\tH\x03\x88\x01\x01\x12 \n\x13\x61\x64\x64itional_metadata\x18\x08 \x01(\tH\x04\x88\x01\x01\x12\x15\n\x08priority\x18\t \x01(\x05H\x05\x88\x01\x01\x42\x0c\n\n_parent_idB\x1e\n\x1c_parent_task_run_external_idB\x0e\n\x0c_child_indexB\x0c\n\n_child_keyB\x16\n\x14_additional_metadataB\x0b\n\t_priority\"O\n\x11ScheduledWorkflow\x12\n\n\x02id\x18\x01 \x01(\t\x12.\n\ntrigger_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\xe3\x01\n\x0fWorkflowVersion\x12\n\n\x02id\x18\x01 \x01(\t\x12.\n\ncreated_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12.\n\nupdated_at\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x0f\n\x07version\x18\x05 \x01(\t\x12\r\n\x05order\x18\x06 \x01(\x03\x12\x13\n\x0bworkflow_id\x18\x07 \x01(\t\x12/\n\x13scheduled_workflows\x18\x08 \x03(\x0b\x32\x12.ScheduledWorkflow\"?\n\x17WorkflowTriggerEventRef\x12\x11\n\tparent_id\x18\x01 \x01(\t\x12\x11\n\tevent_key\x18\x02 \x01(\t\"9\n\x16WorkflowTriggerCronRef\x12\x11\n\tparent_id\x18\x01 \x01(\t\x12\x0c\n\x04\x63ron\x18\x02 \x01(\t\"H\n\x1a\x42ulkTriggerWorkflowRequest\x12*\n\tworkflows\x18\x01 \x03(\x0b\x32\x17.TriggerWorkflowRequest\"7\n\x1b\x42ulkTriggerWorkflowResponse\x12\x18\n\x10workflow_run_ids\x18\x01 \x03(\t\"\x89\x03\n\x16TriggerWorkflowRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05input\x18\x02 \x01(\t\x12\x16\n\tparent_id\x18\x03 \x01(\tH\x00\x88\x01\x01\x12(\n\x1bparent_task_run_external_id\x18\x04 \x01(\tH\x01\x88\x01\x01\x12\x18\n\x0b\x63hild_index\x18\x05 \x01(\x05H\x02\x88\x01\x01\x12\x16\n\tchild_key\x18\x06 \x01(\tH\x03\x88\x01\x01\x12 \n\x13\x61\x64\x64itional_metadata\x18\x07 \x01(\tH\x04\x88\x01\x01\x12\x1e\n\x11\x64\x65sired_worker_id\x18\x08 \x01(\tH\x05\x88\x01\x01\x12\x15\n\x08priority\x18\t \x01(\x05H\x06\x88\x01\x01\x42\x0c\n\n_parent_idB\x1e\n\x1c_parent_task_run_external_idB\x0e\n\x0c_child_indexB\x0c\n\n_child_keyB\x16\n\x14_additional_metadataB\x14\n\x12_desired_worker_idB\x0b\n\t_priority\"2\n\x17TriggerWorkflowResponse\x12\x17\n\x0fworkflow_run_id\x18\x01 \x01(\t\"W\n\x13PutRateLimitRequest\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05limit\x18\x02 \x01(\x05\x12$\n\x08\x64uration\x18\x03 \x01(\x0e\x32\x12.RateLimitDuration\"\x16\n\x14PutRateLimitResponse*$\n\x0eStickyStrategy\x12\x08\n\x04SOFT\x10\x00\x12\x08\n\x04HARD\x10\x01*2\n\x0cWorkflowKind\x12\x0c\n\x08\x46UNCTION\x10\x00\x12\x0b\n\x07\x44URABLE\x10\x01\x12\x07\n\x03\x44\x41G\x10\x02*\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*\x85\x01\n\x15WorkerLabelComparator\x12\t\n\x05\x45QUAL\x10\x00\x12\r\n\tNOT_EQUAL\x10\x01\x12\x10\n\x0cGREATER_THAN\x10\x02\x12\x19\n\x15GREATER_THAN_OR_EQUAL\x10\x03\x12\r\n\tLESS_THAN\x10\x04\x12\x16\n\x12LESS_THAN_OR_EQUAL\x10\x05*]\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\x32\xdc\x02\n\x0fWorkflowService\x12\x34\n\x0bPutWorkflow\x12\x13.PutWorkflowRequest\x1a\x10.WorkflowVersion\x12>\n\x10ScheduleWorkflow\x12\x18.ScheduleWorkflowRequest\x1a\x10.WorkflowVersion\x12\x44\n\x0fTriggerWorkflow\x12\x17.TriggerWorkflowRequest\x1a\x18.TriggerWorkflowResponse\x12P\n\x13\x42ulkTriggerWorkflow\x12\x1b.BulkTriggerWorkflowRequest\x1a\x1c.BulkTriggerWorkflowResponse\x12;\n\x0cPutRateLimit\x12\x14.PutRateLimitRequest\x1a\x15.PutRateLimitResponseBBZ@github.com/hatchet-dev/hatchet/internal/services/admin/contractsb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0fworkflows.proto\x1a\x1fgoogle/protobuf/timestamp.proto\">\n\x12PutWorkflowRequest\x12(\n\x04opts\x18\x01 \x01(\x0b\x32\x1a.CreateWorkflowVersionOpts\"\xbf\x04\n\x19\x43reateWorkflowVersionOpts\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\x36\n\x12scheduled_triggers\x18\x06 \x03(\x0b\x32\x1a.google.protobuf.Timestamp\x12$\n\x04jobs\x18\x07 \x03(\x0b\x32\x16.CreateWorkflowJobOpts\x12-\n\x0b\x63oncurrency\x18\x08 \x01(\x0b\x32\x18.WorkflowConcurrencyOpts\x12\x1d\n\x10schedule_timeout\x18\t \x01(\tH\x00\x88\x01\x01\x12\x17\n\ncron_input\x18\n \x01(\tH\x01\x88\x01\x01\x12\x33\n\x0eon_failure_job\x18\x0b \x01(\x0b\x32\x16.CreateWorkflowJobOptsH\x02\x88\x01\x01\x12$\n\x06sticky\x18\x0c \x01(\x0e\x32\x0f.StickyStrategyH\x03\x88\x01\x01\x12 \n\x04kind\x18\r \x01(\x0e\x32\r.WorkflowKindH\x04\x88\x01\x01\x12\x1d\n\x10\x64\x65\x66\x61ult_priority\x18\x0e \x01(\x05H\x05\x88\x01\x01\x42\x13\n\x11_schedule_timeoutB\r\n\x0b_cron_inputB\x11\n\x0f_on_failure_jobB\t\n\x07_stickyB\x07\n\x05_kindB\x13\n\x11_default_priority\"\xd0\x01\n\x17WorkflowConcurrencyOpts\x12\x13\n\x06\x61\x63tion\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x15\n\x08max_runs\x18\x02 \x01(\x05H\x01\x88\x01\x01\x12\x36\n\x0elimit_strategy\x18\x03 \x01(\x0e\x32\x19.ConcurrencyLimitStrategyH\x02\x88\x01\x01\x12\x17\n\nexpression\x18\x04 \x01(\tH\x03\x88\x01\x01\x42\t\n\x07_actionB\x0b\n\t_max_runsB\x11\n\x0f_limit_strategyB\r\n\x0b_expression\"h\n\x15\x43reateWorkflowJobOpts\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12&\n\x05steps\x18\x04 \x03(\x0b\x32\x17.CreateWorkflowStepOptsJ\x04\x08\x03\x10\x04\"\xe5\x01\n\x13\x44\x65siredWorkerLabels\x12\x16\n\tstr_value\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x16\n\tint_value\x18\x02 \x01(\x05H\x01\x88\x01\x01\x12\x15\n\x08required\x18\x03 \x01(\x08H\x02\x88\x01\x01\x12/\n\ncomparator\x18\x04 \x01(\x0e\x32\x16.WorkerLabelComparatorH\x03\x88\x01\x01\x12\x13\n\x06weight\x18\x05 \x01(\x05H\x04\x88\x01\x01\x42\x0c\n\n_str_valueB\x0c\n\n_int_valueB\x0b\n\t_requiredB\r\n\x0b_comparatorB\t\n\x07_weight\"\xb5\x03\n\x16\x43reateWorkflowStepOpts\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\x11\n\tuser_data\x18\x06 \x01(\t\x12\x0f\n\x07retries\x18\x07 \x01(\x05\x12)\n\x0brate_limits\x18\x08 \x03(\x0b\x32\x14.CreateStepRateLimit\x12@\n\rworker_labels\x18\t \x03(\x0b\x32).CreateWorkflowStepOpts.WorkerLabelsEntry\x12\x1b\n\x0e\x62\x61\x63koff_factor\x18\n \x01(\x02H\x00\x88\x01\x01\x12 \n\x13\x62\x61\x63koff_max_seconds\x18\x0b \x01(\x05H\x01\x88\x01\x01\x1aI\n\x11WorkerLabelsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12#\n\x05value\x18\x02 \x01(\x0b\x32\x14.DesiredWorkerLabels:\x02\x38\x01\x42\x11\n\x0f_backoff_factorB\x16\n\x14_backoff_max_seconds\"\xfa\x01\n\x13\x43reateStepRateLimit\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\x12.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\"\x16\n\x14ListWorkflowsRequest\"\x83\x03\n\x17ScheduleWorkflowRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12-\n\tschedules\x18\x02 \x03(\x0b\x32\x1a.google.protobuf.Timestamp\x12\r\n\x05input\x18\x03 \x01(\t\x12\x16\n\tparent_id\x18\x04 \x01(\tH\x00\x88\x01\x01\x12(\n\x1bparent_task_run_external_id\x18\x05 \x01(\tH\x01\x88\x01\x01\x12\x18\n\x0b\x63hild_index\x18\x06 \x01(\x05H\x02\x88\x01\x01\x12\x16\n\tchild_key\x18\x07 \x01(\tH\x03\x88\x01\x01\x12 \n\x13\x61\x64\x64itional_metadata\x18\x08 \x01(\tH\x04\x88\x01\x01\x12\x15\n\x08priority\x18\t \x01(\x05H\x05\x88\x01\x01\x42\x0c\n\n_parent_idB\x1e\n\x1c_parent_task_run_external_idB\x0e\n\x0c_child_indexB\x0c\n\n_child_keyB\x16\n\x14_additional_metadataB\x0b\n\t_priority\"O\n\x11ScheduledWorkflow\x12\n\n\x02id\x18\x01 \x01(\t\x12.\n\ntrigger_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\xe3\x01\n\x0fWorkflowVersion\x12\n\n\x02id\x18\x01 \x01(\t\x12.\n\ncreated_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12.\n\nupdated_at\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x0f\n\x07version\x18\x05 \x01(\t\x12\r\n\x05order\x18\x06 \x01(\x03\x12\x13\n\x0bworkflow_id\x18\x07 \x01(\t\x12/\n\x13scheduled_workflows\x18\x08 \x03(\x0b\x32\x12.ScheduledWorkflow\"?\n\x17WorkflowTriggerEventRef\x12\x11\n\tparent_id\x18\x01 \x01(\t\x12\x11\n\tevent_key\x18\x02 \x01(\t\"9\n\x16WorkflowTriggerCronRef\x12\x11\n\tparent_id\x18\x01 \x01(\t\x12\x0c\n\x04\x63ron\x18\x02 \x01(\t\"H\n\x1a\x42ulkTriggerWorkflowRequest\x12*\n\tworkflows\x18\x01 \x03(\x0b\x32\x17.TriggerWorkflowRequest\"7\n\x1b\x42ulkTriggerWorkflowResponse\x12\x18\n\x10workflow_run_ids\x18\x01 \x03(\t\"\xbb\x03\n\x16TriggerWorkflowRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05input\x18\x02 \x01(\t\x12\x16\n\tparent_id\x18\x03 \x01(\tH\x00\x88\x01\x01\x12(\n\x1bparent_task_run_external_id\x18\x04 \x01(\tH\x01\x88\x01\x01\x12\x18\n\x0b\x63hild_index\x18\x05 \x01(\x05H\x02\x88\x01\x01\x12\x16\n\tchild_key\x18\x06 \x01(\tH\x03\x88\x01\x01\x12 \n\x13\x61\x64\x64itional_metadata\x18\x07 \x01(\tH\x04\x88\x01\x01\x12\x1e\n\x11\x64\x65sired_worker_id\x18\x08 \x01(\tH\x05\x88\x01\x01\x12\x15\n\x08priority\x18\t \x01(\x05H\x06\x88\x01\x01\x12\x1c\n\x0fidempotency_key\x18\n \x01(\tH\x07\x88\x01\x01\x42\x0c\n\n_parent_idB\x1e\n\x1c_parent_task_run_external_idB\x0e\n\x0c_child_indexB\x0c\n\n_child_keyB\x16\n\x14_additional_metadataB\x14\n\x12_desired_worker_idB\x0b\n\t_priorityB\x12\n\x10_idempotency_key\"2\n\x17TriggerWorkflowResponse\x12\x17\n\x0fworkflow_run_id\x18\x01 \x01(\t\"W\n\x13PutRateLimitRequest\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05limit\x18\x02 \x01(\x05\x12$\n\x08\x64uration\x18\x03 \x01(\x0e\x32\x12.RateLimitDuration\"\x16\n\x14PutRateLimitResponse*$\n\x0eStickyStrategy\x12\x08\n\x04SOFT\x10\x00\x12\x08\n\x04HARD\x10\x01*2\n\x0cWorkflowKind\x12\x0c\n\x08\x46UNCTION\x10\x00\x12\x0b\n\x07\x44URABLE\x10\x01\x12\x07\n\x03\x44\x41G\x10\x02*\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*\x85\x01\n\x15WorkerLabelComparator\x12\t\n\x05\x45QUAL\x10\x00\x12\r\n\tNOT_EQUAL\x10\x01\x12\x10\n\x0cGREATER_THAN\x10\x02\x12\x19\n\x15GREATER_THAN_OR_EQUAL\x10\x03\x12\r\n\tLESS_THAN\x10\x04\x12\x16\n\x12LESS_THAN_OR_EQUAL\x10\x05*]\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\x32\xdc\x02\n\x0fWorkflowService\x12\x34\n\x0bPutWorkflow\x12\x13.PutWorkflowRequest\x1a\x10.WorkflowVersion\x12>\n\x10ScheduleWorkflow\x12\x18.ScheduleWorkflowRequest\x1a\x10.WorkflowVersion\x12\x44\n\x0fTriggerWorkflow\x12\x17.TriggerWorkflowRequest\x1a\x18.TriggerWorkflowResponse\x12P\n\x13\x42ulkTriggerWorkflow\x12\x1b.BulkTriggerWorkflowRequest\x1a\x1c.BulkTriggerWorkflowResponse\x12;\n\x0cPutRateLimit\x12\x14.PutRateLimitRequest\x1a\x15.PutRateLimitResponseBBZ@github.com/hatchet-dev/hatchet/internal/services/admin/contractsb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -35,16 +35,16 @@ _globals['DESCRIPTOR']._serialized_options = b'Z@github.com/hatchet-dev/hatchet/internal/services/admin/contracts' _globals['_CREATEWORKFLOWSTEPOPTS_WORKERLABELSENTRY']._loaded_options = None _globals['_CREATEWORKFLOWSTEPOPTS_WORKERLABELSENTRY']._serialized_options = b'8\001' - _globals['_STICKYSTRATEGY']._serialized_start=3477 - _globals['_STICKYSTRATEGY']._serialized_end=3513 - _globals['_WORKFLOWKIND']._serialized_start=3515 - _globals['_WORKFLOWKIND']._serialized_end=3565 - _globals['_CONCURRENCYLIMITSTRATEGY']._serialized_start=3567 - _globals['_CONCURRENCYLIMITSTRATEGY']._serialized_end=3694 - _globals['_WORKERLABELCOMPARATOR']._serialized_start=3697 - _globals['_WORKERLABELCOMPARATOR']._serialized_end=3830 - _globals['_RATELIMITDURATION']._serialized_start=3832 - _globals['_RATELIMITDURATION']._serialized_end=3925 + _globals['_STICKYSTRATEGY']._serialized_start=3527 + _globals['_STICKYSTRATEGY']._serialized_end=3563 + _globals['_WORKFLOWKIND']._serialized_start=3565 + _globals['_WORKFLOWKIND']._serialized_end=3615 + _globals['_CONCURRENCYLIMITSTRATEGY']._serialized_start=3617 + _globals['_CONCURRENCYLIMITSTRATEGY']._serialized_end=3744 + _globals['_WORKERLABELCOMPARATOR']._serialized_start=3747 + _globals['_WORKERLABELCOMPARATOR']._serialized_end=3880 + _globals['_RATELIMITDURATION']._serialized_start=3882 + _globals['_RATELIMITDURATION']._serialized_end=3975 _globals['_PUTWORKFLOWREQUEST']._serialized_start=52 _globals['_PUTWORKFLOWREQUEST']._serialized_end=114 _globals['_CREATEWORKFLOWVERSIONOPTS']._serialized_start=117 @@ -78,13 +78,13 @@ _globals['_BULKTRIGGERWORKFLOWRESPONSE']._serialized_start=2859 _globals['_BULKTRIGGERWORKFLOWRESPONSE']._serialized_end=2914 _globals['_TRIGGERWORKFLOWREQUEST']._serialized_start=2917 - _globals['_TRIGGERWORKFLOWREQUEST']._serialized_end=3310 - _globals['_TRIGGERWORKFLOWRESPONSE']._serialized_start=3312 - _globals['_TRIGGERWORKFLOWRESPONSE']._serialized_end=3362 - _globals['_PUTRATELIMITREQUEST']._serialized_start=3364 - _globals['_PUTRATELIMITREQUEST']._serialized_end=3451 - _globals['_PUTRATELIMITRESPONSE']._serialized_start=3453 - _globals['_PUTRATELIMITRESPONSE']._serialized_end=3475 - _globals['_WORKFLOWSERVICE']._serialized_start=3928 - _globals['_WORKFLOWSERVICE']._serialized_end=4276 + _globals['_TRIGGERWORKFLOWREQUEST']._serialized_end=3360 + _globals['_TRIGGERWORKFLOWRESPONSE']._serialized_start=3362 + _globals['_TRIGGERWORKFLOWRESPONSE']._serialized_end=3412 + _globals['_PUTRATELIMITREQUEST']._serialized_start=3414 + _globals['_PUTRATELIMITREQUEST']._serialized_end=3501 + _globals['_PUTRATELIMITRESPONSE']._serialized_start=3503 + _globals['_PUTRATELIMITRESPONSE']._serialized_end=3525 + _globals['_WORKFLOWSERVICE']._serialized_start=3978 + _globals['_WORKFLOWSERVICE']._serialized_end=4326 # @@protoc_insertion_point(module_scope) diff --git a/sdks/python/hatchet_sdk/contracts/workflows_pb2.pyi b/sdks/python/hatchet_sdk/contracts/workflows_pb2.pyi index 686b99e332..81e026ec8e 100644 --- a/sdks/python/hatchet_sdk/contracts/workflows_pb2.pyi +++ b/sdks/python/hatchet_sdk/contracts/workflows_pb2.pyi @@ -143,7 +143,7 @@ class DesiredWorkerLabels(_message.Message): required: bool comparator: WorkerLabelComparator weight: int - def __init__(self, str_value: _Optional[str] = ..., int_value: _Optional[int] = ..., required: bool = ..., comparator: _Optional[_Union[WorkerLabelComparator, str]] = ..., weight: _Optional[int] = ...) -> None: ... + def __init__(self, str_value: _Optional[str] = ..., int_value: _Optional[int] = ..., required: _Optional[bool] = ..., comparator: _Optional[_Union[WorkerLabelComparator, str]] = ..., weight: _Optional[int] = ...) -> None: ... class CreateWorkflowStepOpts(_message.Message): __slots__ = ("readable_id", "action", "timeout", "inputs", "parents", "user_data", "retries", "rate_limits", "worker_labels", "backoff_factor", "backoff_max_seconds") @@ -275,7 +275,7 @@ class BulkTriggerWorkflowResponse(_message.Message): def __init__(self, workflow_run_ids: _Optional[_Iterable[str]] = ...) -> None: ... class TriggerWorkflowRequest(_message.Message): - __slots__ = ("name", "input", "parent_id", "parent_task_run_external_id", "child_index", "child_key", "additional_metadata", "desired_worker_id", "priority") + __slots__ = ("name", "input", "parent_id", "parent_task_run_external_id", "child_index", "child_key", "additional_metadata", "desired_worker_id", "priority", "idempotency_key") NAME_FIELD_NUMBER: _ClassVar[int] INPUT_FIELD_NUMBER: _ClassVar[int] PARENT_ID_FIELD_NUMBER: _ClassVar[int] @@ -285,6 +285,7 @@ class TriggerWorkflowRequest(_message.Message): ADDITIONAL_METADATA_FIELD_NUMBER: _ClassVar[int] DESIRED_WORKER_ID_FIELD_NUMBER: _ClassVar[int] PRIORITY_FIELD_NUMBER: _ClassVar[int] + IDEMPOTENCY_KEY_FIELD_NUMBER: _ClassVar[int] name: str input: str parent_id: str @@ -294,7 +295,8 @@ class TriggerWorkflowRequest(_message.Message): additional_metadata: str desired_worker_id: str priority: int - def __init__(self, name: _Optional[str] = ..., input: _Optional[str] = ..., parent_id: _Optional[str] = ..., parent_task_run_external_id: _Optional[str] = ..., child_index: _Optional[int] = ..., child_key: _Optional[str] = ..., additional_metadata: _Optional[str] = ..., desired_worker_id: _Optional[str] = ..., priority: _Optional[int] = ...) -> None: ... + idempotency_key: str + def __init__(self, name: _Optional[str] = ..., input: _Optional[str] = ..., parent_id: _Optional[str] = ..., parent_task_run_external_id: _Optional[str] = ..., child_index: _Optional[int] = ..., child_key: _Optional[str] = ..., additional_metadata: _Optional[str] = ..., desired_worker_id: _Optional[str] = ..., priority: _Optional[int] = ..., idempotency_key: _Optional[str] = ...) -> None: ... class TriggerWorkflowResponse(_message.Message): __slots__ = ("workflow_run_id",) diff --git a/sdks/typescript/generate-protoc.sh b/sdks/typescript/generate-protoc.sh index 39c8ea155f..b93d9fe1ca 100755 --- a/sdks/typescript/generate-protoc.sh +++ b/sdks/typescript/generate-protoc.sh @@ -1,3 +1,7 @@ +#!/usr/bin/env bash + +set -euo pipefail + # Directory to write generated code to (.js and .d.ts files) OUT_DIR="./src/protoc" @@ -8,12 +12,22 @@ else IN_DIR="../../api-contracts" fi -# Generate code -./node_modules/.bin/grpc_tools_node_protoc \ +# Generate code. +# Prefer system `protoc` (available in Nix environments) over `grpc-tools`' bundled protoc. +PROTOC_BIN="${PROTOC_BIN:-}" +if [ -z "$PROTOC_BIN" ]; then + if command -v protoc >/dev/null 2>&1; then + PROTOC_BIN="protoc" + else + PROTOC_BIN="./node_modules/.bin/grpc_tools_node_protoc" + fi +fi + +"$PROTOC_BIN" \ --plugin=protoc-gen-ts_proto=./node_modules/.bin/protoc-gen-ts_proto \ - --ts_proto_out=$OUT_DIR \ + --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="$IN_DIR" \ + $(find "$IN_DIR" -type f -name '*.proto' -print) pnpm lint:fix diff --git a/sdks/typescript/src/clients/admin/admin-client.ts b/sdks/typescript/src/clients/admin/admin-client.ts index 74c34c7027..f1ab430e27 100644 --- a/sdks/typescript/src/clients/admin/admin-client.ts +++ b/sdks/typescript/src/clients/admin/admin-client.ts @@ -52,6 +52,7 @@ export type WorkflowRun = { parentStepRunId?: string | undefined; childIndex?: number | undefined; childKey?: string | undefined; + idempotencyKey?: string | undefined; additionalMetadata?: Record | undefined; }; }; @@ -171,6 +172,7 @@ export class AdminClient { parentStepRunId?: string | undefined; childIndex?: number | undefined; childKey?: string | undefined; + idempotencyKey?: string | undefined; additionalMetadata?: Record | undefined; } ) { @@ -203,6 +205,7 @@ export class AdminClient { parentStepRunId?: string | undefined; childIndex?: number | undefined; childKey?: string | undefined; + idempotencyKey?: string | undefined; additionalMetadata?: Record | undefined; desiredWorkerId?: string | undefined; priority?: Priority; @@ -256,6 +259,7 @@ export class AdminClient { parentStepRunId?: string | undefined; childIndex?: number | undefined; childKey?: string | undefined; + idempotencyKey?: string | undefined; additionalMetadata?: Record | undefined; desiredWorkerId?: string | undefined; priority?: Priority; diff --git a/sdks/typescript/src/protoc/dispatcher/dispatcher.ts b/sdks/typescript/src/protoc/dispatcher/dispatcher.ts index 439534be2e..e4041127c2 100644 --- a/sdks/typescript/src/protoc/dispatcher/dispatcher.ts +++ b/sdks/typescript/src/protoc/dispatcher/dispatcher.ts @@ -1,7 +1,7 @@ // Code generated by protoc-gen-ts_proto. DO NOT EDIT. // versions: // protoc-gen-ts_proto v2.7.7 -// protoc v3.19.1 +// protoc v6.32.1 // source: dispatcher/dispatcher.proto /* eslint-disable */ diff --git a/sdks/typescript/src/protoc/events/events.ts b/sdks/typescript/src/protoc/events/events.ts index e1a741bab4..edcaf9a009 100644 --- a/sdks/typescript/src/protoc/events/events.ts +++ b/sdks/typescript/src/protoc/events/events.ts @@ -1,7 +1,7 @@ // Code generated by protoc-gen-ts_proto. DO NOT EDIT. // versions: // protoc-gen-ts_proto v2.7.7 -// protoc v3.19.1 +// protoc v6.32.1 // source: events/events.proto /* eslint-disable */ diff --git a/sdks/typescript/src/protoc/google/protobuf/timestamp.ts b/sdks/typescript/src/protoc/google/protobuf/timestamp.ts index 1d907b84f6..a947b916f2 100644 --- a/sdks/typescript/src/protoc/google/protobuf/timestamp.ts +++ b/sdks/typescript/src/protoc/google/protobuf/timestamp.ts @@ -1,7 +1,7 @@ // Code generated by protoc-gen-ts_proto. DO NOT EDIT. // versions: // protoc-gen-ts_proto v2.7.7 -// protoc v3.19.1 +// protoc v6.32.1 // source: google/protobuf/timestamp.proto /* eslint-disable */ @@ -97,7 +97,7 @@ export const protobufPackage = 'google.protobuf'; * [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with * the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use * the Joda Time's [`ISODateTimeFormat.dateTime()`]( - * http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D + * http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime() * ) to obtain a formatter capable of generating timestamps in this format. */ export interface Timestamp { diff --git a/sdks/typescript/src/protoc/v1/dispatcher.ts b/sdks/typescript/src/protoc/v1/dispatcher.ts index 8df1d1e8e9..ba027c089b 100644 --- a/sdks/typescript/src/protoc/v1/dispatcher.ts +++ b/sdks/typescript/src/protoc/v1/dispatcher.ts @@ -1,7 +1,7 @@ // Code generated by protoc-gen-ts_proto. DO NOT EDIT. // versions: // protoc-gen-ts_proto v2.7.7 -// protoc v3.19.1 +// protoc v6.32.1 // source: v1/dispatcher.proto /* eslint-disable */ diff --git a/sdks/typescript/src/protoc/v1/shared/condition.ts b/sdks/typescript/src/protoc/v1/shared/condition.ts index 422f935d81..95540adaf8 100644 --- a/sdks/typescript/src/protoc/v1/shared/condition.ts +++ b/sdks/typescript/src/protoc/v1/shared/condition.ts @@ -1,7 +1,7 @@ // Code generated by protoc-gen-ts_proto. DO NOT EDIT. // versions: // protoc-gen-ts_proto v2.7.7 -// protoc v3.19.1 +// protoc v6.32.1 // source: v1/shared/condition.proto /* eslint-disable */ diff --git a/sdks/typescript/src/protoc/v1/workflows.ts b/sdks/typescript/src/protoc/v1/workflows.ts index 714362f4e7..6b7a7bd6a4 100644 --- a/sdks/typescript/src/protoc/v1/workflows.ts +++ b/sdks/typescript/src/protoc/v1/workflows.ts @@ -1,7 +1,7 @@ // Code generated by protoc-gen-ts_proto. DO NOT EDIT. // versions: // protoc-gen-ts_proto v2.7.7 -// protoc v3.19.1 +// protoc v6.32.1 // source: v1/workflows.proto /* eslint-disable */ @@ -302,6 +302,8 @@ export interface TriggerWorkflowRunRequest { input: Uint8Array; additionalMetadata: Uint8Array; priority?: number | undefined; + /** (optional) idempotency key for deduplicating workflow runs */ + idempotencyKey?: string | undefined; } export interface TriggerWorkflowRunResponse { @@ -901,6 +903,7 @@ function createBaseTriggerWorkflowRunRequest(): TriggerWorkflowRunRequest { input: new Uint8Array(0), additionalMetadata: new Uint8Array(0), priority: undefined, + idempotencyKey: undefined, }; } @@ -921,6 +924,9 @@ export const TriggerWorkflowRunRequest: MessageFns = if (message.priority !== undefined) { writer.uint32(32).int32(message.priority); } + if (message.idempotencyKey !== undefined) { + writer.uint32(42).string(message.idempotencyKey); + } return writer; }, @@ -963,6 +969,14 @@ export const TriggerWorkflowRunRequest: MessageFns = message.priority = reader.int32(); continue; } + case 5: { + if (tag !== 42) { + break; + } + + message.idempotencyKey = reader.string(); + continue; + } } if ((tag & 7) === 4 || tag === 0) { break; @@ -980,6 +994,9 @@ export const TriggerWorkflowRunRequest: MessageFns = ? bytesFromBase64(object.additionalMetadata) : new Uint8Array(0), priority: isSet(object.priority) ? globalThis.Number(object.priority) : undefined, + idempotencyKey: isSet(object.idempotencyKey) + ? globalThis.String(object.idempotencyKey) + : undefined, }; }, @@ -997,6 +1014,9 @@ export const TriggerWorkflowRunRequest: MessageFns = if (message.priority !== undefined) { obj.priority = Math.round(message.priority); } + if (message.idempotencyKey !== undefined) { + obj.idempotencyKey = message.idempotencyKey; + } return obj; }, @@ -1009,6 +1029,7 @@ export const TriggerWorkflowRunRequest: MessageFns = message.input = object.input ?? new Uint8Array(0); message.additionalMetadata = object.additionalMetadata ?? new Uint8Array(0); message.priority = object.priority ?? undefined; + message.idempotencyKey = object.idempotencyKey ?? undefined; return message; }, }; diff --git a/sdks/typescript/src/protoc/workflows/workflows.ts b/sdks/typescript/src/protoc/workflows/workflows.ts index 7c93d3cfb4..547e85a46b 100644 --- a/sdks/typescript/src/protoc/workflows/workflows.ts +++ b/sdks/typescript/src/protoc/workflows/workflows.ts @@ -1,7 +1,7 @@ // Code generated by protoc-gen-ts_proto. DO NOT EDIT. // versions: // protoc-gen-ts_proto v2.7.7 -// protoc v3.19.1 +// protoc v6.32.1 // source: workflows/workflows.proto /* eslint-disable */ @@ -476,6 +476,8 @@ export interface TriggerWorkflowRequest { desiredWorkerId?: string | undefined; /** (optional) override for the priority of the workflow tasks, will set all tasks to this priority */ priority?: number | undefined; + /** (optional) idempotency key for deduplicating workflow runs */ + idempotencyKey?: string | undefined; } export interface TriggerWorkflowResponse { @@ -2498,6 +2500,7 @@ function createBaseTriggerWorkflowRequest(): TriggerWorkflowRequest { additionalMetadata: undefined, desiredWorkerId: undefined, priority: undefined, + idempotencyKey: undefined, }; } @@ -2530,6 +2533,9 @@ export const TriggerWorkflowRequest: MessageFns = { if (message.priority !== undefined) { writer.uint32(72).int32(message.priority); } + if (message.idempotencyKey !== undefined) { + writer.uint32(82).string(message.idempotencyKey); + } return writer; }, @@ -2612,6 +2618,14 @@ export const TriggerWorkflowRequest: MessageFns = { message.priority = reader.int32(); continue; } + case 10: { + if (tag !== 82) { + break; + } + + message.idempotencyKey = reader.string(); + continue; + } } if ((tag & 7) === 4 || tag === 0) { break; @@ -2638,6 +2652,9 @@ export const TriggerWorkflowRequest: MessageFns = { ? globalThis.String(object.desiredWorkerId) : undefined, priority: isSet(object.priority) ? globalThis.Number(object.priority) : undefined, + idempotencyKey: isSet(object.idempotencyKey) + ? globalThis.String(object.idempotencyKey) + : undefined, }; }, @@ -2670,6 +2687,9 @@ export const TriggerWorkflowRequest: MessageFns = { if (message.priority !== undefined) { obj.priority = Math.round(message.priority); } + if (message.idempotencyKey !== undefined) { + obj.idempotencyKey = message.idempotencyKey; + } return obj; }, @@ -2687,6 +2707,7 @@ export const TriggerWorkflowRequest: MessageFns = { message.additionalMetadata = object.additionalMetadata ?? undefined; message.desiredWorkerId = object.desiredWorkerId ?? undefined; message.priority = object.priority ?? undefined; + message.idempotencyKey = object.idempotencyKey ?? undefined; return message; }, }; diff --git a/sdks/typescript/src/v1/client/admin.ts b/sdks/typescript/src/v1/client/admin.ts index e0197edcad..5dfa3030f7 100644 --- a/sdks/typescript/src/v1/client/admin.ts +++ b/sdks/typescript/src/v1/client/admin.ts @@ -34,6 +34,7 @@ export type WorkflowRun = { parentStepRunId?: string | undefined; childIndex?: number | undefined; childKey?: string | undefined; + idempotencyKey?: string | undefined; additionalMetadata?: Record | undefined; }; }; @@ -81,6 +82,7 @@ export class AdminClient { parentStepRunId?: string | undefined; childIndex?: number | undefined; childKey?: string | undefined; + idempotencyKey?: string | undefined; additionalMetadata?: Record | undefined; desiredWorkerId?: string | undefined; priority?: Priority; @@ -149,6 +151,7 @@ export class AdminClient { parentStepRunId?: string | undefined; childIndex?: number | undefined; childKey?: string | undefined; + idempotencyKey?: string | undefined; additionalMetadata?: Record | undefined; desiredWorkerId?: string | undefined; priority?: Priority; diff --git a/sdks/typescript/src/v1/declaration.ts b/sdks/typescript/src/v1/declaration.ts index 090b86dc67..07063c82dd 100644 --- a/sdks/typescript/src/v1/declaration.ts +++ b/sdks/typescript/src/v1/declaration.ts @@ -56,6 +56,11 @@ export type RunOpts = { */ priority?: Priority; + /** + * (optional) idempotency key for deduplicating workflow runs. + */ + idempotencyKey?: string; + /** * (optional) if the task run should be run on the same worker. * only used if spawned from within a parent task. diff --git a/sql/schema/v1-core.sql b/sql/schema/v1-core.sql index e9ae7cda15..e8b227c8cd 100644 --- a/sql/schema/v1-core.sql +++ b/sql/schema/v1-core.sql @@ -2138,6 +2138,7 @@ CREATE TABLE v1_idempotency_key ( expires_at TIMESTAMPTZ NOT NULL, claimed_by_external_id UUID, + last_denied_at TIMESTAMPTZ, inserted_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,