From 2ada843ba5cd52869b06bcc46201e5e4f49ccbf9 Mon Sep 17 00:00:00 2001 From: Matt Toohey Date: Thu, 15 Aug 2024 10:22:58 +1000 Subject: [PATCH 1/8] feat: allow FSM to specify the next event # Conflicts: # backend/controller/dal/fsm.go # backend/controller/dal/fsm_integration_test.go # internal/encryption/integration_test.go --- backend/controller/controller.go | 129 +++-- backend/controller/cronjobs/sql/models.go | 9 + backend/controller/cronjobs/sql/types.go | 5 + backend/controller/dal/fsm.go | 74 ++- .../controller/dal/fsm_integration_test.go | 65 ++- backend/controller/dal/fsm_test.go | 4 +- .../dal/testdata/go/fsmnext/fsmnext.go | 121 +++++ .../dal/testdata/go/fsmnext/fsmnext_test.go | 36 ++ .../dal/testdata/go/fsmnext/ftl.toml | 2 + .../controller/dal/testdata/go/fsmnext/go.mod | 64 +++ .../controller/dal/testdata/go/fsmnext/go.sum | 194 +++++++ backend/controller/pubsub/manager.go | 6 +- backend/controller/sql/models.go | 9 + backend/controller/sql/querier.go | 2 + backend/controller/sql/queries.sql | 19 + backend/controller/sql/queries.sql.go | 56 ++ .../20240813062546_create_fsm_next_events.sql | 17 + backend/controller/sql/types.go | 6 +- backend/protos/xyz/block/ftl/v1/ftl.pb.go | 486 +++++++++--------- backend/protos/xyz/block/ftl/v1/ftl.proto | 3 + .../block/ftl/v1/ftlv1connect/ftl.connect.go | 29 ++ backend/runner/runner.go | 4 + backend/schema/fsm.go | 34 ++ backend/schema/parser.go | 1 + common/configuration/sql/models.go | 9 + common/configuration/sql/types.go | 5 + docs/content/docs/reference/fsm.md | 12 +- .../protos/xyz/block/ftl/v1/ftl_connect.ts | 11 + go-runtime/ftl/fsm.go | 14 +- go-runtime/ftl/ftltest/fake.go | 4 + go-runtime/ftl/ftltest/fsm.go | 38 +- go-runtime/internal/api.go | 3 + go-runtime/internal/impl.go | 20 +- go-runtime/server/server.go | 4 + internal/encryption/integration_test.go | 22 +- .../testdata/go/encryption/encryption.go | 46 +- internal/slices/slices.go | 1 + 37 files changed, 1256 insertions(+), 308 deletions(-) create mode 100644 backend/controller/cronjobs/sql/types.go create mode 100644 backend/controller/dal/testdata/go/fsmnext/fsmnext.go create mode 100644 backend/controller/dal/testdata/go/fsmnext/fsmnext_test.go create mode 100644 backend/controller/dal/testdata/go/fsmnext/ftl.toml create mode 100644 backend/controller/dal/testdata/go/fsmnext/go.mod create mode 100644 backend/controller/dal/testdata/go/fsmnext/go.sum create mode 100644 backend/controller/sql/schema/20240813062546_create_fsm_next_events.sql create mode 100644 common/configuration/sql/types.go diff --git a/backend/controller/controller.go b/backend/controller/controller.go index 3b79041c1..182fe7890 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(), body, encrypted, retryParams) + if err != nil { + 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) } - err = tx.StartFSMTransition(ctx, instance.FSM, instance.Key, destinationRef.ToRefKey(), msg.Body, retryParams) + // Get the current state the instance is transitioning to. + _, currentDestinationState, err := tx.GetFSMStates(ctx, fsmKey, req.Msg.Instance) if err != nil { - return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("could not start fsm transition: %w", err)) + 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 } @@ -1504,22 +1551,17 @@ func (s *Service) catchAsyncCall(ctx context.Context, logger *log.Logger, call * } 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 - } - _, 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 +1571,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 +1594,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 +1604,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 +1614,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..b69926edc 100644 --- a/backend/controller/dal/fsm.go +++ b/backend/controller/dal/fsm.go @@ -31,14 +31,14 @@ 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) { +func (d *DAL) StartFSMTransition(ctx context.Context, fsm schema.RefKey, instanceKey string, destinationState schema.RefKey, request []byte, encrypted bool, 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) } // 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 +62,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 +80,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 +109,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..5797dd9df 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) { @@ -134,3 +135,63 @@ func TestFSMGoTests(t *testing.T) { in.ExecModuleTest("fsm"), ) } + +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"), + + 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"), + ) +} 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/fsmnext/fsmnext.go b/backend/controller/dal/testdata/go/fsmnext/fsmnext.go new file mode 100644 index 000000000..c2b4fa174 --- /dev/null +++ b/backend/controller/dal/testdata/go/fsmnext/fsmnext.go @@ -0,0 +1,121 @@ +package fsmnext + +import ( + "context" + "errors" + + "github.com/TBD54566975/ftl/go-runtime/ftl" +) + +func fsm() *ftl.FSMHandle { + // 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), + ) + return fsm +} + +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 := fsm().Next(ctx, in.Instance, 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..f7ed57c06 --- /dev/null +++ b/backend/controller/dal/testdata/go/fsmnext/fsmnext_test.go @@ -0,0 +1,36 @@ +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`) +} 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..7563d73ec --- /dev/null +++ b/backend/controller/dal/testdata/go/fsmnext/go.mod @@ -0,0 +1,64 @@ +module ftl/fsmnext + +go 1.22.5 + +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..ef4547cbc 100644 --- a/go-runtime/ftl/fsm.go +++ b/go-runtime/ftl/fsm.go @@ -57,5 +57,17 @@ 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. 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 +} + +// Next 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 (f *FSMHandle) Next(ctx context.Context, instance string, event any) error { + return internal.FromContext(ctx).FSMNext(ctx, f.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..da9166679 100644 --- a/go-runtime/ftl/ftltest/fsm.go +++ b/go-runtime/ftl/ftltest/fsm.go @@ -5,6 +5,7 @@ import ( "fmt" "reflect" + "github.com/TBD54566975/ftl/go-runtime/ftl" "github.com/TBD54566975/ftl/go-runtime/ftl/reflection" ) @@ -12,6 +13,7 @@ type fakeFSMInstance struct { name string terminated bool state reflect.Value + next ftl.Option[any] } func newFakeFSMManager() *fakeFSMManager { @@ -66,14 +68,17 @@ func (f *fakeFSMManager) SendEvent(ctx context.Context, fsm string, instance str } out := transition.To.Call([]reflect.Value{reflect.ValueOf(ctx), reflect.ValueOf(event)}) - var err error 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 +88,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..9e925e378 100644 --- a/go-runtime/internal/api.go +++ b/go-runtime/internal/api.go @@ -17,6 +17,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 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..22d3a2877 100644 --- a/go-runtime/server/server.go +++ b/go-runtime/server/server.go @@ -175,6 +175,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..a4923bf32 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,13 +56,17 @@ 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), -) +func fsm() *ftl.FSMHandle { + 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), + ) + return fsm +} type OnlinePaymentCompleted struct { Name string `json:"name"` @@ -73,14 +78,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 TransitionFSM(ctx context.Context, req OnlinePaymentPaid) error { - return fsm.Send(ctx, "test", req) +func TransitionToPaid(ctx context.Context, req OnlinePaymentPaid) error { + return fsm().Send(ctx, req.Name, req) +} + +//ftl:verb +func TransitionToNextAndSleep(ctx context.Context, req NextAndSleepEvent) error { + return fsm().Send(ctx, req.Name, req) } //ftl:verb @@ -97,3 +111,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 := fsm().Next(ctx, in.Name, OnlinePaymentCompleted{Name: in.Name}) + if err != nil { + return err + } + time.Sleep(1 * time.Minute) + return nil +} 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 { From 5ec6b795b1b6bc2312ad638f1cef1c02e473bcbe Mon Sep 17 00:00:00 2001 From: Matt Toohey Date: Thu, 15 Aug 2024 11:35:21 +1000 Subject: [PATCH 2/8] move from fsm.Next() to FSMNext() --- backend/controller/controller.go | 34 ++++++++++++++++--- .../dal/testdata/go/fsmnext/fsmnext.go | 33 ++++++++---------- go-runtime/ftl/fsm.go | 19 +++++++++-- go-runtime/internal/api.go | 16 +++++++++ go-runtime/server/server.go | 16 +++++++-- .../testdata/go/encryption/encryption.go | 27 +++++++-------- 6 files changed, 102 insertions(+), 43 deletions(-) diff --git a/backend/controller/controller.go b/backend/controller/controller.go index 182fe7890..84177fc65 100644 --- a/backend/controller/controller.go +++ b/backend/controller/controller.go @@ -1453,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] @@ -1511,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] @@ -1550,6 +1552,30 @@ func (s *Service) catchAsyncCall(ctx context.Context, logger *log.Logger, call * 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]) diff --git a/backend/controller/dal/testdata/go/fsmnext/fsmnext.go b/backend/controller/dal/testdata/go/fsmnext/fsmnext.go index c2b4fa174..d93292103 100644 --- a/backend/controller/dal/testdata/go/fsmnext/fsmnext.go +++ b/backend/controller/dal/testdata/go/fsmnext/fsmnext.go @@ -7,22 +7,19 @@ import ( "github.com/TBD54566975/ftl/go-runtime/ftl" ) -func fsm() *ftl.FSMHandle { - // 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), - ) - return fsm -} +// 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 @@ -80,7 +77,7 @@ type Request struct { //ftl:verb export func SendOne(ctx context.Context, in Request) error { - return fsm().Send(ctx, in.Event.Instance, eventFor(in.Event, in.State)) + return fsm.Send(ctx, in.Event.Instance, eventFor(in.Event, in.State)) } func handleEvent(ctx context.Context, in Event) error { @@ -95,7 +92,7 @@ func handleEvent(ctx context.Context, in Event) error { 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 := fsm().Next(ctx, in.Instance, event); err != nil { + if err := ftl.FSMNext(ctx, event); err != nil { return err } } diff --git a/go-runtime/ftl/fsm.go b/go-runtime/ftl/fsm.go index ef4547cbc..49e5954ea 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,11 +57,14 @@ 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) //nolint:wrapcheck } -// Next schedules the next event for an instance of the FSM from within a transition. +// 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. @@ -68,6 +72,15 @@ func (f *FSMHandle) Send(ctx context.Context, instance string, event any) error // 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 (f *FSMHandle) Next(ctx context.Context, instance string, event any) error { - return internal.FromContext(ctx).FSMNext(ctx, f.name, instance, event) //nolint:wrapcheck +func FSMNext(ctx context.Context, event any) error { + metadata := internal.CallMetadataFromContext(ctx) + name, ok := metadata["fsmName"] + if !ok { + return fmt.Errorf("could not schedule next FSM transition while not within an FSM transition: missing fsm name") + } + instance, ok := metadata["fsmInstance"] + 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/internal/api.go b/go-runtime/internal/api.go index 9e925e378..40270ea5d 100644 --- a/go-runtime/internal/api.go +++ b/go-runtime/internal/api.go @@ -52,3 +52,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[string]string) context.Context { + return context.WithValue(ctx, callMetadataKey{}, metadata) +} + +// CallMetadataFromContext returns the call metadata from the context. +func CallMetadataFromContext(ctx context.Context) map[string]string { + metadata, ok := ctx.Value(callMetadataKey{}).(map[string]string) + if !ok { + panic("Call metadata not found in context") + } + return metadata +} diff --git a/go-runtime/server/server.go b/go-runtime/server/server.go index 22d3a2877..ad1d193b4 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[string]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[string]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[string]string{} + if req.Msg.Metadata != nil { + for _, pair := range req.Msg.Metadata.Values { + metadata[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{ diff --git a/internal/encryption/testdata/go/encryption/encryption.go b/internal/encryption/testdata/go/encryption/encryption.go index a4923bf32..c10dfff3b 100644 --- a/internal/encryption/testdata/go/encryption/encryption.go +++ b/internal/encryption/testdata/go/encryption/encryption.go @@ -56,17 +56,14 @@ func Consume(ctx context.Context, e Event) error { // FSM // // Used to test encryption of async_calls tables via FSM operations -func fsm() *ftl.FSMHandle { - 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), - ) - return fsm -} +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 { Name string `json:"name"` @@ -84,17 +81,17 @@ type NextAndSleepEvent struct { //ftl:verb func BeginFSM(ctx context.Context, req OnlinePaymentCreated) error { - return fsm().Send(ctx, req.Name, 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) + return fsm.Send(ctx, req.Name, req) } //ftl:verb func TransitionToNextAndSleep(ctx context.Context, req NextAndSleepEvent) error { - return fsm().Send(ctx, req.Name, req) + return fsm.Send(ctx, req.Name, req) } //ftl:verb @@ -116,7 +113,7 @@ func Paid(ctx context.Context, in OnlinePaymentPaid) error { // //ftl:verb func NextAndSleep(ctx context.Context, in NextAndSleepEvent) error { - err := fsm().Next(ctx, in.Name, OnlinePaymentCompleted{Name: in.Name}) + err := ftl.FSMNext(ctx, OnlinePaymentCompleted{Name: in.Name}) if err != nil { return err } From 8ad38d3d6250e46427fb4e2aa3555ab63d37e6b8 Mon Sep 17 00:00:00 2001 From: Matt Toohey Date: Thu, 15 Aug 2024 11:37:37 +1000 Subject: [PATCH 3/8] fix fsm encryption after rebase --- backend/controller/dal/fsm.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/backend/controller/dal/fsm.go b/backend/controller/dal/fsm.go index b69926edc..b9b871b26 100644 --- a/backend/controller/dal/fsm.go +++ b/backend/controller/dal/fsm.go @@ -32,9 +32,14 @@ import ( // // Note: no validation of the FSM is performed. func (d *DAL) StartFSMTransition(ctx context.Context, fsm schema.RefKey, instanceKey string, destinationState schema.RefKey, request []byte, encrypted bool, 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) + 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. From be2280e46cfb053263234a4ee6dbb0fe2daea17a Mon Sep 17 00:00:00 2001 From: Matt Toohey Date: Thu, 15 Aug 2024 11:37:51 +1000 Subject: [PATCH 4/8] fix integration test and run module unit tests --- backend/controller/dal/fsm_integration_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/backend/controller/dal/fsm_integration_test.go b/backend/controller/dal/fsm_integration_test.go index 5797dd9df..b829a208d 100644 --- a/backend/controller/dal/fsm_integration_test.go +++ b/backend/controller/dal/fsm_integration_test.go @@ -131,8 +131,10 @@ 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"), ) } @@ -174,7 +176,7 @@ func TestFSMNext(t *testing.T) { } } - in.Run(t, "", + in.Run(t, in.CopyModule("fsmnext"), in.Deploy("fsmnext"), From d2fa8707dd793c8fc9f13d4d2fd69d36a0d23b25 Mon Sep 17 00:00:00 2001 From: Matt Toohey Date: Thu, 15 Aug 2024 14:12:06 +1000 Subject: [PATCH 5/8] lessen use of magic strings for metadata keys --- go-runtime/ftl/fsm.go | 4 ++-- go-runtime/ftl/ftltest/fsm.go | 9 +++++++-- go-runtime/internal/api.go | 13 ++++++++++--- go-runtime/server/server.go | 8 ++++---- 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/go-runtime/ftl/fsm.go b/go-runtime/ftl/fsm.go index 49e5954ea..c3abce024 100644 --- a/go-runtime/ftl/fsm.go +++ b/go-runtime/ftl/fsm.go @@ -74,11 +74,11 @@ func (f *FSMHandle) Send(ctx context.Context, instance string, event any) error // an error will be returned. func FSMNext(ctx context.Context, event any) error { metadata := internal.CallMetadataFromContext(ctx) - name, ok := metadata["fsmName"] + 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["fsmInstance"] + 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") } diff --git a/go-runtime/ftl/ftltest/fsm.go b/go-runtime/ftl/ftltest/fsm.go index da9166679..371038198 100644 --- a/go-runtime/ftl/ftltest/fsm.go +++ b/go-runtime/ftl/ftltest/fsm.go @@ -7,6 +7,7 @@ import ( "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 { @@ -64,10 +65,14 @@ 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) + return fmt.Errorf(`invalid event "%T" for state "%v"`, event, fsmInstance.state.Type().In(1)) } - out := transition.To.Call([]reflect.Value{reflect.ValueOf(ctx), reflect.ValueOf(event)}) + callCtx := internal.ContextWithCallMetadata(ctx, map[string]string{ + "fsmName": fsm, + "fsmInstance": instance, + }) + out := transition.To.Call([]reflect.Value{reflect.ValueOf(callCtx), reflect.ValueOf(event)}) erri := out[0] if !erri.IsNil() { err := erri.Interface().(error) //nolint:forcetypeassert diff --git a/go-runtime/internal/api.go b/go-runtime/internal/api.go index 40270ea5d..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 @@ -56,13 +63,13 @@ func FromContext(ctx context.Context) FTL { type callMetadataKey struct{} // ContextWithCallMetadata returns a new context with the call metadata. -func ContextWithCallMetadata(ctx context.Context, metadata map[string]string) context.Context { +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[string]string { - metadata, ok := ctx.Value(callMetadataKey{}).(map[string]string) +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") } diff --git a/go-runtime/server/server.go b/go-runtime/server/server.go index ad1d193b4..eea5d5846 100644 --- a/go-runtime/server/server.go +++ b/go-runtime/server/server.go @@ -57,13 +57,13 @@ 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, metadata map[string]string) ([]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, metadata map[string]string) ([]byte, error) { + fn: func(ctx context.Context, reqdata []byte, metadata map[internal.MetadataKey]string) ([]byte, error) { ctx = internal.ContextWithCallMetadata(ctx, metadata) // Decode request. @@ -149,10 +149,10 @@ func (m *moduleServer) Call(ctx context.Context, req *connect.Request[ftlv1.Call return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("verb %s.%s not found", req.Msg.Verb.Module, req.Msg.Verb.Name)) } - metadata := map[string]string{} + metadata := map[internal.MetadataKey]string{} if req.Msg.Metadata != nil { for _, pair := range req.Msg.Metadata.Values { - metadata[pair.Key] = pair.Value + metadata[internal.MetadataKey(pair.Key)] = pair.Value } } From 86a73210099c969c7e2e606b342b4c2e8f628adf Mon Sep 17 00:00:00 2001 From: Matt Toohey Date: Thu, 15 Aug 2024 14:12:21 +1000 Subject: [PATCH 6/8] add tests for bad transitions with fsmnext --- backend/controller/dal/fsm_integration_test.go | 4 ++++ .../dal/testdata/go/fsmnext/fsmnext_test.go | 13 +++++++++++++ 2 files changed, 17 insertions(+) diff --git a/backend/controller/dal/fsm_integration_test.go b/backend/controller/dal/fsm_integration_test.go index b829a208d..6ab7cad22 100644 --- a/backend/controller/dal/fsm_integration_test.go +++ b/backend/controller/dal/fsm_integration_test.go @@ -189,11 +189,15 @@ func TestFSMNext(t *testing.T) { // 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/testdata/go/fsmnext/fsmnext_test.go b/backend/controller/dal/testdata/go/fsmnext/fsmnext_test.go index f7ed57c06..294e5816e 100644 --- a/backend/controller/dal/testdata/go/fsmnext/fsmnext_test.go +++ b/backend/controller/dal/testdata/go/fsmnext/fsmnext_test.go @@ -34,3 +34,16 @@ func TestDoubleNext(t *testing.T) { }, }).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"`) +} From 5f3a3a00b9d344c10909ed9c1c99a8c0e0814718 Mon Sep 17 00:00:00 2001 From: Matt Toohey Date: Thu, 15 Aug 2024 14:17:54 +1000 Subject: [PATCH 7/8] cleanup --- backend/controller/dal/testdata/go/fsm/go.mod | 2 ++ backend/controller/dal/testdata/go/fsm/go.sum | 8 ++++++++ backend/controller/dal/testdata/go/fsmnext/go.mod | 2 +- go-runtime/ftl/ftltest/fsm.go | 2 +- internal/encryption/testdata/go/encryption/go.mod | 2 +- 5 files changed, 13 insertions(+), 3 deletions(-) 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/go.mod b/backend/controller/dal/testdata/go/fsmnext/go.mod index 7563d73ec..ebb3f6575 100644 --- a/backend/controller/dal/testdata/go/fsmnext/go.mod +++ b/backend/controller/dal/testdata/go/fsmnext/go.mod @@ -1,6 +1,6 @@ module ftl/fsmnext -go 1.22.5 +go 1.22.2 require ( github.com/TBD54566975/ftl v1.1.5 diff --git a/go-runtime/ftl/ftltest/fsm.go b/go-runtime/ftl/ftltest/fsm.go index 371038198..619faaa99 100644 --- a/go-runtime/ftl/ftltest/fsm.go +++ b/go-runtime/ftl/ftltest/fsm.go @@ -68,7 +68,7 @@ func (f *fakeFSMManager) SendEvent(ctx context.Context, fsm string, instance str return fmt.Errorf(`invalid event "%T" for state "%v"`, event, fsmInstance.state.Type().In(1)) } - callCtx := internal.ContextWithCallMetadata(ctx, map[string]string{ + callCtx := internal.ContextWithCallMetadata(ctx, map[internal.MetadataKey]string{ "fsmName": fsm, "fsmInstance": instance, }) 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 From 1a77cb439ed257e286ee8cbba92239d7ee9d083d Mon Sep 17 00:00:00 2001 From: Matt Toohey Date: Thu, 15 Aug 2024 14:35:59 +1000 Subject: [PATCH 8/8] fix panic --- go-runtime/ftl/ftltest/fsm.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/go-runtime/ftl/ftltest/fsm.go b/go-runtime/ftl/ftltest/fsm.go index 619faaa99..206574db2 100644 --- a/go-runtime/ftl/ftltest/fsm.go +++ b/go-runtime/ftl/ftltest/fsm.go @@ -65,7 +65,10 @@ func (f *fakeFSMManager) SendEvent(ctx context.Context, fsm string, instance str // Didn't find a transition. if !transition.To.IsValid() { - return fmt.Errorf(`invalid event "%T" for state "%v"`, event, fsmInstance.state.Type().In(1)) + 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) } callCtx := internal.ContextWithCallMetadata(ctx, map[internal.MetadataKey]string{