diff --git a/backend/controller/controller.go b/backend/controller/controller.go index 3b79041c1..84177fc65 100644 --- a/backend/controller/controller.go +++ b/backend/controller/controller.go @@ -831,17 +831,13 @@ func (s *Service) Call(ctx context.Context, req *connect.Request[ftlv1.CallReque func (s *Service) SendFSMEvent(ctx context.Context, req *connect.Request[ftlv1.SendFSMEventRequest]) (resp *connect.Response[ftlv1.SendFSMEventResponse], err error) { msg := req.Msg - sch := s.schema.Load() + // Resolve the FSM. - fsm := &schema.FSM{} - if err := sch.ResolveToType(schema.RefFromProto(msg.Fsm), fsm); err != nil { - return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("fsm not found: %w", err)) + fsm, eventType, fsmKey, err := s.resolveFSMEvent(msg) + if err != nil { + return nil, connect.NewError(connect.CodeNotFound, err) } - eventType := schema.TypeFromProto(msg.Event) - - fsmKey := schema.RefFromProto(msg.Fsm).ToRefKey() - tx, err := s.dal.Begin(ctx) if err != nil { return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("could not start transaction: %w", err)) @@ -854,12 +850,24 @@ func (s *Service) SendFSMEvent(ctx context.Context, req *connect.Request[ftlv1.S } defer instance.Release() //nolint:errcheck + err = s.sendFSMEventInTx(ctx, tx, instance, fsm, eventType, msg.Body, false) + if err != nil { + return nil, err + } + return connect.NewResponse(&ftlv1.SendFSMEventResponse{}), nil +} + +// schedules an event for a FSM instance within a db transaction +// body may already be encrypted, which is denoted by the encrypted flag +func (s *Service) sendFSMEventInTx(ctx context.Context, tx *dal.Tx, instance *dal.FSMInstance, fsm *schema.FSM, eventType schema.Type, body []byte, encrypted bool) error { // Populated if we find a matching transition. var destinationRef *schema.Ref var destinationVerb *schema.Verb var candidates []string + sch := s.schema.Load() + updateCandidates := func(ref *schema.Ref) (brk bool, err error) { verb := &schema.Verb{} if err := sch.ResolveToType(ref, verb); err != nil { @@ -879,7 +887,7 @@ func (s *Service) SendFSMEvent(ctx context.Context, req *connect.Request[ftlv1.S if !instance.CurrentState.Ok() { for _, start := range fsm.Start { if brk, err := updateCandidates(start); err != nil { - return nil, err + return err } else if brk { break } @@ -892,7 +900,7 @@ func (s *Service) SendFSMEvent(ctx context.Context, req *connect.Request[ftlv1.S continue } if brk, err := updateCandidates(transition.To); err != nil { - return nil, err + return err } else if brk { break } @@ -901,20 +909,59 @@ func (s *Service) SendFSMEvent(ctx context.Context, req *connect.Request[ftlv1.S if destinationRef == nil { if len(candidates) > 0 { - return nil, connect.NewError(connect.CodeFailedPrecondition, + return connect.NewError(connect.CodeFailedPrecondition, fmt.Errorf("no transition found from state %s for type %s, candidates are %s", instance.CurrentState, eventType, strings.Join(candidates, ", "))) } - return nil, connect.NewError(connect.CodeFailedPrecondition, fmt.Errorf("no transition found from state %s for type %s", instance.CurrentState, eventType)) + return connect.NewError(connect.CodeFailedPrecondition, fmt.Errorf("no transition found from state %s for type %s", instance.CurrentState, eventType)) } retryParams, err := schema.RetryParamsForFSMTransition(fsm, destinationVerb) if err != nil { - return nil, connect.NewError(connect.CodeInternal, err) + return connect.NewError(connect.CodeInternal, err) } - err = tx.StartFSMTransition(ctx, instance.FSM, instance.Key, destinationRef.ToRefKey(), msg.Body, retryParams) + err = tx.StartFSMTransition(ctx, instance.FSM, instance.Key, destinationRef.ToRefKey(), body, encrypted, retryParams) if err != nil { - return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("could not start fsm transition: %w", err)) + return connect.NewError(connect.CodeInternal, fmt.Errorf("could not start fsm transition: %w", err)) + } + return nil +} + +func (s *Service) SetNextFSMEvent(ctx context.Context, req *connect.Request[ftlv1.SendFSMEventRequest]) (resp *connect.Response[ftlv1.SendFSMEventResponse], err error) { + tx, err := s.dal.Begin(ctx) + if err != nil { + return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("could not start transaction: %w", err)) + } + defer tx.CommitOrRollback(ctx, &err) + sch := s.schema.Load() + msg := req.Msg + fsm, eventType, fsmKey, err := s.resolveFSMEvent(msg) + if err != nil { + return nil, connect.NewError(connect.CodeNotFound, err) + } + + // Get the current state the instance is transitioning to. + _, currentDestinationState, err := tx.GetFSMStates(ctx, fsmKey, req.Msg.Instance) + if err != nil { + if errors.Is(err, dalerrs.ErrNotFound) { + return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("fsm instance not found: %w", err)) + } + return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("could not get fsm instance: %w", err)) + } + + // Check if the transition is valid from the current state. + nextState, ok := fsm.NextState(sch, currentDestinationState, eventType).Get() + if !ok { + return nil, connect.NewError(connect.CodeFailedPrecondition, fmt.Errorf("invalid event %q for state %q", eventType, currentDestinationState)) + } + + // Set the next event. + err = tx.SetNextFSMEvent(ctx, fsmKey, msg.Instance, nextState.ToRefKey(), msg.Body, eventType) + if err != nil { + if errors.Is(err, dalerrs.ErrConflict) { + return nil, connect.NewError(connect.CodeFailedPrecondition, fmt.Errorf("fsm instance already has its next state set: %w", err)) + } + return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("could not set next fsm event: %w", err)) } return connect.NewResponse(&ftlv1.SendFSMEventResponse{}), nil } @@ -1406,8 +1453,9 @@ func (s *Service) executeAsyncCalls(ctx context.Context) (interval time.Duration logger.Tracef("Executing async call") req := &ftlv1.CallRequest{ - Verb: call.Verb.ToProto(), - Body: call.Request, + Verb: call.Verb.ToProto(), + Body: call.Request, + Metadata: metadataForAsyncCall(call), } resp, err := s.callWithRequest(ctx, connect.NewRequest(req), optional.None[model.RequestKey](), parentRequestKey, s.config.Advertise.String()) var callResult either.Either[[]byte, string] @@ -1464,8 +1512,9 @@ func (s *Service) catchAsyncCall(ctx context.Context, logger *log.Logger, call * } req := &ftlv1.CallRequest{ - Verb: catchVerb.ToProto(), - Body: body, + Verb: catchVerb.ToProto(), + Body: body, + Metadata: metadataForAsyncCall(call), } resp, err := s.callWithRequest(ctx, connect.NewRequest(req), optional.None[model.RequestKey](), optional.None[model.RequestKey](), s.config.Advertise.String()) var catchResult either.Either[[]byte, string] @@ -1503,23 +1552,42 @@ func (s *Service) catchAsyncCall(ctx context.Context, logger *log.Logger, call * return nil } -func (s *Service) finaliseAsyncCall(ctx context.Context, tx *dal.Tx, call *dal.AsyncCall, callResult either.Either[[]byte, string], isFinalResult bool) error { - if !isFinalResult { - // Will retry, do not propagate yet. - return nil +func metadataForAsyncCall(call *dal.AsyncCall) *ftlv1.Metadata { + switch origin := call.Origin.(type) { + case dal.AsyncOriginFSM: + return &ftlv1.Metadata{ + Values: []*ftlv1.Metadata_Pair{ + { + Key: "fsmName", + Value: origin.FSM.Name, + }, + { + Key: "fsmInstance", + Value: origin.Key, + }, + }, + } + + case dal.AsyncOriginPubSub: + return &ftlv1.Metadata{} + + default: + panic(fmt.Errorf("unsupported async call origin: %v", call.Origin)) } +} +func (s *Service) finaliseAsyncCall(ctx context.Context, tx *dal.Tx, call *dal.AsyncCall, callResult either.Either[[]byte, string], isFinalResult bool) error { _, failed := callResult.(either.Right[[]byte, string]) // Allow for handling of completion based on origin switch origin := call.Origin.(type) { case dal.AsyncOriginFSM: - if err := s.onAsyncFSMCallCompletion(ctx, tx, origin, failed); err != nil { + if err := s.onAsyncFSMCallCompletion(ctx, tx, origin, failed, isFinalResult); err != nil { return fmt.Errorf("failed to finalize FSM async call: %w", err) } case dal.AsyncOriginPubSub: - if err := s.pubSub.OnCallCompletion(ctx, tx, origin, failed); err != nil { + if err := s.pubSub.OnCallCompletion(ctx, tx, origin, failed, isFinalResult); err != nil { return fmt.Errorf("failed to finalize pubsub async call: %w", err) } @@ -1529,12 +1597,22 @@ func (s *Service) finaliseAsyncCall(ctx context.Context, tx *dal.Tx, call *dal.A return nil } -func (s *Service) onAsyncFSMCallCompletion(ctx context.Context, tx *dal.Tx, origin dal.AsyncOriginFSM, failed bool) error { +func (s *Service) onAsyncFSMCallCompletion(ctx context.Context, tx *dal.Tx, origin dal.AsyncOriginFSM, failed bool, isFinalResult bool) error { logger := log.FromContext(ctx).Scope(origin.FSM.String()) + // retrieve the next fsm event and delete it + next, err := tx.PopNextFSMEvent(ctx, origin.FSM, origin.Key) + if err != nil { + return fmt.Errorf("%s: failed to get next FSM event: %w", origin, err) + } + if !isFinalResult { + // Will retry, so we only want next fsm to be removed + return nil + } + instance, err := tx.AcquireFSMInstance(ctx, origin.FSM, origin.Key) if err != nil { - return fmt.Errorf("could not acquire lock on FSM instance: %w", err) + return fmt.Errorf("%s: could not acquire lock on FSM instance: %w", origin, err) } defer instance.Release() //nolint:errcheck @@ -1542,7 +1620,7 @@ func (s *Service) onAsyncFSMCallCompletion(ctx context.Context, tx *dal.Tx, orig logger.Warnf("FSM %s failed async call", origin.FSM) err := tx.FailFSMInstance(ctx, origin.FSM, origin.Key) if err != nil { - return fmt.Errorf("failed to fail FSM instance: %w", err) + return fmt.Errorf("%s: failed to fail FSM instance: %w", origin, err) } return nil } @@ -1552,7 +1630,7 @@ func (s *Service) onAsyncFSMCallCompletion(ctx context.Context, tx *dal.Tx, orig fsm := &schema.FSM{} err = sch.ResolveToType(origin.FSM.ToRef(), fsm) if err != nil { - return fmt.Errorf("could not resolve FSM: %w", err) + return fmt.Errorf("%s: could not resolve FSM: %w", origin, err) } destinationState, _ := instance.DestinationState.Get() @@ -1562,20 +1640,39 @@ func (s *Service) onAsyncFSMCallCompletion(ctx context.Context, tx *dal.Tx, orig logger.Debugf("FSM reached terminal state %s", destinationState) err := tx.SucceedFSMInstance(ctx, origin.FSM, origin.Key) if err != nil { - return fmt.Errorf("failed to succeed FSM instance: %w", err) + return fmt.Errorf("%s: failed to succeed FSM instance: %w", origin, err) } return nil } } - err = tx.FinishFSMTransition(ctx, origin.FSM, origin.Key) + instance, err = tx.FinishFSMTransition(ctx, instance) if err != nil { - return fmt.Errorf("failed to complete FSM transition: %w", err) + return fmt.Errorf("%s: failed to complete FSM transition: %w", origin, err) + } + + // If there's a next event enqueued, we immediately start it. + if next, ok := next.Get(); ok { + return s.sendFSMEventInTx(ctx, tx, instance, fsm, next.RequestType, next.Request, true) } return nil } +func (s *Service) resolveFSMEvent(msg *ftlv1.SendFSMEventRequest) (fsm *schema.FSM, eventType schema.Type, fsmKey schema.RefKey, err error) { + sch := s.schema.Load() + + fsm = &schema.FSM{} + if err := sch.ResolveToType(schema.RefFromProto(msg.Fsm), fsm); err != nil { + return nil, nil, schema.RefKey{}, fmt.Errorf("fsm not found: %w", err) + } + + eventType = schema.TypeFromProto(msg.Event) + + fsmKey = schema.RefFromProto(msg.Fsm).ToRefKey() + return fsm, eventType, fsmKey, nil +} + func (s *Service) expireStaleLeases(ctx context.Context) (time.Duration, error) { err := s.dal.ExpireLeases(ctx) if err != nil { diff --git a/backend/controller/cronjobs/sql/models.go b/backend/controller/cronjobs/sql/models.go index 5c2780936..0c59dbef5 100644 --- a/backend/controller/cronjobs/sql/models.go +++ b/backend/controller/cronjobs/sql/models.go @@ -447,6 +447,15 @@ type FsmInstance struct { UpdatedAt time.Time } +type FsmNextEvent struct { + ID int64 + CreatedAt time.Time + FsmInstanceID int64 + NextState schema.RefKey + Request []byte + RequestType Type +} + type IngressRoute struct { Method string Path string diff --git a/backend/controller/cronjobs/sql/types.go b/backend/controller/cronjobs/sql/types.go new file mode 100644 index 000000000..31097b10c --- /dev/null +++ b/backend/controller/cronjobs/sql/types.go @@ -0,0 +1,5 @@ +package sql + +import csql "github.com/TBD54566975/ftl/backend/controller/sql" + +type Type = csql.Type diff --git a/backend/controller/dal/fsm.go b/backend/controller/dal/fsm.go index 1311c76ed..b9b871b26 100644 --- a/backend/controller/dal/fsm.go +++ b/backend/controller/dal/fsm.go @@ -31,14 +31,19 @@ import ( // future execution. // // Note: no validation of the FSM is performed. -func (d *DAL) StartFSMTransition(ctx context.Context, fsm schema.RefKey, executionKey string, destinationState schema.RefKey, request json.RawMessage, retryParams schema.RetryParams) (err error) { - encryptedRequest, err := d.encryptJSON(encryption.AsyncSubKey, request) - if err != nil { - return fmt.Errorf("failed to encrypt FSM request: %w", err) +func (d *DAL) StartFSMTransition(ctx context.Context, fsm schema.RefKey, instanceKey string, destinationState schema.RefKey, request []byte, encrypted bool, retryParams schema.RetryParams) (err error) { + var encryptedRequest []byte + if encrypted { + encryptedRequest = request + } else { + encryptedRequest, err = d.encrypt(encryption.AsyncSubKey, request) + if err != nil { + return fmt.Errorf("failed to encrypt FSM request: %w", err) + } } // Create an async call for the event. - origin := AsyncOriginFSM{FSM: fsm, Key: executionKey} + origin := AsyncOriginFSM{FSM: fsm, Key: instanceKey} asyncCallID, err := d.db.CreateAsyncCall(ctx, sql.CreateAsyncCallParams{ Verb: destinationState, Origin: origin.String(), @@ -62,7 +67,7 @@ func (d *DAL) StartFSMTransition(ctx context.Context, fsm schema.RefKey, executi // Start a transition. instance, err := d.db.StartFSMTransition(ctx, sql.StartFSMTransitionParams{ Fsm: fsm, - Key: executionKey, + Key: instanceKey, DestinationState: destinationState, AsyncCallID: asyncCallID, }) @@ -80,11 +85,21 @@ func (d *DAL) StartFSMTransition(ctx context.Context, fsm schema.RefKey, executi return nil } -func (d *DAL) FinishFSMTransition(ctx context.Context, fsm schema.RefKey, instanceKey string) error { - _, err := d.db.FinishFSMTransition(ctx, fsm, instanceKey) - observability.FSM.TransitionCompleted(ctx, fsm) - - return dalerrs.TranslatePGError(err) +// FinishFSMTransition marks an FSM transition as completed +func (d *DAL) FinishFSMTransition(ctx context.Context, instance *FSMInstance) (*FSMInstance, error) { + _, err := d.db.FinishFSMTransition(ctx, instance.FSM, instance.Key) + observability.FSM.TransitionCompleted(ctx, instance.FSM) + if err != nil { + return nil, dalerrs.TranslatePGError(err) + } + return &FSMInstance{ + Lease: instance.Lease, + FSM: instance.FSM, + Key: instance.Key, + Status: instance.Status, + CurrentState: instance.DestinationState, + DestinationState: optional.None[schema.RefKey](), + }, nil } func (d *DAL) FailFSMInstance(ctx context.Context, fsm schema.RefKey, instanceKey string) error { @@ -99,6 +114,54 @@ func (d *DAL) SucceedFSMInstance(ctx context.Context, fsm schema.RefKey, instanc return dalerrs.TranslatePGError(err) } +func (d *DAL) GetFSMStates(ctx context.Context, fsm schema.RefKey, instanceKey string) (currentState, destinationState optional.Option[schema.RefKey], err error) { + instance, err := d.db.GetFSMInstance(ctx, fsm, instanceKey) + if err != nil { + return optional.None[schema.RefKey](), optional.None[schema.RefKey](), dalerrs.TranslatePGError(err) + } + return instance.CurrentState, instance.DestinationState, nil +} + +type NextFSMEvent struct { + DestinationState schema.RefKey + Request json.RawMessage + RequestType schema.Type +} + +// PopNextFSMEvent returns the next event for an FSM instance, if any, and deletes it. +func (d *DAL) PopNextFSMEvent(ctx context.Context, fsm schema.RefKey, instanceKey string) (optional.Option[NextFSMEvent], error) { + next, err := d.db.PopNextFSMEvent(ctx, fsm, instanceKey) + if err != nil { + err = dalerrs.TranslatePGError(err) + if errors.Is(err, dalerrs.ErrNotFound) { + return optional.None[NextFSMEvent](), nil + } + return optional.None[NextFSMEvent](), err + } + return optional.Some(NextFSMEvent{ + DestinationState: next.NextState, + Request: next.Request, + RequestType: next.RequestType, + }), nil +} + +func (d *DAL) SetNextFSMEvent(ctx context.Context, fsm schema.RefKey, instanceKey string, nextState schema.RefKey, request json.RawMessage, requestType schema.Type) error { + encryptedRequest, err := d.encryptJSON(encryption.AsyncSubKey, request) + if err != nil { + return fmt.Errorf("failed to encrypt FSM request: %w", err) + } + _, err = d.db.SetNextFSMEvent(ctx, sql.SetNextFSMEventParams{ + Fsm: fsm, + InstanceKey: instanceKey, + Event: nextState, + Request: encryptedRequest, + RequestType: sql.Type{ + Type: requestType, + }, + }) + return dalerrs.TranslatePGError(err) +} + type FSMStatus = sql.FsmStatus const ( diff --git a/backend/controller/dal/fsm_integration_test.go b/backend/controller/dal/fsm_integration_test.go index 2fee9701c..6ab7cad22 100644 --- a/backend/controller/dal/fsm_integration_test.go +++ b/backend/controller/dal/fsm_integration_test.go @@ -8,9 +8,10 @@ import ( "testing" "time" - "github.com/alecthomas/assert/v2" - in "github.com/TBD54566975/ftl/integration" + "github.com/TBD54566975/ftl/internal/slices" + "github.com/alecthomas/assert/v2" + "github.com/alecthomas/types/optional" ) func TestFSM(t *testing.T) { @@ -130,7 +131,73 @@ func TestFSMGoTests(t *testing.T) { t.Setenv("FSM_LOG_FILE", logFilePath) in.Run(t, in.CopyModule("fsm"), - in.Build("fsm"), + in.CopyModule("fsmnext"), + in.Build("fsm", "fsmnext"), in.ExecModuleTest("fsm"), + in.ExecModuleTest("fsmnext"), + ) +} + +func TestFSMNext(t *testing.T) { + transitionFSMWithOptions := func(instance string, nextAttempts int, maybeErr optional.Option[string], states ...string) in.Action { + if len(states) == 0 { + return func(t testing.TB, ic in.TestContext) {} + } + return in.Call[in.Obj, in.Obj]("fsmnext", "sendOne", in.Obj{ + "state": states[0], + "event": map[string]any{ + "instance": instance, + "nextStates": states[1:], + "nextAttempts": nextAttempts, + "error": maybeErr, + }, + }, nil) + } + transitionFSM := func(instance string, states ...string) in.Action { + return transitionFSMWithOptions(instance, 1, optional.None[string](), states...) + } + + checkAsyncCall := func(instance string, states ...string) in.Action { + actions := slices.Map(states, func(state string) in.Action { + return in.QueryRow("ftl", fmt.Sprintf("SELECT COUNT(*) FROM async_calls WHERE origin = 'fsm:fsmnext.fsm:%s' AND verb = 'fsmnext.state%s' AND state = 'success'", instance, state), int64(1)) + }) + return in.Chain(actions...) + } + + checkRepeatedAsyncCallError := func(instance string, state string, errorStr string) in.Action { + return func(t testing.TB, ic in.TestContext) { + // make sure each retry got the same error + for offset := range 3 { + result := in.GetRow(t, ic, "ftl", fmt.Sprintf("SELECT error FROM async_calls WHERE origin = 'fsm:fsmnext.fsm:%s' AND verb = 'fsmnext.state%s' AND state = 'error' ORDER BY created_at LIMIT 1 OFFSET %d", instance, state, offset), 1) + resultError, ok := result[0].(string) + assert.True(t, ok, "unexpected error type: %T", result[0]) + assert.Contains(t, resultError, errorStr, "unexpected error: %s", resultError) + } + } + } + + in.Run(t, + in.CopyModule("fsmnext"), + in.Deploy("fsmnext"), + + // Simple progression through each state + transitionFSM("1", "A", "B", "C", "D"), + + // Bad progression where fsm.Next() is called twice + transitionFSMWithOptions("2", 2, optional.None[string](), "A", "B"), + + // Schedule next and then error and retry. Each error should be the expected error, not a failure to schedule the next state + transitionFSMWithOptions("3", 1, optional.Some("computers are fun"), "A", "B"), + + // Bad progression + transitionFSM("4", "A", "B", "B"), + + in.Sleep(4*time.Second), + + checkAsyncCall("1", "A", "B", "C", "D"), + checkRepeatedAsyncCallError("2", "A", "fsm instance already has its next state set"), + // will get "fsm instance already has its next state set" if next event is not cleared properly + checkRepeatedAsyncCallError("3", "A", "computers are fun"), + checkRepeatedAsyncCallError("4", "B", `invalid event "fsmnext.EventB" for state "fsmnext.stateB"`), ) } diff --git a/backend/controller/dal/fsm_test.go b/backend/controller/dal/fsm_test.go index 7f1eabe03..abdb0166c 100644 --- a/backend/controller/dal/fsm_test.go +++ b/backend/controller/dal/fsm_test.go @@ -25,10 +25,10 @@ func TestSendFSMEvent(t *testing.T) { assert.IsError(t, err, dalerrs.ErrNotFound) ref := schema.RefKey{Module: "module", Name: "verb"} - err = dal.StartFSMTransition(ctx, schema.RefKey{Module: "test", Name: "test"}, "invoiceID", ref, []byte(`{}`), schema.RetryParams{}) + err = dal.StartFSMTransition(ctx, schema.RefKey{Module: "test", Name: "test"}, "invoiceID", ref, []byte(`{}`), false, schema.RetryParams{}) assert.NoError(t, err) - err = dal.StartFSMTransition(ctx, schema.RefKey{Module: "test", Name: "test"}, "invoiceID", ref, []byte(`{}`), schema.RetryParams{}) + err = dal.StartFSMTransition(ctx, schema.RefKey{Module: "test", Name: "test"}, "invoiceID", ref, []byte(`{}`), false, schema.RetryParams{}) assert.IsError(t, err, dalerrs.ErrConflict) assert.EqualError(t, err, "transition already executing: conflict") diff --git a/backend/controller/dal/testdata/go/fsm/go.mod b/backend/controller/dal/testdata/go/fsm/go.mod index 853bf7e14..19d4c0db8 100644 --- a/backend/controller/dal/testdata/go/fsm/go.mod +++ b/backend/controller/dal/testdata/go/fsm/go.mod @@ -21,6 +21,7 @@ require ( github.com/alecthomas/repr v0.4.0 // indirect github.com/alecthomas/types v0.16.0 // indirect github.com/alessio/shellescape v1.4.2 // indirect + github.com/amacneil/dbmate/v2 v2.19.0 // indirect github.com/aws/aws-sdk-go-v2 v1.30.3 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 // indirect @@ -42,6 +43,7 @@ require ( github.com/jackc/puddle/v2 v2.2.1 // indirect github.com/jpillora/backoff v1.0.0 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/lib/pq v1.10.9 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/multiformats/go-base36 v0.2.0 // indirect github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect diff --git a/backend/controller/dal/testdata/go/fsm/go.sum b/backend/controller/dal/testdata/go/fsm/go.sum index 225708ab0..156587b7e 100644 --- a/backend/controller/dal/testdata/go/fsm/go.sum +++ b/backend/controller/dal/testdata/go/fsm/go.sum @@ -4,6 +4,8 @@ connectrpc.com/grpcreflect v1.2.0 h1:Q6og1S7HinmtbEuBvARLNwYmTbhEGRpHDhqrPNlmK+U connectrpc.com/grpcreflect v1.2.0/go.mod h1:nwSOKmE8nU5u/CidgHtPYk1PFI3U9ignz7iDMxOYkSY= connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY= connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/TBD54566975/scaffolder v1.0.0 h1:QUFSy2wVzumLDg7IHcKC6AP+IYyqWe9Wxiu72nZn5qU= @@ -72,6 +74,8 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -104,6 +108,8 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= @@ -141,6 +147,8 @@ github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3Ifn github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= github.com/zalando/go-keyring v0.2.5 h1:Bc2HHpjALryKD62ppdEzaFG6VxL6Bc+5v0LYpN8Lba8= github.com/zalando/go-keyring v0.2.5/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= +github.com/zenizh/go-capturer v0.0.0-20211219060012-52ea6c8fed04 h1:qXafrlZL1WsJW5OokjraLLRURHiw0OzKHD/RNdspp4w= +github.com/zenizh/go-capturer v0.0.0-20211219060012-52ea6c8fed04/go.mod h1:FiwNQxz6hGoNFBC4nIx+CxZhI3nne5RmIOlT/MXcSD4= go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= diff --git a/backend/controller/dal/testdata/go/fsmnext/fsmnext.go b/backend/controller/dal/testdata/go/fsmnext/fsmnext.go new file mode 100644 index 000000000..d93292103 --- /dev/null +++ b/backend/controller/dal/testdata/go/fsmnext/fsmnext.go @@ -0,0 +1,118 @@ +package fsmnext + +import ( + "context" + "errors" + + "github.com/TBD54566975/ftl/go-runtime/ftl" +) + +// This FSM allows transitions moving forward through the alphabet +// Each transition also declares the next state(s) to transition to using State +// +//ftl:retry 2 1s +var fsm = ftl.FSM("fsm", + ftl.Start(StateA), + ftl.Transition(StateA, StateB), + ftl.Transition(StateA, StateC), + ftl.Transition(StateA, StateD), + ftl.Transition(StateB, StateC), + ftl.Transition(StateB, StateD), + ftl.Transition(StateC, StateD), +) + +type State string + +const ( + A State = "A" + B State = "B" + C State = "C" + D State = "D" +) + +type Event struct { + Instance string + NextStates []State // will schedule fsm.Next with these states progressively + NextAttempts ftl.Option[int] // will call fsm.Next this many times. Once otherwise + Error ftl.Option[string] // if present, returns this error after calling fsm.Next() as needed +} + +//ftl:typealias +type EventA Event + +//ftl:verb +func StateA(ctx context.Context, in EventA) error { + return handleEvent(ctx, Event(in)) +} + +//ftl:typealias +type EventB Event + +//ftl:verb +func StateB(ctx context.Context, in EventB) error { + return handleEvent(ctx, Event(in)) +} + +//ftl:typealias +type EventC Event + +//ftl:verb +func StateC(ctx context.Context, in EventC) error { + return handleEvent(ctx, Event(in)) +} + +//ftl:typealias +type EventD Event + +//ftl:verb +func StateD(ctx context.Context, in EventD) error { + return handleEvent(ctx, Event(in)) +} + +//ftl:data export +type Request struct { + State State + Event Event +} + +//ftl:verb export +func SendOne(ctx context.Context, in Request) error { + return fsm.Send(ctx, in.Event.Instance, eventFor(in.Event, in.State)) +} + +func handleEvent(ctx context.Context, in Event) error { + if len(in.NextStates) == 0 { + return nil + } + event := eventFor(Event{ + Instance: in.Instance, + NextStates: in.NextStates[1:], + NextAttempts: in.NextAttempts, + }, in.NextStates[0]) + attempts := in.NextAttempts.Default(1) + for i := range attempts { + ftl.LoggerFromContext(ctx).Infof("scheduling next event for %s (%d/%d)", in.Instance, i+1, attempts) + if err := ftl.FSMNext(ctx, event); err != nil { + return err + } + } + if errStr, ok := in.Error.Get(); ok { + return errors.New(errStr) + } + return nil +} + +func eventFor(event Event, state State) any { + switch state { + case A: + return EventA(event) + case B: + return EventB(event) + case C: + return EventC(event) + case D: + return EventD(event) + default: + panic("unknown state") + } +} diff --git a/backend/controller/dal/testdata/go/fsmnext/fsmnext_test.go b/backend/controller/dal/testdata/go/fsmnext/fsmnext_test.go new file mode 100644 index 000000000..294e5816e --- /dev/null +++ b/backend/controller/dal/testdata/go/fsmnext/fsmnext_test.go @@ -0,0 +1,49 @@ +package fsmnext + +import ( + "testing" + + "github.com/TBD54566975/ftl/go-runtime/ftl" + "github.com/TBD54566975/ftl/go-runtime/ftl/ftltest" + "github.com/alecthomas/assert/v2" +) + +func TestProgression(t *testing.T) { + // Simple progression through each state + ctx := ftltest.Context() + + assert.NoError(t, SendOne(ctx, Request{ + State: A, + Event: Event{ + Instance: "1", + NextStates: []State{B, C, D}, + }, + })) +} + +func TestDoubleNext(t *testing.T) { + // Bad progression where fsm.Next() is called twice + ctx := ftltest.Context() + + assert.Contains(t, SendOne(ctx, Request{ + State: A, + Event: Event{ + Instance: "1", + NextStates: []State{B}, + NextAttempts: ftl.Some(2), + }, + }).Error(), `fsm "fsm" instance "1" already has a pending event`) +} + +func TestBadProgression(t *testing.T) { + // Simple progression through each state + ctx := ftltest.Context() + + assert.EqualError(t, SendOne(ctx, Request{ + State: A, + Event: Event{ + Instance: "1", + NextStates: []State{B, C, C}, + }, + }), `invalid event "fsmnext.EventC" for state "fsmnext.EventC"`) +} diff --git a/backend/controller/dal/testdata/go/fsmnext/ftl.toml b/backend/controller/dal/testdata/go/fsmnext/ftl.toml new file mode 100644 index 000000000..0672f9850 --- /dev/null +++ b/backend/controller/dal/testdata/go/fsmnext/ftl.toml @@ -0,0 +1,2 @@ +module = "fsmnext" +language = "go" diff --git a/backend/controller/dal/testdata/go/fsmnext/go.mod b/backend/controller/dal/testdata/go/fsmnext/go.mod new file mode 100644 index 000000000..ebb3f6575 --- /dev/null +++ b/backend/controller/dal/testdata/go/fsmnext/go.mod @@ -0,0 +1,64 @@ +module ftl/fsmnext + +go 1.22.2 + +require ( + github.com/TBD54566975/ftl v1.1.5 + github.com/alecthomas/assert/v2 v2.10.0 +) + +require ( + connectrpc.com/connect v1.16.2 // indirect + connectrpc.com/grpcreflect v1.2.0 // indirect + connectrpc.com/otelconnect v0.7.1 // indirect + github.com/BurntSushi/toml v1.4.0 // indirect + github.com/TBD54566975/scaffolder v1.0.0 // indirect + github.com/alecthomas/atomic v0.1.0-alpha2 // indirect + github.com/alecthomas/concurrency v0.0.2 // indirect + github.com/alecthomas/participle/v2 v2.1.1 // indirect + github.com/alecthomas/repr v0.4.0 // indirect + github.com/alecthomas/types v0.16.0 // indirect + github.com/alessio/shellescape v1.4.2 // indirect + github.com/amacneil/dbmate/v2 v2.19.0 // indirect + github.com/aws/aws-sdk-go-v2 v1.30.3 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 // indirect + github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.32.4 // indirect + github.com/aws/smithy-go v1.20.3 // indirect + github.com/benbjohnson/clock v1.3.5 // indirect + github.com/danieljoos/wincred v1.2.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/hashicorp/cronexpr v1.1.2 // indirect + github.com/hexops/gotextdiff v1.0.3 // indirect + github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgx/v5 v5.6.0 // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/jpillora/backoff v1.0.0 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/lib/pq v1.10.9 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/multiformats/go-base36 v0.2.0 // indirect + github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect + github.com/sqlc-dev/pqtype v0.3.0 // indirect + github.com/swaggest/jsonschema-go v0.3.72 // indirect + github.com/swaggest/refl v1.3.0 // indirect + github.com/zalando/go-keyring v0.2.5 // indirect + go.opentelemetry.io/otel v1.28.0 // indirect + go.opentelemetry.io/otel/metric v1.28.0 // indirect + go.opentelemetry.io/otel/trace v1.28.0 // indirect + golang.org/x/crypto v0.26.0 // indirect + golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect +) + +replace github.com/TBD54566975/ftl => ./../../../../../.. diff --git a/backend/controller/dal/testdata/go/fsmnext/go.sum b/backend/controller/dal/testdata/go/fsmnext/go.sum new file mode 100644 index 000000000..a2c7b8b47 --- /dev/null +++ b/backend/controller/dal/testdata/go/fsmnext/go.sum @@ -0,0 +1,194 @@ +connectrpc.com/connect v1.16.2 h1:ybd6y+ls7GOlb7Bh5C8+ghA6SvCBajHwxssO2CGFjqE= +connectrpc.com/connect v1.16.2/go.mod h1:n2kgwskMHXC+lVqb18wngEpF95ldBHXjZYJussz5FRc= +connectrpc.com/grpcreflect v1.2.0 h1:Q6og1S7HinmtbEuBvARLNwYmTbhEGRpHDhqrPNlmK+U= +connectrpc.com/grpcreflect v1.2.0/go.mod h1:nwSOKmE8nU5u/CidgHtPYk1PFI3U9ignz7iDMxOYkSY= +connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY= +connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/TBD54566975/scaffolder v1.0.0 h1:QUFSy2wVzumLDg7IHcKC6AP+IYyqWe9Wxiu72nZn5qU= +github.com/TBD54566975/scaffolder v1.0.0/go.mod h1:auVpczIbOAdIhYDVSruIw41DanxOKB9bSvjf6MEl7Fs= +github.com/alecthomas/assert/v2 v2.10.0 h1:jjRCHsj6hBJhkmhznrCzoNpbA3zqy0fYiUcYZP/GkPY= +github.com/alecthomas/assert/v2 v2.10.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= +github.com/alecthomas/atomic v0.1.0-alpha2/go.mod h1:zD6QGEyw49HIq19caJDc2NMXAy8rNi9ROrxtMXATfyI= +github.com/alecthomas/concurrency v0.0.2 h1:Q3kGPtLbleMbH9lHX5OBFvJygfyFw29bXZKBg+IEVuo= +github.com/alecthomas/concurrency v0.0.2/go.mod h1:GmuQb/iHX7mbNtPlC/WDzEFxDMB0HYFer2Qda9QTs7w= +github.com/alecthomas/participle/v2 v2.1.1 h1:hrjKESvSqGHzRb4yW1ciisFJ4p3MGYih6icjJvbsmV8= +github.com/alecthomas/participle/v2 v2.1.1/go.mod h1:Y1+hAs8DHPmc3YUFzqllV+eSQ9ljPTk0ZkPMtEdAx2c= +github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= +github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/alecthomas/types v0.16.0 h1:o9+JSwCRB6DDaWDeR/Mg7v/zh3R+MlknM6DrnDyY7U0= +github.com/alecthomas/types v0.16.0/go.mod h1:Tswm0qQpjpVq8rn70OquRsUtFxbQKub/8TMyYYGI0+k= +github.com/alessio/shellescape v1.4.2 h1:MHPfaU+ddJ0/bYWpgIeUnQUqKrlJ1S7BfEYPM4uEoM0= +github.com/alessio/shellescape v1.4.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= +github.com/amacneil/dbmate/v2 v2.19.0 h1:RqqkBv6/Jbupuv8GcpBXNdRdSjocddiwmfE56Oil6YA= +github.com/amacneil/dbmate/v2 v2.19.0/go.mod h1:zyIkE4QZ+C+NxX0YNDb6zYlmEzCXMU7SGyYrtAJVDEk= +github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY= +github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc= +github.com/aws/aws-sdk-go-v2/config v1.27.27 h1:HdqgGt1OAP0HkEDDShEl0oSYa9ZZBSOmKpdpsDMdO90= +github.com/aws/aws-sdk-go-v2/config v1.27.27/go.mod h1:MVYamCg76dFNINkZFu4n4RjDixhVr51HLj4ErWzrVwg= +github.com/aws/aws-sdk-go-v2/credentials v1.17.27 h1:2raNba6gr2IfA0eqqiP2XiQ0UVOpGPgDSi0I9iAP+UI= +github.com/aws/aws-sdk-go-v2/credentials v1.17.27/go.mod h1:gniiwbGahQByxan6YjQUMcW4Aov6bLC3m+evgcoN4r4= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 h1:KreluoV8FZDEtI6Co2xuNk/UqI9iwMrOx/87PBNIKqw= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11/go.mod h1:SeSUYBLsMYFoRvHE0Tjvn7kbxaUhl75CJi1sbfhMxkU= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 h1:SoNJ4RlFEQEbtDcCEt+QG56MY4fm4W8rYirAmq+/DdU= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15/go.mod h1:U9ke74k1n2bf+RIgoX1SXFed1HLs51OgUSs+Ph0KJP8= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 h1:C6WHdGnTDIYETAm5iErQUiVNsclNx9qbJVPIt03B6bI= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15/go.mod h1:ZQLZqhcu+JhSrA9/NXRm8SkDvsycE+JkV3WGY41e+IM= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrxZlQ044RiM+WdoZxp0p+EGM62y3L6pwA4olE= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17/go.mod h1:RkZEx4l0EHYDJpWppMJ3nD9wZJAa8/0lq9aVC+r2UII= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.32.4 h1:NgRFYyFpiMD62y4VPXh4DosPFbZd4vdMVBWKk0VmWXc= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.32.4/go.mod h1:TKKN7IQoM7uTnyuFm9bm9cw5P//ZYTl4m3htBWQ1G/c= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 h1:BXx0ZIxvrJdSgSvKTZ+yRBeSqqgPM89VPlulEcl37tM= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.4/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 h1:yiwVzJW2ZxZTurVbYWA7QOrAaCYQR72t0wrSBfoesUE= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4/go.mod h1:0oxfLkpz3rQ/CHlx5hB7H69YUpFiI1tql6Q6Ne+1bCw= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 h1:ZsDKRLXGWHk8WdtyYMoGNO7bTudrvuKpDKgMVRlepGE= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.3/go.mod h1:zwySh8fpFyXp9yOr/KVzxOl8SRqgf/IDw5aUt9UKFcQ= +github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE= +github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= +github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= +github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/bool64/dev v0.2.35 h1:M17TLsO/pV2J7PYI/gpe3Ua26ETkzZGb+dC06eoMqlk= +github.com/bool64/dev v0.2.35/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg= +github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E= +github.com/bool64/shared v0.1.5/go.mod h1:081yz68YC9jeFB3+Bbmno2RFWvGKv1lPKkMP6MHJlPs= +github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE= +github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/cronexpr v1.1.2 h1:wG/ZYIKT+RT3QkOdgYc+xsKWVRgnxJ1OJtjjy84fJ9A= +github.com/hashicorp/cronexpr v1.1.2/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= +github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= +github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 h1:Dj0L5fhJ9F82ZJyVOmBx6msDp/kfd1t9GRfny/mfJA0= +github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= +github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= +github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= +github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4= +github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sqlc-dev/pqtype v0.3.0 h1:b09TewZ3cSnO5+M1Kqq05y0+OjqIptxELaSayg7bmqk= +github.com/sqlc-dev/pqtype v0.3.0/go.mod h1:oyUjp5981ctiL9UYvj1bVvCKi8OXkCa0u645hce7CAs= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ= +github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU= +github.com/swaggest/jsonschema-go v0.3.72 h1:IHaGlR1bdBUBPfhe4tfacN2TGAPKENEGiNyNzvnVHv4= +github.com/swaggest/jsonschema-go v0.3.72/go.mod h1:OrGyEoVqpfSFJ4Am4V/FQcQ3mlEC1vVeleA+5ggbVW4= +github.com/swaggest/refl v1.3.0 h1:PEUWIku+ZznYfsoyheF97ypSduvMApYyGkYF3nabS0I= +github.com/swaggest/refl v1.3.0/go.mod h1:3Ujvbmh1pfSbDYjC6JGG7nMgPvpG0ehQL4iNonnLNbg= +github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/zalando/go-keyring v0.2.5 h1:Bc2HHpjALryKD62ppdEzaFG6VxL6Bc+5v0LYpN8Lba8= +github.com/zalando/go-keyring v0.2.5/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= +github.com/zenizh/go-capturer v0.0.0-20211219060012-52ea6c8fed04 h1:qXafrlZL1WsJW5OokjraLLRURHiw0OzKHD/RNdspp4w= +github.com/zenizh/go-capturer v0.0.0-20211219060012-52ea6c8fed04/go.mod h1:FiwNQxz6hGoNFBC4nIx+CxZhI3nne5RmIOlT/MXcSD4= +go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= +go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= +go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= +go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= +go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnCQArXCKlg08= +go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= +go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= +go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= +golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI= +modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= +modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U= +modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= +modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= +modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/backend/controller/pubsub/manager.go b/backend/controller/pubsub/manager.go index e4aa6a262..23c34ebe5 100644 --- a/backend/controller/pubsub/manager.go +++ b/backend/controller/pubsub/manager.go @@ -66,7 +66,11 @@ func (m *Manager) progressSubscriptions(ctx context.Context) (time.Duration, err } // OnCallCompletion is called within a transaction after an async call has completed to allow the subscription state to be updated. -func (m *Manager) OnCallCompletion(ctx context.Context, tx *dal.Tx, origin dal.AsyncOriginPubSub, failed bool) error { +func (m *Manager) OnCallCompletion(ctx context.Context, tx *dal.Tx, origin dal.AsyncOriginPubSub, failed bool, isFinalResult bool) error { + if !isFinalResult { + // Wait for the async call's retries to complete before progressing the subscription + return nil + } return m.dal.CompleteEventForSubscription(ctx, origin.Subscription.Module, origin.Subscription.Name) } diff --git a/backend/controller/sql/models.go b/backend/controller/sql/models.go index 5c2780936..0c59dbef5 100644 --- a/backend/controller/sql/models.go +++ b/backend/controller/sql/models.go @@ -447,6 +447,15 @@ type FsmInstance struct { UpdatedAt time.Time } +type FsmNextEvent struct { + ID int64 + CreatedAt time.Time + FsmInstanceID int64 + NextState schema.RefKey + Request []byte + RequestType Type +} + type IngressRoute struct { Method string Path string diff --git a/backend/controller/sql/querier.go b/backend/controller/sql/querier.go index 0f2602688..a48ae4034 100644 --- a/backend/controller/sql/querier.go +++ b/backend/controller/sql/querier.go @@ -101,12 +101,14 @@ type Querier interface { KillStaleRunners(ctx context.Context, timeout sqltypes.Duration) (int64, error) LoadAsyncCall(ctx context.Context, id int64) (AsyncCall, error) NewLease(ctx context.Context, key leases.Key, ttl sqltypes.Duration, metadata pqtype.NullRawMessage) (uuid.UUID, error) + PopNextFSMEvent(ctx context.Context, fsm schema.RefKey, instanceKey string) (FsmNextEvent, error) PublishEventForTopic(ctx context.Context, arg PublishEventForTopicParams) error ReleaseLease(ctx context.Context, idempotencyKey uuid.UUID, key leases.Key) (bool, error) RenewLease(ctx context.Context, ttl sqltypes.Duration, idempotencyKey uuid.UUID, key leases.Key) (bool, error) // Find an idle runner and reserve it for the given deployment. ReserveRunner(ctx context.Context, reservationTimeout time.Time, deploymentKey model.DeploymentKey, labels json.RawMessage) (Runner, error) SetDeploymentDesiredReplicas(ctx context.Context, key model.DeploymentKey, minReplicas int32) error + SetNextFSMEvent(ctx context.Context, arg SetNextFSMEventParams) (int64, error) SetSubscriptionCursor(ctx context.Context, column1 model.SubscriptionKey, column2 model.TopicEventKey) error StartCronJobs(ctx context.Context, keys []string) ([]StartCronJobsRow, error) // Start a new FSM transition, populating the destination state and async call ID. diff --git a/backend/controller/sql/queries.sql b/backend/controller/sql/queries.sql index 90ae6b2e5..705667f21 100644 --- a/backend/controller/sql/queries.sql +++ b/backend/controller/sql/queries.sql @@ -660,6 +660,25 @@ WHERE fsm = @fsm::schema_ref AND key = @key::TEXT RETURNING true; +-- name: SetNextFSMEvent :one +INSERT INTO fsm_next_event (fsm_instance_id, next_state, request, request_type) +VALUES ( + (SELECT id FROM fsm_instances WHERE fsm = @fsm::schema_ref AND key = @instance_key), + @event, + @request, + sqlc.arg('request_type')::schema_type +) +RETURNING id; + +-- name: PopNextFSMEvent :one +DELETE FROM fsm_next_event +WHERE fsm_instance_id = ( + SELECT id + FROM fsm_instances + WHERE fsm = @fsm::schema_ref AND key = @instance_key +) +RETURNING *; + -- name: UpsertTopic :exec INSERT INTO topics (key, module_id, name, type) VALUES ( diff --git a/backend/controller/sql/queries.sql.go b/backend/controller/sql/queries.sql.go index 7dc54f632..84d5ca7f5 100644 --- a/backend/controller/sql/queries.sql.go +++ b/backend/controller/sql/queries.sql.go @@ -2306,6 +2306,30 @@ func (q *Queries) NewLease(ctx context.Context, key leases.Key, ttl sqltypes.Dur return idempotency_key, err } +const popNextFSMEvent = `-- name: PopNextFSMEvent :one +DELETE FROM fsm_next_event +WHERE fsm_instance_id = ( + SELECT id + FROM fsm_instances + WHERE fsm = $1::schema_ref AND key = $2 +) +RETURNING id, created_at, fsm_instance_id, next_state, request, request_type +` + +func (q *Queries) PopNextFSMEvent(ctx context.Context, fsm schema.RefKey, instanceKey string) (FsmNextEvent, error) { + row := q.db.QueryRowContext(ctx, popNextFSMEvent, fsm, instanceKey) + var i FsmNextEvent + err := row.Scan( + &i.ID, + &i.CreatedAt, + &i.FsmInstanceID, + &i.NextState, + &i.Request, + &i.RequestType, + ) + return i, err +} + const publishEventForTopic = `-- name: PublishEventForTopic :exec INSERT INTO topic_events ( "key", @@ -2430,6 +2454,38 @@ func (q *Queries) SetDeploymentDesiredReplicas(ctx context.Context, key model.De return err } +const setNextFSMEvent = `-- name: SetNextFSMEvent :one +INSERT INTO fsm_next_event (fsm_instance_id, next_state, request, request_type) +VALUES ( + (SELECT id FROM fsm_instances WHERE fsm = $1::schema_ref AND key = $2), + $3, + $4, + $5::schema_type +) +RETURNING id +` + +type SetNextFSMEventParams struct { + Fsm schema.RefKey + InstanceKey string + Event schema.RefKey + Request []byte + RequestType Type +} + +func (q *Queries) SetNextFSMEvent(ctx context.Context, arg SetNextFSMEventParams) (int64, error) { + row := q.db.QueryRowContext(ctx, setNextFSMEvent, + arg.Fsm, + arg.InstanceKey, + arg.Event, + arg.Request, + arg.RequestType, + ) + var id int64 + err := row.Scan(&id) + return id, err +} + const setSubscriptionCursor = `-- name: SetSubscriptionCursor :exec WITH event AS ( SELECT id, created_at, key, topic_id, payload diff --git a/backend/controller/sql/schema/20240813062546_create_fsm_next_events.sql b/backend/controller/sql/schema/20240813062546_create_fsm_next_events.sql new file mode 100644 index 000000000..10b2e8da3 --- /dev/null +++ b/backend/controller/sql/schema/20240813062546_create_fsm_next_events.sql @@ -0,0 +1,17 @@ +-- migrate:up + +-- This table is used to store the next event to be processed by an FSM instance. +-- The next event can be enqueued by the FSM itself while a transition is in progress. +CREATE TABLE fsm_next_event ( + id BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + created_at TIMESTAMPTZ NOT NULL DEFAULT (NOW() AT TIME ZONE 'utc'), + fsm_instance_id BIGINT UNIQUE NOT NULL REFERENCES fsm_instances(id) ON DELETE CASCADE, + next_state schema_ref NOT NULL, + request BYTEA NOT NULL, + request_type schema_type NOT NULL, + UNIQUE (fsm_instance_id, next_state) +); + + +-- migrate:down + diff --git a/backend/controller/sql/types.go b/backend/controller/sql/types.go index 90bc4f422..86f62d2c7 100644 --- a/backend/controller/sql/types.go +++ b/backend/controller/sql/types.go @@ -22,7 +22,7 @@ func (t *Type) Scan(src interface{}) error { case []byte: pb := &schemapb.Type{} if err := proto.Unmarshal(src, pb); err != nil { - return err + return fmt.Errorf("could not unmarshal type: %w", err) } t.Type = schema.TypeFromProto(pb) return nil @@ -31,8 +31,8 @@ func (t *Type) Scan(src interface{}) error { } } -func (t *Type) Value() (driver.Value, error) { - data, err := proto.Marshal(t.Type.ToProto()) +func (t Type) Value() (driver.Value, error) { + data, err := proto.Marshal(schema.TypeToProto(t.Type)) if err != nil { return nil, err } diff --git a/backend/protos/xyz/block/ftl/v1/ftl.pb.go b/backend/protos/xyz/block/ftl/v1/ftl.pb.go index 5aba79a48..70040409c 100644 --- a/backend/protos/xyz/block/ftl/v1/ftl.pb.go +++ b/backend/protos/xyz/block/ftl/v1/ftl.pb.go @@ -4992,7 +4992,7 @@ var file_xyz_block_ftl_v1_ftl_proto_rawDesc = []byte{ 0x53, 0x45, 0x43, 0x52, 0x45, 0x54, 0x5f, 0x4b, 0x45, 0x59, 0x43, 0x48, 0x41, 0x49, 0x4e, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x45, 0x43, 0x52, 0x45, 0x54, 0x5f, 0x4f, 0x50, 0x10, 0x03, 0x12, 0x0e, 0x0a, 0x0a, 0x53, 0x45, 0x43, 0x52, 0x45, 0x54, 0x5f, 0x41, 0x53, 0x4d, 0x10, 0x04, - 0x32, 0xa8, 0x04, 0x0a, 0x0b, 0x56, 0x65, 0x72, 0x62, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x32, 0x8a, 0x05, 0x0a, 0x0b, 0x56, 0x65, 0x72, 0x62, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4a, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x1d, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, @@ -5016,188 +5016,194 @@ var file_xyz_block_ftl_v1_ftl_proto_rawDesc = []byte{ 0x4d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x46, 0x53, 0x4d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5d, 0x0a, 0x0c, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x25, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, - 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x78, - 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, - 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, 0x04, 0x43, 0x61, 0x6c, 0x6c, 0x12, 0x1d, 0x2e, 0x78, - 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, - 0x43, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x78, 0x79, - 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x43, - 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xe4, 0x0b, 0x0a, 0x11, - 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x12, 0x4a, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x1d, 0x2e, 0x78, 0x79, 0x7a, 0x2e, - 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x69, 0x6e, - 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, - 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x69, 0x6e, 0x67, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x90, 0x02, 0x01, 0x12, 0x5a, 0x0a, - 0x0b, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x24, 0x2e, 0x78, - 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, - 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, - 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x69, 0x73, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x06, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x12, 0x1f, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, - 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, - 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x69, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x41, 0x72, 0x74, - 0x65, 0x66, 0x61, 0x63, 0x74, 0x44, 0x69, 0x66, 0x66, 0x73, 0x12, 0x29, 0x2e, 0x78, 0x79, 0x7a, - 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, - 0x74, 0x41, 0x72, 0x74, 0x65, 0x66, 0x61, 0x63, 0x74, 0x44, 0x69, 0x66, 0x66, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, - 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x72, 0x74, 0x65, - 0x66, 0x61, 0x63, 0x74, 0x44, 0x69, 0x66, 0x66, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x63, 0x0a, 0x0e, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x41, 0x72, 0x74, 0x65, 0x66, - 0x61, 0x63, 0x74, 0x12, 0x27, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, - 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x41, 0x72, 0x74, - 0x65, 0x66, 0x61, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x78, - 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, - 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x41, 0x72, 0x74, 0x65, 0x66, 0x61, 0x63, 0x74, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x69, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x29, 0x2e, 0x78, 0x79, 0x7a, - 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, - 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, - 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x60, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, - 0x6e, 0x74, 0x12, 0x26, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, - 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, - 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x78, 0x79, 0x7a, - 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, - 0x74, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x7d, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x41, 0x72, 0x74, 0x65, 0x66, 0x61, 0x63, 0x74, 0x73, 0x12, 0x2f, 0x2e, - 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, - 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x41, 0x72, - 0x74, 0x65, 0x66, 0x61, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x60, 0x0a, 0x0f, 0x53, 0x65, 0x74, 0x4e, 0x65, 0x78, 0x74, + 0x46, 0x53, 0x4d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x25, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, + 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, + 0x46, 0x53, 0x4d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x26, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, + 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x46, 0x53, 0x4d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5d, 0x0a, 0x0c, 0x50, 0x75, 0x62, 0x6c, 0x69, + 0x73, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x25, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, + 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, + 0x73, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, - 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x41, - 0x72, 0x74, 0x65, 0x66, 0x61, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x30, 0x01, 0x12, 0x65, 0x0a, 0x0e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x75, - 0x6e, 0x6e, 0x65, 0x72, 0x12, 0x27, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, - 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, - 0x52, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, + 0x31, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, 0x04, 0x43, 0x61, 0x6c, 0x6c, 0x12, 0x1d, + 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, + 0x31, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, - 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x12, 0x5d, 0x0a, 0x0c, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x12, 0x25, 0x2e, 0x78, 0x79, 0x7a, 0x2e, - 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x26, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, - 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x60, 0x0a, 0x0d, 0x52, 0x65, 0x70, 0x6c, - 0x61, 0x63, 0x65, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x12, 0x26, 0x2e, 0x78, 0x79, 0x7a, 0x2e, - 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x70, - 0x6c, 0x61, 0x63, 0x65, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x27, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, - 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x44, 0x65, 0x70, 0x6c, - 0x6f, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x77, 0x0a, 0x14, 0x53, 0x74, - 0x72, 0x65, 0x61, 0x6d, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4c, 0x6f, - 0x67, 0x73, 0x12, 0x2d, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, - 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x44, 0x65, 0x70, 0x6c, - 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x2e, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, - 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x44, 0x65, 0x70, 0x6c, 0x6f, - 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x28, 0x01, 0x12, 0x54, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, - 0x12, 0x22, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, - 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, - 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x63, 0x68, 0x65, 0x6d, - 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x59, 0x0a, 0x0a, 0x50, 0x75, 0x6c, - 0x6c, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x23, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, - 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x6c, 0x6c, 0x53, - 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x78, + 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xe4, 0x0b, + 0x0a, 0x11, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x12, 0x4a, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x1d, 0x2e, 0x78, 0x79, + 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x50, + 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x78, 0x79, 0x7a, + 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x69, + 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x90, 0x02, 0x01, 0x12, + 0x5a, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x24, + 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, + 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, + 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4c, + 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x06, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1f, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, + 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, + 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x69, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x41, + 0x72, 0x74, 0x65, 0x66, 0x61, 0x63, 0x74, 0x44, 0x69, 0x66, 0x66, 0x73, 0x12, 0x29, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, - 0x50, 0x75, 0x6c, 0x6c, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x30, 0x01, 0x12, 0x6c, 0x0a, 0x11, 0x52, 0x65, 0x73, 0x65, 0x74, 0x53, 0x75, 0x62, - 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2a, 0x2e, 0x78, 0x79, 0x7a, 0x2e, - 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, - 0x65, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, - 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x65, 0x74, 0x53, 0x75, - 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x32, 0xd2, 0x02, 0x0a, 0x0d, 0x52, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x53, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x12, 0x4a, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x1d, 0x2e, 0x78, + 0x47, 0x65, 0x74, 0x41, 0x72, 0x74, 0x65, 0x66, 0x61, 0x63, 0x74, 0x44, 0x69, 0x66, 0x66, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, + 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x72, + 0x74, 0x65, 0x66, 0x61, 0x63, 0x74, 0x44, 0x69, 0x66, 0x66, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x63, 0x0a, 0x0e, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x41, 0x72, 0x74, + 0x65, 0x66, 0x61, 0x63, 0x74, 0x12, 0x27, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, + 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x41, + 0x72, 0x74, 0x65, 0x66, 0x61, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, + 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, + 0x31, 0x2e, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x41, 0x72, 0x74, 0x65, 0x66, 0x61, 0x63, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x69, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x29, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, - 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x78, 0x79, - 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x50, - 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x90, 0x02, 0x01, - 0x12, 0x4e, 0x0a, 0x07, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x12, 0x20, 0x2e, 0x78, 0x79, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, + 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x60, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x26, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, + 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6c, 0x6f, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x78, + 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, + 0x47, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7d, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6c, + 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x41, 0x72, 0x74, 0x65, 0x66, 0x61, 0x63, 0x74, 0x73, 0x12, + 0x2f, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, + 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x41, 0x72, 0x74, 0x65, 0x66, 0x61, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x30, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, + 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x41, 0x72, 0x74, 0x65, 0x66, 0x61, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x30, 0x01, 0x12, 0x65, 0x0a, 0x0e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, + 0x52, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x12, 0x27, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, + 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, + 0x65, 0x72, 0x52, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x28, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, + 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x75, 0x6e, 0x6e, 0x65, + 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x12, 0x5d, 0x0a, 0x0c, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x12, 0x25, 0x2e, 0x78, 0x79, + 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, + 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x65, 0x70, 0x6c, + 0x6f, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x60, 0x0a, 0x0d, 0x52, 0x65, + 0x70, 0x6c, 0x61, 0x63, 0x65, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x12, 0x26, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x52, - 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, - 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, - 0x2e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x4b, 0x0a, 0x06, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x12, 0x1f, 0x2e, 0x78, 0x79, 0x7a, - 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, - 0x70, 0x6c, 0x6f, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x78, 0x79, - 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x44, - 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, - 0x09, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x12, 0x22, 0x2e, 0x78, 0x79, 0x7a, - 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, - 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, + 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, + 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x44, 0x65, + 0x70, 0x6c, 0x6f, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x77, 0x0a, 0x14, + 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x4c, 0x6f, 0x67, 0x73, 0x12, 0x2d, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, + 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x44, 0x65, + 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, + 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x44, 0x65, 0x70, + 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x28, 0x01, 0x12, 0x54, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x53, 0x63, 0x68, 0x65, + 0x6d, 0x61, 0x12, 0x22, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, + 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, + 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x63, 0x68, + 0x65, 0x6d, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x59, 0x0a, 0x0a, 0x50, + 0x75, 0x6c, 0x6c, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x23, 0x2e, 0x78, 0x79, 0x7a, 0x2e, + 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x6c, + 0x6c, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, - 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x75, 0x6e, 0x6e, 0x65, 0x72, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x32, 0x9f, 0x06, 0x0a, 0x0c, 0x41, 0x64, 0x6d, 0x69, - 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4a, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, - 0x12, 0x1d, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, - 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1e, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, - 0x76, 0x31, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x03, 0x90, 0x02, 0x01, 0x12, 0x57, 0x0a, 0x0a, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4c, 0x69, - 0x73, 0x74, 0x12, 0x23, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, - 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, - 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, - 0x09, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x47, 0x65, 0x74, 0x12, 0x22, 0x2e, 0x78, 0x79, 0x7a, - 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, - 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, + 0x31, 0x2e, 0x50, 0x75, 0x6c, 0x6c, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x6c, 0x0a, 0x11, 0x52, 0x65, 0x73, 0x65, 0x74, 0x53, + 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2a, 0x2e, 0x78, 0x79, + 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x52, + 0x65, 0x73, 0x65, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, + 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x65, 0x74, + 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xd2, 0x02, 0x0a, 0x0d, 0x52, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4a, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x1d, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, - 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x09, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x53, 0x65, 0x74, + 0x31, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, + 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, + 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x90, + 0x02, 0x01, 0x12, 0x4e, 0x0a, 0x07, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x12, 0x20, 0x2e, + 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, + 0x2e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x21, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, + 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x06, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x12, 0x1f, 0x2e, 0x78, + 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, + 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, + 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, + 0x2e, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x58, 0x0a, 0x09, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x12, 0x22, 0x2e, 0x78, + 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, + 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x27, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, + 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x75, 0x6e, 0x6e, + 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x32, 0x9f, 0x06, 0x0a, 0x0c, 0x41, 0x64, + 0x6d, 0x69, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4a, 0x0a, 0x04, 0x50, 0x69, + 0x6e, 0x67, 0x12, 0x1d, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, + 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1e, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, + 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x03, 0x90, 0x02, 0x01, 0x12, 0x57, 0x0a, 0x0a, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x4c, 0x69, 0x73, 0x74, 0x12, 0x23, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, + 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x78, 0x79, 0x7a, 0x2e, + 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x54, 0x0a, 0x09, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x47, 0x65, 0x74, 0x12, 0x22, 0x2e, 0x78, + 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, + 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x23, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, + 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x09, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x53, + 0x65, 0x74, 0x12, 0x22, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, + 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, + 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0b, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x55, 0x6e, 0x73, 0x65, 0x74, 0x12, 0x24, 0x2e, 0x78, 0x79, 0x7a, + 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, + 0x73, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x25, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, + 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x73, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0b, 0x53, 0x65, 0x63, 0x72, 0x65, + 0x74, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x24, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, + 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, + 0x63, 0x72, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x78, + 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x09, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x47, 0x65, 0x74, 0x12, 0x22, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, - 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, + 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, - 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0b, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x55, 0x6e, 0x73, 0x65, 0x74, 0x12, 0x24, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, - 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x73, 0x65, - 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, - 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, - 0x31, 0x2e, 0x55, 0x6e, 0x73, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0b, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, - 0x4c, 0x69, 0x73, 0x74, 0x12, 0x24, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, - 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x63, 0x72, - 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x78, 0x79, 0x7a, - 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x54, 0x0a, 0x09, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x47, 0x65, 0x74, 0x12, 0x22, + 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x09, 0x53, 0x65, 0x63, + 0x72, 0x65, 0x74, 0x53, 0x65, 0x74, 0x12, 0x22, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, + 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x53, 0x65, 0x63, + 0x72, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x78, 0x79, 0x7a, + 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, + 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x5a, 0x0a, 0x0b, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x55, 0x6e, 0x73, 0x65, 0x74, 0x12, 0x24, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, - 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, - 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x09, 0x53, 0x65, 0x63, 0x72, 0x65, - 0x74, 0x53, 0x65, 0x74, 0x12, 0x22, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, - 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, - 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x53, - 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, - 0x0b, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x55, 0x6e, 0x73, 0x65, 0x74, 0x12, 0x24, 0x2e, 0x78, - 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, - 0x55, 0x6e, 0x73, 0x65, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, - 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x73, 0x65, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x44, 0x50, 0x01, 0x5a, 0x40, 0x67, - 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x54, 0x42, 0x44, 0x35, 0x34, 0x35, - 0x36, 0x36, 0x39, 0x37, 0x35, 0x2f, 0x66, 0x74, 0x6c, 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, - 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x78, 0x79, 0x7a, 0x2f, 0x62, 0x6c, 0x6f, - 0x63, 0x6b, 0x2f, 0x66, 0x74, 0x6c, 0x2f, 0x76, 0x31, 0x3b, 0x66, 0x74, 0x6c, 0x76, 0x31, 0x62, - 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x31, 0x2e, 0x55, 0x6e, 0x73, 0x65, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, + 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x73, 0x65, 0x74, 0x53, 0x65, 0x63, + 0x72, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x44, 0x50, 0x01, 0x5a, + 0x40, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x54, 0x42, 0x44, 0x35, + 0x34, 0x35, 0x36, 0x36, 0x39, 0x37, 0x35, 0x2f, 0x66, 0x74, 0x6c, 0x2f, 0x62, 0x61, 0x63, 0x6b, + 0x65, 0x6e, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x78, 0x79, 0x7a, 0x2f, 0x62, + 0x6c, 0x6f, 0x63, 0x6b, 0x2f, 0x66, 0x74, 0x6c, 0x2f, 0x76, 0x31, 0x3b, 0x66, 0x74, 0x6c, 0x76, + 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -5369,72 +5375,74 @@ var file_xyz_block_ftl_v1_ftl_proto_depIdxs = []int32{ 7, // 57: xyz.block.ftl.v1.VerbService.GetModuleContext:input_type -> xyz.block.ftl.v1.ModuleContextRequest 12, // 58: xyz.block.ftl.v1.VerbService.AcquireLease:input_type -> xyz.block.ftl.v1.AcquireLeaseRequest 14, // 59: xyz.block.ftl.v1.VerbService.SendFSMEvent:input_type -> xyz.block.ftl.v1.SendFSMEventRequest - 16, // 60: xyz.block.ftl.v1.VerbService.PublishEvent:input_type -> xyz.block.ftl.v1.PublishEventRequest - 10, // 61: xyz.block.ftl.v1.VerbService.Call:input_type -> xyz.block.ftl.v1.CallRequest - 5, // 62: xyz.block.ftl.v1.ControllerService.Ping:input_type -> xyz.block.ftl.v1.PingRequest - 43, // 63: xyz.block.ftl.v1.ControllerService.ProcessList:input_type -> xyz.block.ftl.v1.ProcessListRequest - 41, // 64: xyz.block.ftl.v1.ControllerService.Status:input_type -> xyz.block.ftl.v1.StatusRequest - 22, // 65: xyz.block.ftl.v1.ControllerService.GetArtefactDiffs:input_type -> xyz.block.ftl.v1.GetArtefactDiffsRequest - 24, // 66: xyz.block.ftl.v1.ControllerService.UploadArtefact:input_type -> xyz.block.ftl.v1.UploadArtefactRequest - 27, // 67: xyz.block.ftl.v1.ControllerService.CreateDeployment:input_type -> xyz.block.ftl.v1.CreateDeploymentRequest - 31, // 68: xyz.block.ftl.v1.ControllerService.GetDeployment:input_type -> xyz.block.ftl.v1.GetDeploymentRequest - 29, // 69: xyz.block.ftl.v1.ControllerService.GetDeploymentArtefacts:input_type -> xyz.block.ftl.v1.GetDeploymentArtefactsRequest - 33, // 70: xyz.block.ftl.v1.ControllerService.RegisterRunner:input_type -> xyz.block.ftl.v1.RegisterRunnerRequest - 35, // 71: xyz.block.ftl.v1.ControllerService.UpdateDeploy:input_type -> xyz.block.ftl.v1.UpdateDeployRequest - 37, // 72: xyz.block.ftl.v1.ControllerService.ReplaceDeploy:input_type -> xyz.block.ftl.v1.ReplaceDeployRequest - 39, // 73: xyz.block.ftl.v1.ControllerService.StreamDeploymentLogs:input_type -> xyz.block.ftl.v1.StreamDeploymentLogsRequest - 18, // 74: xyz.block.ftl.v1.ControllerService.GetSchema:input_type -> xyz.block.ftl.v1.GetSchemaRequest - 20, // 75: xyz.block.ftl.v1.ControllerService.PullSchema:input_type -> xyz.block.ftl.v1.PullSchemaRequest - 45, // 76: xyz.block.ftl.v1.ControllerService.ResetSubscription:input_type -> xyz.block.ftl.v1.ResetSubscriptionRequest - 5, // 77: xyz.block.ftl.v1.RunnerService.Ping:input_type -> xyz.block.ftl.v1.PingRequest - 50, // 78: xyz.block.ftl.v1.RunnerService.Reserve:input_type -> xyz.block.ftl.v1.ReserveRequest - 47, // 79: xyz.block.ftl.v1.RunnerService.Deploy:input_type -> xyz.block.ftl.v1.DeployRequest - 49, // 80: xyz.block.ftl.v1.RunnerService.Terminate:input_type -> xyz.block.ftl.v1.TerminateRequest - 5, // 81: xyz.block.ftl.v1.AdminService.Ping:input_type -> xyz.block.ftl.v1.PingRequest - 53, // 82: xyz.block.ftl.v1.AdminService.ConfigList:input_type -> xyz.block.ftl.v1.ListConfigRequest - 55, // 83: xyz.block.ftl.v1.AdminService.ConfigGet:input_type -> xyz.block.ftl.v1.GetConfigRequest - 57, // 84: xyz.block.ftl.v1.AdminService.ConfigSet:input_type -> xyz.block.ftl.v1.SetConfigRequest - 59, // 85: xyz.block.ftl.v1.AdminService.ConfigUnset:input_type -> xyz.block.ftl.v1.UnsetConfigRequest - 61, // 86: xyz.block.ftl.v1.AdminService.SecretsList:input_type -> xyz.block.ftl.v1.ListSecretsRequest - 63, // 87: xyz.block.ftl.v1.AdminService.SecretGet:input_type -> xyz.block.ftl.v1.GetSecretRequest - 65, // 88: xyz.block.ftl.v1.AdminService.SecretSet:input_type -> xyz.block.ftl.v1.SetSecretRequest - 67, // 89: xyz.block.ftl.v1.AdminService.SecretUnset:input_type -> xyz.block.ftl.v1.UnsetSecretRequest - 6, // 90: xyz.block.ftl.v1.VerbService.Ping:output_type -> xyz.block.ftl.v1.PingResponse - 8, // 91: xyz.block.ftl.v1.VerbService.GetModuleContext:output_type -> xyz.block.ftl.v1.ModuleContextResponse - 13, // 92: xyz.block.ftl.v1.VerbService.AcquireLease:output_type -> xyz.block.ftl.v1.AcquireLeaseResponse - 15, // 93: xyz.block.ftl.v1.VerbService.SendFSMEvent:output_type -> xyz.block.ftl.v1.SendFSMEventResponse - 17, // 94: xyz.block.ftl.v1.VerbService.PublishEvent:output_type -> xyz.block.ftl.v1.PublishEventResponse - 11, // 95: xyz.block.ftl.v1.VerbService.Call:output_type -> xyz.block.ftl.v1.CallResponse - 6, // 96: xyz.block.ftl.v1.ControllerService.Ping:output_type -> xyz.block.ftl.v1.PingResponse - 44, // 97: xyz.block.ftl.v1.ControllerService.ProcessList:output_type -> xyz.block.ftl.v1.ProcessListResponse - 42, // 98: xyz.block.ftl.v1.ControllerService.Status:output_type -> xyz.block.ftl.v1.StatusResponse - 23, // 99: xyz.block.ftl.v1.ControllerService.GetArtefactDiffs:output_type -> xyz.block.ftl.v1.GetArtefactDiffsResponse - 25, // 100: xyz.block.ftl.v1.ControllerService.UploadArtefact:output_type -> xyz.block.ftl.v1.UploadArtefactResponse - 28, // 101: xyz.block.ftl.v1.ControllerService.CreateDeployment:output_type -> xyz.block.ftl.v1.CreateDeploymentResponse - 32, // 102: xyz.block.ftl.v1.ControllerService.GetDeployment:output_type -> xyz.block.ftl.v1.GetDeploymentResponse - 30, // 103: xyz.block.ftl.v1.ControllerService.GetDeploymentArtefacts:output_type -> xyz.block.ftl.v1.GetDeploymentArtefactsResponse - 34, // 104: xyz.block.ftl.v1.ControllerService.RegisterRunner:output_type -> xyz.block.ftl.v1.RegisterRunnerResponse - 36, // 105: xyz.block.ftl.v1.ControllerService.UpdateDeploy:output_type -> xyz.block.ftl.v1.UpdateDeployResponse - 38, // 106: xyz.block.ftl.v1.ControllerService.ReplaceDeploy:output_type -> xyz.block.ftl.v1.ReplaceDeployResponse - 40, // 107: xyz.block.ftl.v1.ControllerService.StreamDeploymentLogs:output_type -> xyz.block.ftl.v1.StreamDeploymentLogsResponse - 19, // 108: xyz.block.ftl.v1.ControllerService.GetSchema:output_type -> xyz.block.ftl.v1.GetSchemaResponse - 21, // 109: xyz.block.ftl.v1.ControllerService.PullSchema:output_type -> xyz.block.ftl.v1.PullSchemaResponse - 46, // 110: xyz.block.ftl.v1.ControllerService.ResetSubscription:output_type -> xyz.block.ftl.v1.ResetSubscriptionResponse - 6, // 111: xyz.block.ftl.v1.RunnerService.Ping:output_type -> xyz.block.ftl.v1.PingResponse - 51, // 112: xyz.block.ftl.v1.RunnerService.Reserve:output_type -> xyz.block.ftl.v1.ReserveResponse - 48, // 113: xyz.block.ftl.v1.RunnerService.Deploy:output_type -> xyz.block.ftl.v1.DeployResponse - 33, // 114: xyz.block.ftl.v1.RunnerService.Terminate:output_type -> xyz.block.ftl.v1.RegisterRunnerRequest - 6, // 115: xyz.block.ftl.v1.AdminService.Ping:output_type -> xyz.block.ftl.v1.PingResponse - 54, // 116: xyz.block.ftl.v1.AdminService.ConfigList:output_type -> xyz.block.ftl.v1.ListConfigResponse - 56, // 117: xyz.block.ftl.v1.AdminService.ConfigGet:output_type -> xyz.block.ftl.v1.GetConfigResponse - 58, // 118: xyz.block.ftl.v1.AdminService.ConfigSet:output_type -> xyz.block.ftl.v1.SetConfigResponse - 60, // 119: xyz.block.ftl.v1.AdminService.ConfigUnset:output_type -> xyz.block.ftl.v1.UnsetConfigResponse - 62, // 120: xyz.block.ftl.v1.AdminService.SecretsList:output_type -> xyz.block.ftl.v1.ListSecretsResponse - 64, // 121: xyz.block.ftl.v1.AdminService.SecretGet:output_type -> xyz.block.ftl.v1.GetSecretResponse - 66, // 122: xyz.block.ftl.v1.AdminService.SecretSet:output_type -> xyz.block.ftl.v1.SetSecretResponse - 68, // 123: xyz.block.ftl.v1.AdminService.SecretUnset:output_type -> xyz.block.ftl.v1.UnsetSecretResponse - 90, // [90:124] is the sub-list for method output_type - 56, // [56:90] is the sub-list for method input_type + 14, // 60: xyz.block.ftl.v1.VerbService.SetNextFSMEvent:input_type -> xyz.block.ftl.v1.SendFSMEventRequest + 16, // 61: xyz.block.ftl.v1.VerbService.PublishEvent:input_type -> xyz.block.ftl.v1.PublishEventRequest + 10, // 62: xyz.block.ftl.v1.VerbService.Call:input_type -> xyz.block.ftl.v1.CallRequest + 5, // 63: xyz.block.ftl.v1.ControllerService.Ping:input_type -> xyz.block.ftl.v1.PingRequest + 43, // 64: xyz.block.ftl.v1.ControllerService.ProcessList:input_type -> xyz.block.ftl.v1.ProcessListRequest + 41, // 65: xyz.block.ftl.v1.ControllerService.Status:input_type -> xyz.block.ftl.v1.StatusRequest + 22, // 66: xyz.block.ftl.v1.ControllerService.GetArtefactDiffs:input_type -> xyz.block.ftl.v1.GetArtefactDiffsRequest + 24, // 67: xyz.block.ftl.v1.ControllerService.UploadArtefact:input_type -> xyz.block.ftl.v1.UploadArtefactRequest + 27, // 68: xyz.block.ftl.v1.ControllerService.CreateDeployment:input_type -> xyz.block.ftl.v1.CreateDeploymentRequest + 31, // 69: xyz.block.ftl.v1.ControllerService.GetDeployment:input_type -> xyz.block.ftl.v1.GetDeploymentRequest + 29, // 70: xyz.block.ftl.v1.ControllerService.GetDeploymentArtefacts:input_type -> xyz.block.ftl.v1.GetDeploymentArtefactsRequest + 33, // 71: xyz.block.ftl.v1.ControllerService.RegisterRunner:input_type -> xyz.block.ftl.v1.RegisterRunnerRequest + 35, // 72: xyz.block.ftl.v1.ControllerService.UpdateDeploy:input_type -> xyz.block.ftl.v1.UpdateDeployRequest + 37, // 73: xyz.block.ftl.v1.ControllerService.ReplaceDeploy:input_type -> xyz.block.ftl.v1.ReplaceDeployRequest + 39, // 74: xyz.block.ftl.v1.ControllerService.StreamDeploymentLogs:input_type -> xyz.block.ftl.v1.StreamDeploymentLogsRequest + 18, // 75: xyz.block.ftl.v1.ControllerService.GetSchema:input_type -> xyz.block.ftl.v1.GetSchemaRequest + 20, // 76: xyz.block.ftl.v1.ControllerService.PullSchema:input_type -> xyz.block.ftl.v1.PullSchemaRequest + 45, // 77: xyz.block.ftl.v1.ControllerService.ResetSubscription:input_type -> xyz.block.ftl.v1.ResetSubscriptionRequest + 5, // 78: xyz.block.ftl.v1.RunnerService.Ping:input_type -> xyz.block.ftl.v1.PingRequest + 50, // 79: xyz.block.ftl.v1.RunnerService.Reserve:input_type -> xyz.block.ftl.v1.ReserveRequest + 47, // 80: xyz.block.ftl.v1.RunnerService.Deploy:input_type -> xyz.block.ftl.v1.DeployRequest + 49, // 81: xyz.block.ftl.v1.RunnerService.Terminate:input_type -> xyz.block.ftl.v1.TerminateRequest + 5, // 82: xyz.block.ftl.v1.AdminService.Ping:input_type -> xyz.block.ftl.v1.PingRequest + 53, // 83: xyz.block.ftl.v1.AdminService.ConfigList:input_type -> xyz.block.ftl.v1.ListConfigRequest + 55, // 84: xyz.block.ftl.v1.AdminService.ConfigGet:input_type -> xyz.block.ftl.v1.GetConfigRequest + 57, // 85: xyz.block.ftl.v1.AdminService.ConfigSet:input_type -> xyz.block.ftl.v1.SetConfigRequest + 59, // 86: xyz.block.ftl.v1.AdminService.ConfigUnset:input_type -> xyz.block.ftl.v1.UnsetConfigRequest + 61, // 87: xyz.block.ftl.v1.AdminService.SecretsList:input_type -> xyz.block.ftl.v1.ListSecretsRequest + 63, // 88: xyz.block.ftl.v1.AdminService.SecretGet:input_type -> xyz.block.ftl.v1.GetSecretRequest + 65, // 89: xyz.block.ftl.v1.AdminService.SecretSet:input_type -> xyz.block.ftl.v1.SetSecretRequest + 67, // 90: xyz.block.ftl.v1.AdminService.SecretUnset:input_type -> xyz.block.ftl.v1.UnsetSecretRequest + 6, // 91: xyz.block.ftl.v1.VerbService.Ping:output_type -> xyz.block.ftl.v1.PingResponse + 8, // 92: xyz.block.ftl.v1.VerbService.GetModuleContext:output_type -> xyz.block.ftl.v1.ModuleContextResponse + 13, // 93: xyz.block.ftl.v1.VerbService.AcquireLease:output_type -> xyz.block.ftl.v1.AcquireLeaseResponse + 15, // 94: xyz.block.ftl.v1.VerbService.SendFSMEvent:output_type -> xyz.block.ftl.v1.SendFSMEventResponse + 15, // 95: xyz.block.ftl.v1.VerbService.SetNextFSMEvent:output_type -> xyz.block.ftl.v1.SendFSMEventResponse + 17, // 96: xyz.block.ftl.v1.VerbService.PublishEvent:output_type -> xyz.block.ftl.v1.PublishEventResponse + 11, // 97: xyz.block.ftl.v1.VerbService.Call:output_type -> xyz.block.ftl.v1.CallResponse + 6, // 98: xyz.block.ftl.v1.ControllerService.Ping:output_type -> xyz.block.ftl.v1.PingResponse + 44, // 99: xyz.block.ftl.v1.ControllerService.ProcessList:output_type -> xyz.block.ftl.v1.ProcessListResponse + 42, // 100: xyz.block.ftl.v1.ControllerService.Status:output_type -> xyz.block.ftl.v1.StatusResponse + 23, // 101: xyz.block.ftl.v1.ControllerService.GetArtefactDiffs:output_type -> xyz.block.ftl.v1.GetArtefactDiffsResponse + 25, // 102: xyz.block.ftl.v1.ControllerService.UploadArtefact:output_type -> xyz.block.ftl.v1.UploadArtefactResponse + 28, // 103: xyz.block.ftl.v1.ControllerService.CreateDeployment:output_type -> xyz.block.ftl.v1.CreateDeploymentResponse + 32, // 104: xyz.block.ftl.v1.ControllerService.GetDeployment:output_type -> xyz.block.ftl.v1.GetDeploymentResponse + 30, // 105: xyz.block.ftl.v1.ControllerService.GetDeploymentArtefacts:output_type -> xyz.block.ftl.v1.GetDeploymentArtefactsResponse + 34, // 106: xyz.block.ftl.v1.ControllerService.RegisterRunner:output_type -> xyz.block.ftl.v1.RegisterRunnerResponse + 36, // 107: xyz.block.ftl.v1.ControllerService.UpdateDeploy:output_type -> xyz.block.ftl.v1.UpdateDeployResponse + 38, // 108: xyz.block.ftl.v1.ControllerService.ReplaceDeploy:output_type -> xyz.block.ftl.v1.ReplaceDeployResponse + 40, // 109: xyz.block.ftl.v1.ControllerService.StreamDeploymentLogs:output_type -> xyz.block.ftl.v1.StreamDeploymentLogsResponse + 19, // 110: xyz.block.ftl.v1.ControllerService.GetSchema:output_type -> xyz.block.ftl.v1.GetSchemaResponse + 21, // 111: xyz.block.ftl.v1.ControllerService.PullSchema:output_type -> xyz.block.ftl.v1.PullSchemaResponse + 46, // 112: xyz.block.ftl.v1.ControllerService.ResetSubscription:output_type -> xyz.block.ftl.v1.ResetSubscriptionResponse + 6, // 113: xyz.block.ftl.v1.RunnerService.Ping:output_type -> xyz.block.ftl.v1.PingResponse + 51, // 114: xyz.block.ftl.v1.RunnerService.Reserve:output_type -> xyz.block.ftl.v1.ReserveResponse + 48, // 115: xyz.block.ftl.v1.RunnerService.Deploy:output_type -> xyz.block.ftl.v1.DeployResponse + 33, // 116: xyz.block.ftl.v1.RunnerService.Terminate:output_type -> xyz.block.ftl.v1.RegisterRunnerRequest + 6, // 117: xyz.block.ftl.v1.AdminService.Ping:output_type -> xyz.block.ftl.v1.PingResponse + 54, // 118: xyz.block.ftl.v1.AdminService.ConfigList:output_type -> xyz.block.ftl.v1.ListConfigResponse + 56, // 119: xyz.block.ftl.v1.AdminService.ConfigGet:output_type -> xyz.block.ftl.v1.GetConfigResponse + 58, // 120: xyz.block.ftl.v1.AdminService.ConfigSet:output_type -> xyz.block.ftl.v1.SetConfigResponse + 60, // 121: xyz.block.ftl.v1.AdminService.ConfigUnset:output_type -> xyz.block.ftl.v1.UnsetConfigResponse + 62, // 122: xyz.block.ftl.v1.AdminService.SecretsList:output_type -> xyz.block.ftl.v1.ListSecretsResponse + 64, // 123: xyz.block.ftl.v1.AdminService.SecretGet:output_type -> xyz.block.ftl.v1.GetSecretResponse + 66, // 124: xyz.block.ftl.v1.AdminService.SecretSet:output_type -> xyz.block.ftl.v1.SetSecretResponse + 68, // 125: xyz.block.ftl.v1.AdminService.SecretUnset:output_type -> xyz.block.ftl.v1.UnsetSecretResponse + 91, // [91:126] is the sub-list for method output_type + 56, // [56:91] is the sub-list for method input_type 56, // [56:56] is the sub-list for extension type_name 56, // [56:56] is the sub-list for extension extendee 0, // [0:56] is the sub-list for field type_name diff --git a/backend/protos/xyz/block/ftl/v1/ftl.proto b/backend/protos/xyz/block/ftl/v1/ftl.proto index c10f7b78f..060d73a8d 100644 --- a/backend/protos/xyz/block/ftl/v1/ftl.proto +++ b/backend/protos/xyz/block/ftl/v1/ftl.proto @@ -115,6 +115,9 @@ service VerbService { // Send an event to an FSM. rpc SendFSMEvent(SendFSMEventRequest) returns (SendFSMEventResponse); + // Set the next event for an FSM. + rpc SetNextFSMEvent(SendFSMEventRequest) returns (SendFSMEventResponse); + // Publish an event to a topic. rpc PublishEvent(PublishEventRequest) returns (PublishEventResponse); diff --git a/backend/protos/xyz/block/ftl/v1/ftlv1connect/ftl.connect.go b/backend/protos/xyz/block/ftl/v1/ftlv1connect/ftl.connect.go index 1510614b5..d423ab155 100644 --- a/backend/protos/xyz/block/ftl/v1/ftlv1connect/ftl.connect.go +++ b/backend/protos/xyz/block/ftl/v1/ftlv1connect/ftl.connect.go @@ -50,6 +50,9 @@ const ( // VerbServiceSendFSMEventProcedure is the fully-qualified name of the VerbService's SendFSMEvent // RPC. VerbServiceSendFSMEventProcedure = "/xyz.block.ftl.v1.VerbService/SendFSMEvent" + // VerbServiceSetNextFSMEventProcedure is the fully-qualified name of the VerbService's + // SetNextFSMEvent RPC. + VerbServiceSetNextFSMEventProcedure = "/xyz.block.ftl.v1.VerbService/SetNextFSMEvent" // VerbServicePublishEventProcedure is the fully-qualified name of the VerbService's PublishEvent // RPC. VerbServicePublishEventProcedure = "/xyz.block.ftl.v1.VerbService/PublishEvent" @@ -142,6 +145,8 @@ type VerbServiceClient interface { AcquireLease(context.Context) *connect.BidiStreamForClient[v1.AcquireLeaseRequest, v1.AcquireLeaseResponse] // Send an event to an FSM. SendFSMEvent(context.Context, *connect.Request[v1.SendFSMEventRequest]) (*connect.Response[v1.SendFSMEventResponse], error) + // Set the next event for an FSM. + SetNextFSMEvent(context.Context, *connect.Request[v1.SendFSMEventRequest]) (*connect.Response[v1.SendFSMEventResponse], error) // Publish an event to a topic. PublishEvent(context.Context, *connect.Request[v1.PublishEventRequest]) (*connect.Response[v1.PublishEventResponse], error) // Issue a synchronous call to a Verb. @@ -179,6 +184,11 @@ func NewVerbServiceClient(httpClient connect.HTTPClient, baseURL string, opts .. baseURL+VerbServiceSendFSMEventProcedure, opts..., ), + setNextFSMEvent: connect.NewClient[v1.SendFSMEventRequest, v1.SendFSMEventResponse]( + httpClient, + baseURL+VerbServiceSetNextFSMEventProcedure, + opts..., + ), publishEvent: connect.NewClient[v1.PublishEventRequest, v1.PublishEventResponse]( httpClient, baseURL+VerbServicePublishEventProcedure, @@ -198,6 +208,7 @@ type verbServiceClient struct { getModuleContext *connect.Client[v1.ModuleContextRequest, v1.ModuleContextResponse] acquireLease *connect.Client[v1.AcquireLeaseRequest, v1.AcquireLeaseResponse] sendFSMEvent *connect.Client[v1.SendFSMEventRequest, v1.SendFSMEventResponse] + setNextFSMEvent *connect.Client[v1.SendFSMEventRequest, v1.SendFSMEventResponse] publishEvent *connect.Client[v1.PublishEventRequest, v1.PublishEventResponse] call *connect.Client[v1.CallRequest, v1.CallResponse] } @@ -222,6 +233,11 @@ func (c *verbServiceClient) SendFSMEvent(ctx context.Context, req *connect.Reque return c.sendFSMEvent.CallUnary(ctx, req) } +// SetNextFSMEvent calls xyz.block.ftl.v1.VerbService.SetNextFSMEvent. +func (c *verbServiceClient) SetNextFSMEvent(ctx context.Context, req *connect.Request[v1.SendFSMEventRequest]) (*connect.Response[v1.SendFSMEventResponse], error) { + return c.setNextFSMEvent.CallUnary(ctx, req) +} + // PublishEvent calls xyz.block.ftl.v1.VerbService.PublishEvent. func (c *verbServiceClient) PublishEvent(ctx context.Context, req *connect.Request[v1.PublishEventRequest]) (*connect.Response[v1.PublishEventResponse], error) { return c.publishEvent.CallUnary(ctx, req) @@ -244,6 +260,8 @@ type VerbServiceHandler interface { AcquireLease(context.Context, *connect.BidiStream[v1.AcquireLeaseRequest, v1.AcquireLeaseResponse]) error // Send an event to an FSM. SendFSMEvent(context.Context, *connect.Request[v1.SendFSMEventRequest]) (*connect.Response[v1.SendFSMEventResponse], error) + // Set the next event for an FSM. + SetNextFSMEvent(context.Context, *connect.Request[v1.SendFSMEventRequest]) (*connect.Response[v1.SendFSMEventResponse], error) // Publish an event to a topic. PublishEvent(context.Context, *connect.Request[v1.PublishEventRequest]) (*connect.Response[v1.PublishEventResponse], error) // Issue a synchronous call to a Verb. @@ -277,6 +295,11 @@ func NewVerbServiceHandler(svc VerbServiceHandler, opts ...connect.HandlerOption svc.SendFSMEvent, opts..., ) + verbServiceSetNextFSMEventHandler := connect.NewUnaryHandler( + VerbServiceSetNextFSMEventProcedure, + svc.SetNextFSMEvent, + opts..., + ) verbServicePublishEventHandler := connect.NewUnaryHandler( VerbServicePublishEventProcedure, svc.PublishEvent, @@ -297,6 +320,8 @@ func NewVerbServiceHandler(svc VerbServiceHandler, opts ...connect.HandlerOption verbServiceAcquireLeaseHandler.ServeHTTP(w, r) case VerbServiceSendFSMEventProcedure: verbServiceSendFSMEventHandler.ServeHTTP(w, r) + case VerbServiceSetNextFSMEventProcedure: + verbServiceSetNextFSMEventHandler.ServeHTTP(w, r) case VerbServicePublishEventProcedure: verbServicePublishEventHandler.ServeHTTP(w, r) case VerbServiceCallProcedure: @@ -326,6 +351,10 @@ func (UnimplementedVerbServiceHandler) SendFSMEvent(context.Context, *connect.Re return nil, connect.NewError(connect.CodeUnimplemented, errors.New("xyz.block.ftl.v1.VerbService.SendFSMEvent is not implemented")) } +func (UnimplementedVerbServiceHandler) SetNextFSMEvent(context.Context, *connect.Request[v1.SendFSMEventRequest]) (*connect.Response[v1.SendFSMEventResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("xyz.block.ftl.v1.VerbService.SetNextFSMEvent is not implemented")) +} + func (UnimplementedVerbServiceHandler) PublishEvent(context.Context, *connect.Request[v1.PublishEventRequest]) (*connect.Response[v1.PublishEventResponse], error) { return nil, connect.NewError(connect.CodeUnimplemented, errors.New("xyz.block.ftl.v1.VerbService.PublishEvent is not implemented")) } diff --git a/backend/runner/runner.go b/backend/runner/runner.go index d48d0c863..6942f44cf 100644 --- a/backend/runner/runner.go +++ b/backend/runner/runner.go @@ -241,6 +241,10 @@ func (s *Service) SendFSMEvent(context.Context, *connect.Request[ftlv1.SendFSMEv return nil, connect.NewError(connect.CodeUnimplemented, errors.New("FSM events must be sent to the controller")) } +func (s *Service) SetNextFSMEvent(context.Context, *connect.Request[ftlv1.SendFSMEventRequest]) (*connect.Response[ftlv1.SendFSMEventResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("FSM events must be set by the controller")) +} + func (s *Service) PublishEvent(context.Context, *connect.Request[ftlv1.PublishEventRequest]) (*connect.Response[ftlv1.PublishEventResponse], error) { return nil, connect.NewError(connect.CodeUnimplemented, errors.New("topic events must be sent to the controller")) } diff --git a/backend/schema/fsm.go b/backend/schema/fsm.go index 95a5f2c58..f3c75233c 100644 --- a/backend/schema/fsm.go +++ b/backend/schema/fsm.go @@ -5,6 +5,7 @@ import ( "sort" "strings" + "github.com/alecthomas/types/optional" "google.golang.org/protobuf/reflect/protoreflect" schemapb "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1/schema" @@ -57,8 +58,41 @@ func (f *FSM) TerminalStates() []*Ref { return out[i].String() < out[j].String() }) return out +} +// NextState returns the next state, if any, given the current state and event. +// +// If currentState is None, the instance has not started. +func (f *FSM) NextState(sch *Schema, currentState optional.Option[RefKey], event Type) optional.Option[*Ref] { + verb := Verb{} + curState, ok := currentState.Get() + if !ok { + for _, start := range f.Start { + // This shouldn't happen, but if it does, we'll just return false. + if err := sch.ResolveToType(start, &verb); err != nil { + return optional.None[*Ref]() + } + if verb.Request.Equal(event) { + return optional.Some(start) + } + } + return optional.None[*Ref]() + } + for _, transition := range f.Transitions { + if transition.From.ToRefKey() != curState { + continue + } + // This shouldn't happen, but if it does we'll just return false. + if err := sch.ResolveToType(transition.To, &verb); err != nil { + return optional.None[*Ref]() + } + if verb.Request.Equal(event) { + return optional.Some(transition.To) + } + } + return optional.None[*Ref]() } + func (f *FSM) GetName() string { return f.Name } func (f *FSM) IsExported() bool { return false } func (f *FSM) Position() Position { return f.Pos } diff --git a/backend/schema/parser.go b/backend/schema/parser.go index 7deb34810..491c43f1b 100644 --- a/backend/schema/parser.go +++ b/backend/schema/parser.go @@ -102,6 +102,7 @@ type Node interface { //sumtype:decl type Type interface { Node + // Equal returns true if this type is equal to another type. Equal(other Type) bool // schemaType is a marker to ensure that all types implement the Type interface. schemaType() diff --git a/common/configuration/sql/models.go b/common/configuration/sql/models.go index 5c2780936..0c59dbef5 100644 --- a/common/configuration/sql/models.go +++ b/common/configuration/sql/models.go @@ -447,6 +447,15 @@ type FsmInstance struct { UpdatedAt time.Time } +type FsmNextEvent struct { + ID int64 + CreatedAt time.Time + FsmInstanceID int64 + NextState schema.RefKey + Request []byte + RequestType Type +} + type IngressRoute struct { Method string Path string diff --git a/common/configuration/sql/types.go b/common/configuration/sql/types.go new file mode 100644 index 000000000..31097b10c --- /dev/null +++ b/common/configuration/sql/types.go @@ -0,0 +1,5 @@ +package sql + +import csql "github.com/TBD54566975/ftl/backend/controller/sql" + +type Type = csql.Type diff --git a/docs/content/docs/reference/fsm.md b/docs/content/docs/reference/fsm.md index f3337f85f..0657d21fe 100644 --- a/docs/content/docs/reference/fsm.md +++ b/docs/content/docs/reference/fsm.md @@ -45,10 +45,20 @@ func Paid(ctx context.Context, in Receipt) error { /* ... */ } func Defaulted(ctx context.Context, in Timeout) error { /* ... */ } ``` -Then to send events to the FSM: +## Creating and transitioning instances + +To send an event to an fsm instance, call `Send()` on the FSM with the instance's unique key: ```go err := payment.Send(ctx, invoiceID, Invoice{Amount: 110}) ``` +The first time you send an event for an instance key, an fsm instance will be created. + Sending an event to an FSM is asynchronous. From the time an event is sent until the state function completes execution, the FSM is transitioning. It is invalid to send an event to an FSM that is transitioning. + +During a transition you may need to trigger a transition to another state. This can be done by calling `Next()` on the FSM: + +```go +err := payment.Next(ctx, invoiceID, Receipt{...}) +``` \ No newline at end of file diff --git a/frontend/src/protos/xyz/block/ftl/v1/ftl_connect.ts b/frontend/src/protos/xyz/block/ftl/v1/ftl_connect.ts index ff4fa92e5..0de5b51e5 100644 --- a/frontend/src/protos/xyz/block/ftl/v1/ftl_connect.ts +++ b/frontend/src/protos/xyz/block/ftl/v1/ftl_connect.ts @@ -61,6 +61,17 @@ export const VerbService = { O: SendFSMEventResponse, kind: MethodKind.Unary, }, + /** + * Set the next event for an FSM. + * + * @generated from rpc xyz.block.ftl.v1.VerbService.SetNextFSMEvent + */ + setNextFSMEvent: { + name: "SetNextFSMEvent", + I: SendFSMEventRequest, + O: SendFSMEventResponse, + kind: MethodKind.Unary, + }, /** * Publish an event to a topic. * diff --git a/go-runtime/ftl/fsm.go b/go-runtime/ftl/fsm.go index 0c983f6b6..c3abce024 100644 --- a/go-runtime/ftl/fsm.go +++ b/go-runtime/ftl/fsm.go @@ -2,6 +2,7 @@ package ftl import ( "context" + "fmt" "reflect" "github.com/TBD54566975/ftl/go-runtime/ftl/reflection" @@ -56,6 +57,30 @@ func FSM(name string, transitions ...FSMTransition) *FSMHandle { // // If the FSM instance is not executing, a new one will be started. If the event // is not valid for the current state, an error will be returned. +// +// To schedule the next event for an instance of the FSM from within a transition, +// use ftl.FSMNext(ctx, event). func (f *FSMHandle) Send(ctx context.Context, instance string, event any) error { - return internal.FromContext(ctx).FSMSend(ctx, f.name, instance, event) + return internal.FromContext(ctx).FSMSend(ctx, f.name, instance, event) //nolint:wrapcheck +} + +// FSMNext schedules the next event for an instance of the FSM from within a transition. +// +// "instance" must uniquely identify an instance of the FSM. The event type must +// be valid for the state the FSM instance is currently transitioning to. +// +// If the event is not valid for the state the FSM is in transition to, an error will +// be returned. If there is already a next event scheduled for the instance of the FSM +// an error will be returned. +func FSMNext(ctx context.Context, event any) error { + metadata := internal.CallMetadataFromContext(ctx) + name, ok := metadata[internal.FSMNameMetadataKey] + if !ok { + return fmt.Errorf("could not schedule next FSM transition while not within an FSM transition: missing fsm name") + } + instance, ok := metadata[internal.FSMInstanceMetadataKey] + if !ok { + return fmt.Errorf("could not schedule next FSM transition while not within an FSM transition: missing fsm instance") + } + return internal.FromContext(ctx).FSMNext(ctx, name, instance, event) //nolint:wrapcheck } diff --git a/go-runtime/ftl/ftltest/fake.go b/go-runtime/ftl/ftltest/fake.go index c94562c9d..ecb541f51 100644 --- a/go-runtime/ftl/ftltest/fake.go +++ b/go-runtime/ftl/ftltest/fake.go @@ -117,6 +117,10 @@ func (f *fakeFTL) FSMSend(ctx context.Context, fsm string, instance string, even return f.fsm.SendEvent(ctx, fsm, instance, event) } +func (f *fakeFTL) FSMNext(ctx context.Context, fsm, instance string, event any) error { + return f.fsm.SetNextFSMEvent(ctx, fsm, instance, event) +} + // addMapMock saves a new mock of ftl.Map to the internal map in fakeFTL. // // mockMap provides the whole mock implemention, so it gets called in place of both `fn` diff --git a/go-runtime/ftl/ftltest/fsm.go b/go-runtime/ftl/ftltest/fsm.go index 6d0b06ff2..206574db2 100644 --- a/go-runtime/ftl/ftltest/fsm.go +++ b/go-runtime/ftl/ftltest/fsm.go @@ -5,13 +5,16 @@ import ( "fmt" "reflect" + "github.com/TBD54566975/ftl/go-runtime/ftl" "github.com/TBD54566975/ftl/go-runtime/ftl/reflection" + "github.com/TBD54566975/ftl/go-runtime/internal" ) type fakeFSMInstance struct { name string terminated bool state reflect.Value + next ftl.Option[any] } func newFakeFSMManager() *fakeFSMManager { @@ -62,18 +65,28 @@ func (f *fakeFSMManager) SendEvent(ctx context.Context, fsm string, instance str // Didn't find a transition. if !transition.To.IsValid() { - return fmt.Errorf("no transition found for event %T", event) + if fsmInstance.state.IsValid() { + return fmt.Errorf(`invalid event "%T" for state "%v"`, event, fsmInstance.state.Type().In(1)) + } + return fmt.Errorf(`invalid event "%T" for new instance`, event) } - out := transition.To.Call([]reflect.Value{reflect.ValueOf(ctx), reflect.ValueOf(event)}) - var err error + callCtx := internal.ContextWithCallMetadata(ctx, map[internal.MetadataKey]string{ + "fsmName": fsm, + "fsmInstance": instance, + }) + out := transition.To.Call([]reflect.Value{reflect.ValueOf(callCtx), reflect.ValueOf(event)}) erri := out[0] - if erri.IsNil() { - fsmInstance.state = transition.To - } else { - err = erri.Interface().(error) //nolint:forcetypeassert + if !erri.IsNil() { + err := erri.Interface().(error) //nolint:forcetypeassert fsmInstance.state = reflect.Value{} + fsmInstance.next = ftl.None[any]() + fsmInstance.terminated = true + return err } + + fsmInstance.state = transition.To + currentStateRef := reflection.FuncRef(fsmInstance.state.Interface()).ToSchema() // Flag the FSM instance as terminated if the current state is a terminal state. @@ -83,5 +96,26 @@ func (f *fakeFSMManager) SendEvent(ctx context.Context, fsm string, instance str break } } - return err + + if next, ok := fsmInstance.next.Get(); ok { + fsmInstance.next = ftl.None[any]() + return f.SendEvent(ctx, fsm, instance, next) + } + return nil +} + +func (f *fakeFSMManager) SetNextFSMEvent(ctx context.Context, fsm string, instance string, event any) error { + key := fsmInstanceKey{fsm, instance} + fsmInstance, ok := f.instances[key] + if !ok { + return fmt.Errorf("fsm %q instance %q not found", fsm, instance) + } + if fsmInstance.terminated { + return fmt.Errorf("fsm %q instance %q is terminated", fsm, instance) + } + if _, ok := fsmInstance.next.Get(); ok { + return fmt.Errorf("fsm %q instance %q already has a pending event", fsm, instance) + } + fsmInstance.next = ftl.Some(event) + return nil } diff --git a/go-runtime/internal/api.go b/go-runtime/internal/api.go index edef7077c..c881cd46e 100644 --- a/go-runtime/internal/api.go +++ b/go-runtime/internal/api.go @@ -6,6 +6,13 @@ import ( "github.com/TBD54566975/ftl/backend/schema" ) +type MetadataKey string + +const ( + FSMNameMetadataKey MetadataKey = "fsmName" + FSMInstanceMetadataKey MetadataKey = "fsmInstance" +) + // FTL is the interface that the FTL runtime provides to user code. // // In production, the FTL runtime will provide an implementation of this @@ -17,6 +24,9 @@ type FTL interface { // FSMSend sends an event to an instance of an FSM. FSMSend(ctx context.Context, fsm, instance string, data any) error + // FSMSend schedules the next transition for an FSM from within an FSM transition. + FSMNext(ctx context.Context, fsm, instance string, data any) error + // PublishEvent sends an event to a pubsub topic. PublishEvent(ctx context.Context, topic *schema.Ref, event any) error @@ -49,3 +59,19 @@ func FromContext(ctx context.Context) FTL { } return ftl } + +type callMetadataKey struct{} + +// ContextWithCallMetadata returns a new context with the call metadata. +func ContextWithCallMetadata(ctx context.Context, metadata map[MetadataKey]string) context.Context { + return context.WithValue(ctx, callMetadataKey{}, metadata) +} + +// CallMetadataFromContext returns the call metadata from the context. +func CallMetadataFromContext(ctx context.Context) map[MetadataKey]string { + metadata, ok := ctx.Value(callMetadataKey{}).(map[MetadataKey]string) + if !ok { + panic("Call metadata not found in context") + } + return metadata +} diff --git a/go-runtime/internal/impl.go b/go-runtime/internal/impl.go index 458830a37..7e1257341 100644 --- a/go-runtime/internal/impl.go +++ b/go-runtime/internal/impl.go @@ -63,7 +63,25 @@ func (r *RealFTL) FSMSend(ctx context.Context, fsm, instance string, event any) Body: body, })) if err != nil { - return fmt.Errorf("failed to send event: %w", err) + return fmt.Errorf("failed to send fsm event: %w", err) + } + return nil +} + +func (r *RealFTL) FSMNext(ctx context.Context, fsm, instance string, event any) error { + client := rpc.ClientFromContext[ftlv1connect.VerbServiceClient](ctx) + body, err := encoding.Marshal(event) + if err != nil { + return fmt.Errorf("failed to marshal event: %w", err) + } + _, err = client.SetNextFSMEvent(ctx, connect.NewRequest(&ftlv1.SendFSMEventRequest{ + Fsm: &schemapb.Ref{Module: reflection.Module(), Name: fsm}, + Instance: instance, + Event: schema.TypeToProto(reflection.ReflectTypeToSchemaType(reflect.TypeOf(event))), + Body: body, + })) + if err != nil { + return fmt.Errorf("failed to set next fsm event: %w", err) } return nil } diff --git a/go-runtime/server/server.go b/go-runtime/server/server.go index 25c73835d..eea5d5846 100644 --- a/go-runtime/server/server.go +++ b/go-runtime/server/server.go @@ -57,13 +57,15 @@ func NewUserVerbServer(projectName string, moduleName string, handlers ...Handle // Handler for a Verb. type Handler struct { ref reflection.Ref - fn func(ctx context.Context, req []byte) ([]byte, error) + fn func(ctx context.Context, req []byte, metadata map[internal.MetadataKey]string) ([]byte, error) } func handler[Req, Resp any](ref reflection.Ref, verb func(ctx context.Context, req Req) (Resp, error)) Handler { return Handler{ ref: ref, - fn: func(ctx context.Context, reqdata []byte) ([]byte, error) { + fn: func(ctx context.Context, reqdata []byte, metadata map[internal.MetadataKey]string) ([]byte, error) { + ctx = internal.ContextWithCallMetadata(ctx, metadata) + // Decode request. var req Req err := encoding.Unmarshal(reqdata, &req) @@ -141,12 +143,20 @@ func (m *moduleServer) Call(ctx context.Context, req *connect.Request[ftlv1.Call }}}) } }() + handler, ok := m.handlers[reflection.RefFromProto(req.Msg.Verb)] if !ok { return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("verb %s.%s not found", req.Msg.Verb.Module, req.Msg.Verb.Name)) } - respdata, err := handler.fn(ctx, req.Msg.Body) + metadata := map[internal.MetadataKey]string{} + if req.Msg.Metadata != nil { + for _, pair := range req.Msg.Metadata.Values { + metadata[internal.MetadataKey(pair.Key)] = pair.Value + } + } + + respdata, err := handler.fn(ctx, req.Msg.Body, metadata) if err != nil { // This makes me slightly ill. return connect.NewResponse(&ftlv1.CallResponse{ @@ -175,6 +185,10 @@ func (m *moduleServer) SendFSMEvent(context.Context, *connect.Request[ftlv1.Send return nil, connect.NewError(connect.CodeUnimplemented, fmt.Errorf("SendFSMEvent not implemented")) } +func (m *moduleServer) SetNextFSMEvent(ctx context.Context, req *connect.Request[ftlv1.SendFSMEventRequest]) (*connect.Response[ftlv1.SendFSMEventResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, fmt.Errorf("SetNextFSMEvent not implemented")) +} + func (m *moduleServer) PublishEvent(context.Context, *connect.Request[ftlv1.PublishEventRequest]) (*connect.Response[ftlv1.PublishEventResponse], error) { return nil, connect.NewError(connect.CodeUnimplemented, fmt.Errorf("PublishEvent not implemented")) } diff --git a/internal/encryption/integration_test.go b/internal/encryption/integration_test.go index d56462e10..539647b41 100644 --- a/internal/encryption/integration_test.go +++ b/internal/encryption/integration_test.go @@ -91,13 +91,25 @@ func TestEncryptionForFSM(t *testing.T) { WithEncryption(), in.CopyModule("encryption"), in.Deploy("encryption"), + // "Rosebud" goes from created -> paid to test normal encryption in.Call[map[string]interface{}, any]("encryption", "beginFsm", map[string]interface{}{"name": "Rosebud"}, nil), - in.Sleep(3*time.Second), - in.Call[map[string]interface{}, any]("encryption", "transitionFsm", map[string]interface{}{"name": "Rosebud"}, nil), - in.Sleep(3*time.Second), - + in.Sleep(2*time.Second), + in.Call[map[string]interface{}, any]("encryption", "transitionToPaid", map[string]interface{}{"name": "Rosebud"}, nil), + in.Sleep(2*time.Second), validateAsyncCall("created", "Rosebud"), validateAsyncCall("paid", "Rosebud"), + + // "Next" goes from created -> nextAndSleep to test fsm next event encryption + in.Call[map[string]interface{}, any]("encryption", "beginFsm", map[string]interface{}{"name": "Next"}, nil), + in.Sleep(2*time.Second), + in.Call[map[string]interface{}, any]("encryption", "transitionToNextAndSleep", map[string]interface{}{"name": "Next"}, nil), + func(t testing.TB, ic in.TestContext) { + in.QueryRow("ftl", "SELECT COUNT(*) FROM fsm_next_event LIMIT 1", int64(1))(t, ic) + values := in.GetRow(t, ic, "ftl", "SELECT request FROM fsm_next_event LIMIT 1", 1) + request, ok := values[0].([]byte) + assert.True(t, ok, "could not convert payload to bytes") + assert.NotContains(t, string(request), "Next", "raw request string should not be stored in the table") + }, ) } @@ -107,7 +119,7 @@ func validateAsyncCall(verb string, sensitive string) in.Action { values := in.GetRow(t, ic, "ftl", fmt.Sprintf("SELECT request FROM async_calls WHERE verb = 'encryption.%s' AND state = 'success'", verb), 1) request, ok := values[0].([]byte) - assert.True(t, ok, "could not convert payload to string") + assert.True(t, ok, "could not convert payload to bytes") assert.NotContains(t, string(request), sensitive, "raw request string should not be stored in the table") } } diff --git a/internal/encryption/testdata/go/encryption/encryption.go b/internal/encryption/testdata/go/encryption/encryption.go index ef203df42..c10dfff3b 100644 --- a/internal/encryption/testdata/go/encryption/encryption.go +++ b/internal/encryption/testdata/go/encryption/encryption.go @@ -3,6 +3,7 @@ package encryption import ( "context" "fmt" + "time" "github.com/TBD54566975/ftl/go-runtime/ftl" // Import the FTL SDK. ) @@ -55,12 +56,13 @@ func Consume(ctx context.Context, e Event) error { // FSM // // Used to test encryption of async_calls tables via FSM operations - var fsm = ftl.FSM("payment", ftl.Start(Created), ftl.Start(Paid), ftl.Transition(Created, Paid), ftl.Transition(Paid, Completed), + ftl.Transition(Created, NextAndSleep), + ftl.Transition(NextAndSleep, Completed), ) type OnlinePaymentCompleted struct { @@ -73,14 +75,23 @@ type OnlinePaymentCreated struct { Name string `json:"name"` } +type NextAndSleepEvent struct { + Name string `json:"name"` +} + //ftl:verb func BeginFSM(ctx context.Context, req OnlinePaymentCreated) error { - return fsm.Send(ctx, "test", req) + return fsm.Send(ctx, req.Name, req) +} + +//ftl:verb +func TransitionToPaid(ctx context.Context, req OnlinePaymentPaid) error { + return fsm.Send(ctx, req.Name, req) } //ftl:verb -func TransitionFSM(ctx context.Context, req OnlinePaymentPaid) error { - return fsm.Send(ctx, "test", req) +func TransitionToNextAndSleep(ctx context.Context, req NextAndSleepEvent) error { + return fsm.Send(ctx, req.Name, req) } //ftl:verb @@ -97,3 +108,15 @@ func Created(ctx context.Context, in OnlinePaymentCreated) error { func Paid(ctx context.Context, in OnlinePaymentPaid) error { return nil } + +// NextAndSleep calls fsm.Next() and then sleeps so we can test what is put into the fsm next event table +// +//ftl:verb +func NextAndSleep(ctx context.Context, in NextAndSleepEvent) error { + err := ftl.FSMNext(ctx, OnlinePaymentCompleted{Name: in.Name}) + if err != nil { + return err + } + time.Sleep(1 * time.Minute) + return nil +} diff --git a/internal/encryption/testdata/go/encryption/go.mod b/internal/encryption/testdata/go/encryption/go.mod index d5490f211..bd5434419 100644 --- a/internal/encryption/testdata/go/encryption/go.mod +++ b/internal/encryption/testdata/go/encryption/go.mod @@ -1,6 +1,6 @@ module ftl/encryption -go 1.22.5 +go 1.22.2 require github.com/TBD54566975/ftl v1.1.5 diff --git a/internal/slices/slices.go b/internal/slices/slices.go index ed4d9bdd0..4c7b99ef7 100644 --- a/internal/slices/slices.go +++ b/internal/slices/slices.go @@ -94,6 +94,7 @@ func Find[T any](slice []T, fn func(T) bool) (T, bool) { return zero, false } +// FindVariant finds the first element in a slice that can be cast to the given type. func FindVariant[T any, U any](slice []U) (T, bool) { for _, el := range slice { if found, ok := any(el).(T); ok {