Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
6515346
chore(deps): upgrade go-libs to v5
flemzord Jun 4, 2026
e531173
fix(triggers): repair broken name filter in ListTriggers
flemzord Jun 11, 2026
258ceba
fix(triggers): restrict link() to an allow-listed host (SSRF/token leak)
flemzord Jun 11, 2026
5a3828a
fix(workflow): target the -main run in Wait and AbortRun, fix error h…
flemzord Jun 11, 2026
5703bf5
fix(triggers): make event processing idempotent under redelivery and …
flemzord Jun 11, 2026
56ca433
fix(triggers): surface listener panics as errors instead of dropping …
flemzord Jun 11, 2026
eaa5779
fix(wait_event): drain signals with a blocking Receive loop
flemzord Jun 11, 2026
8beb773
fix(workflow): run termination bookkeeping on a cancellation-safe con…
flemzord Jun 11, 2026
9360f09
fix(workflow): stop using soft-deleted workflows
flemzord Jun 11, 2026
87a3e99
fix(workflow): return errors instead of panicking in history readers
flemzord Jun 11, 2026
6762188
fix(api): map backend errors to 400/404 instead of 500
flemzord Jun 11, 2026
ddf480a
fix(worker): honour temporal-max-parallel-activities flag
flemzord Jun 11, 2026
89cde18
fix(client): remove stray duplicate generation so the SDK module comp…
flemzord Jun 11, 2026
28c44e6
fix(api): bound v1 list endpoints with a page size
flemzord Jun 11, 2026
6807b99
fix(storage): index triggers_occurrences(trigger_id, date)
flemzord Jun 11, 2026
cd45ca6
fix: resolve consolidated reliability blockers
flemzord Aug 5, 2026
44c5fcd
test: compare retries with persisted timestamps
flemzord Aug 5, 2026
5af27ee
fix(api): expose v1 pagination metadata
flemzord Aug 5, 2026
a8e1175
fix: address automated review findings
flemzord Aug 5, 2026
9091214
chore: merge main into consolidated reliability fixes
flemzord Aug 5, 2026
49f2916
fix(worker): reject activity concurrency overflow
flemzord Aug 5, 2026
ff2e4aa
fix(api): enforce pagination bounds from cursors
flemzord Aug 6, 2026
b4a5da6
fix(wait-event): handle workflow cancellation
flemzord Aug 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 48 additions & 41 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@ import (
"fmt"
"net/http"

"github.com/formancehq/go-libs/v3/auth"
"github.com/formancehq/go-libs/v3/bun/bunconnect"
"github.com/formancehq/go-libs/v3/bun/bunmigrate"
"github.com/formancehq/go-libs/v3/licence"
"github.com/formancehq/go-libs/v3/otlp"
"github.com/formancehq/go-libs/v3/otlp/otlpmetrics"
"github.com/formancehq/go-libs/v3/otlp/otlptraces"
"github.com/formancehq/go-libs/v3/publish"
"github.com/formancehq/go-libs/v3/service"
"github.com/formancehq/go-libs/v3/temporal"
"github.com/formancehq/go-libs/v5/pkg/fx/authnfx"
"github.com/formancehq/go-libs/v5/pkg/fx/messagingfx"
"github.com/formancehq/go-libs/v5/pkg/fx/observefx"
"github.com/formancehq/go-libs/v5/pkg/fx/storagefx"
"github.com/formancehq/go-libs/v5/pkg/fx/workflowfx"
otlp "github.com/formancehq/go-libs/v5/pkg/observe"
otlptraces "github.com/formancehq/go-libs/v5/pkg/observe/traces"
"github.com/formancehq/go-libs/v5/pkg/service"
bunconnect "github.com/formancehq/go-libs/v5/pkg/storage/bun/connect"
bunmigrate "github.com/formancehq/go-libs/v5/pkg/storage/bun/migrate"
"github.com/formancehq/go-libs/v5/pkg/workflow/temporal"
"github.com/formancehq/orchestration/internal/storage"
"github.com/formancehq/orchestration/internal/temporalworker"
"github.com/formancehq/orchestration/internal/tracer"
Expand Down Expand Up @@ -43,6 +44,7 @@ const (
topicsFlag = "topics"
listenFlag = "listen"
workerFlag = "worker"
stackHTTPClientName = "stack"
)

func NewRootCommand() *cobra.Command {
Expand All @@ -69,19 +71,44 @@ func Execute() {
service.Execute(NewRootCommand())
}

func stackHTTPClientModule(cmd *cobra.Command) fx.Option {
return fx.Provide(fx.Annotate(func() *http.Client {
httpClient := &http.Client{
Transport: otlp.NewRoundTripper(http.DefaultTransport, service.IsDebug(cmd)),
}

stackClientID, _ := cmd.Flags().GetString(stackClientIDFlag)
stackClientSecret, _ := cmd.Flags().GetString(stackClientSecretFlag)
stackURL, _ := cmd.Flags().GetString(stackURLFlag)

if stackClientID == "" {
return httpClient
}
oauthConfig := clientcredentials.Config{
ClientID: stackClientID,
ClientSecret: stackClientSecret,
TokenURL: fmt.Sprintf("%s/api/auth/oauth/token", stackURL),
Scopes: []string{"openid", "ledger:read", "ledger:write", "wallets:read", "wallets:write", "payments:read", "payments:write"},
}
return oauthConfig.Client(context.WithValue(context.Background(),
oauth2.HTTPClient, httpClient))
}, fx.ResultTags(`name:"stack"`)))
}

func commonOptions(cmd *cobra.Command) (fx.Option, error) {
connectionOptions, err := bunconnect.ConnectionOptionsFromFlags(cmd)
connectionOptions, err := bunconnect.ConnectionOptionsFromFlags(cmd.Flags(), cmd.Context())
if err != nil {
return nil, err
}

stack, _ := cmd.Flags().GetString(stackFlag)
stackURL, _ := cmd.Flags().GetString(stackURLFlag)
temporalTaskQueue, _ := cmd.Flags().GetString(temporal.TemporalTaskQueueFlag)

return fx.Options(
otlp.FXModuleFromFlags(cmd),
otlptraces.FXModuleFromFlags(cmd),
temporal.FXModuleFromFlags(
observefx.ResourceModuleFromFlags(cmd),
observefx.TracesModuleFromFlags(cmd),
workflowfx.TemporalClientModuleFromFlags(
cmd,
tracer.Tracer,
temporal.SearchAttributes{
Expand All @@ -91,36 +118,16 @@ func commonOptions(cmd *cobra.Command) (fx.Option, error) {
),
},
),
otlpmetrics.FXModuleFromFlags(cmd),
bunconnect.Module(*connectionOptions, service.IsDebug(cmd)),
publish.FXModuleFromFlags(cmd, service.IsDebug(cmd)),
auth.FXModuleFromFlags(cmd),
licence.FXModuleFromFlags(cmd, ServiceName),
observefx.MetricsModuleFromFlags(cmd),
storagefx.BunConnectModule(*connectionOptions, service.IsDebug(cmd)),
messagingfx.PublishModuleFromFlags(cmd, service.IsDebug(cmd)),
authnfx.JWTModuleFromFlags(cmd),
Comment thread
NumaryBot marked this conversation as resolved.
Comment thread
flemzord marked this conversation as resolved.
Comment thread
flemzord marked this conversation as resolved.
authnfx.LicenceModuleFromFlags(cmd, ServiceName),
workflow.NewModule(stack, temporalTaskQueue),
triggers.NewModule(stack, temporalTaskQueue),
triggers.NewModule(stack, stackURL, temporalTaskQueue, stackHTTPClientName),
fx.Provide(func() *bunconnect.ConnectionOptions {
return connectionOptions
}),
fx.Provide(func() *http.Client {
httpClient := &http.Client{
Transport: otlp.NewRoundTripper(http.DefaultTransport, service.IsDebug(cmd)),
}

stackClientID, _ := cmd.Flags().GetString(stackClientIDFlag)
stackClientSecret, _ := cmd.Flags().GetString(stackClientSecretFlag)
stackURL, _ := cmd.Flags().GetString(stackURLFlag)

if stackClientID == "" {
return httpClient
}
oauthConfig := clientcredentials.Config{
ClientID: stackClientID,
ClientSecret: stackClientSecret,
TokenURL: fmt.Sprintf("%s/api/auth/oauth/token", stackURL),
Scopes: []string{"openid", "ledger:read", "ledger:write", "wallets:read", "wallets:write", "payments:read", "payments:write"},
}
return oauthConfig.Client(context.WithValue(context.Background(),
oauth2.HTTPClient, httpClient))
}),
stackHTTPClientModule(cmd),
), nil
}
26 changes: 26 additions & 0 deletions cmd/root_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package cmd

import (
"net/http"
"testing"

"github.com/formancehq/go-libs/v5/pkg/fx/authnfx"
"github.com/stretchr/testify/require"
"go.uber.org/fx"
)

func TestCommonOptionsBuildsWithJWTAndStackHTTPClients(t *testing.T) {
cmd := newServeCommand()
app := fx.New(
fx.NopLogger,
authnfx.JWTModuleFromFlags(cmd),
stackHTTPClientModule(cmd),
fx.Invoke(fx.Annotate(
func(stackClient *http.Client) {
require.NotNil(t, stackClient)
},
fx.ParamTags(`name:"stack"`),
)),
)
require.NoError(t, app.Err())
}
34 changes: 20 additions & 14 deletions cmd/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@ package cmd
import (
"context"

"github.com/formancehq/go-libs/v3/auth"
"github.com/formancehq/go-libs/v3/aws/iam"
"github.com/formancehq/go-libs/v3/bun/bunconnect"
"github.com/formancehq/go-libs/v3/health"
"github.com/formancehq/go-libs/v3/httpserver"
"github.com/formancehq/go-libs/v3/licence"
"github.com/formancehq/go-libs/v3/otlp/otlpmetrics"
"github.com/formancehq/go-libs/v3/publish"
"github.com/formancehq/go-libs/v3/service"
"github.com/formancehq/go-libs/v3/temporal"
auth "github.com/formancehq/go-libs/v5/pkg/authn/jwt"
"github.com/formancehq/go-libs/v5/pkg/authn/licence"
"github.com/formancehq/go-libs/v5/pkg/cloud/aws/iam"
"github.com/formancehq/go-libs/v5/pkg/fx/servicefx"
"github.com/formancehq/go-libs/v5/pkg/fx/transportfx"
"github.com/formancehq/go-libs/v5/pkg/messaging/publish"
otlpmetrics "github.com/formancehq/go-libs/v5/pkg/observe/metrics"
"github.com/formancehq/go-libs/v5/pkg/service"
"github.com/formancehq/go-libs/v5/pkg/service/health"
bunconnect "github.com/formancehq/go-libs/v5/pkg/storage/bun/connect"
"github.com/formancehq/go-libs/v5/pkg/transport/httpserver"
"github.com/formancehq/go-libs/v5/pkg/workflow/temporal"
"github.com/formancehq/orchestration/internal/api"
v1 "github.com/formancehq/orchestration/internal/api/v1"
v2 "github.com/formancehq/orchestration/internal/api/v2"
Expand All @@ -25,8 +27,8 @@ import (

func healthCheckModule() fx.Option {
return fx.Options(
health.Module(),
health.ProvideHealthCheck(func() health.NamedCheck {
servicefx.HealthModule(),
servicefx.ProvideHealthCheck(func() health.NamedCheck {
return health.NewNamedCheck("default", health.CheckFn(func(ctx context.Context) error {
return nil
}))
Expand Down Expand Up @@ -64,12 +66,16 @@ func newServeCommand() *cobra.Command {
}),
api.NewModule(service.IsDebug(cmd)),
fx.Invoke(func(lc fx.Lifecycle, router *chi.Mux) {
lc.Append(httpserver.NewHook(router, httpserver.WithAddress(listen)))
lc.Append(transportfx.FXHook(httpserver.NewHook(router, httpserver.WithAddress(listen))))
}),
}
worker, _ := cmd.Flags().GetBool(workerFlag)
if worker {
options = append(options, workerOptions(cmd))
workerOptions, err := workerOptions(cmd)
if err != nil {
return err
}
options = append(options, workerOptions)
}

return service.New(cmd.OutOrStdout(), options...).Run(cmd)
Expand Down
49 changes: 35 additions & 14 deletions cmd/worker.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
package cmd

import (
"fmt"
"math"
"net/http"
"strconv"

sdk "github.com/formancehq/formance-sdk-go/v3"
"github.com/formancehq/go-libs/v3/aws/iam"
"github.com/formancehq/go-libs/v3/bun/bunconnect"
"github.com/formancehq/go-libs/v3/licence"
"github.com/formancehq/go-libs/v3/otlp/otlpmetrics"
"github.com/formancehq/go-libs/v3/publish"
"github.com/formancehq/go-libs/v3/service"
"github.com/formancehq/go-libs/v3/temporal"
"github.com/formancehq/go-libs/v5/pkg/authn/licence"
"github.com/formancehq/go-libs/v5/pkg/cloud/aws/iam"
"github.com/formancehq/go-libs/v5/pkg/messaging/publish"
otlpmetrics "github.com/formancehq/go-libs/v5/pkg/observe/metrics"
"github.com/formancehq/go-libs/v5/pkg/service"
bunconnect "github.com/formancehq/go-libs/v5/pkg/storage/bun/connect"
"github.com/formancehq/go-libs/v5/pkg/workflow/temporal"
"github.com/formancehq/orchestration/internal/temporalworker"
"github.com/formancehq/orchestration/internal/triggers"
"github.com/spf13/cobra"
Expand All @@ -22,26 +25,40 @@ func stackClientModule(cmd *cobra.Command) fx.Option {
stackURL, _ := cmd.Flags().GetString(stackURLFlag)

return fx.Options(
fx.Provide(func(httpClient *http.Client) *sdk.Formance {
fx.Provide(fx.Annotate(func(httpClient *http.Client) *sdk.Formance {
return sdk.New(
sdk.WithClient(httpClient),
sdk.WithServerURL(stackURL),
)
}),
}, fx.ParamTags(`name:"stack"`))),
)
}

func workerOptions(cmd *cobra.Command) fx.Option {
func workerOptions(cmd *cobra.Command) (fx.Option, error) {

stack, _ := cmd.Flags().GetString(stackFlag)
temporalTaskQueue, _ := cmd.Flags().GetString(temporal.TemporalTaskQueueFlag)
temporalMaxParallelActivities, _ := cmd.Flags().GetInt(temporal.TemporalMaxParallelActivitiesFlag)
// The flag is registered as a float64 in go-libs; reading it with GetInt
// silently fails and yields 0, so the configured limit was never applied.
temporalMaxParallelActivities, err := cmd.Flags().GetFloat64(temporal.TemporalMaxParallelActivitiesFlag)
if err != nil {
return nil, err
}
maxIntExclusive := math.Exp2(float64(strconv.IntSize - 1))
if temporalMaxParallelActivities <= 0 ||
math.Trunc(temporalMaxParallelActivities) != temporalMaxParallelActivities ||
temporalMaxParallelActivities >= maxIntExclusive {
return nil, fmt.Errorf("%s must be a positive whole number", temporal.TemporalMaxParallelActivitiesFlag)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
topics, _ := cmd.Flags().GetStringSlice(topicsFlag)

return fx.Options(
stackClientModule(cmd),
temporalworker.NewWorkerModule(temporalTaskQueue, worker.Options{
TaskQueueActivitiesPerSecond: float64(temporalMaxParallelActivities),
// "max parallel activities" caps concurrency, which maps to
// MaxConcurrentActivityExecutionSize, not the queue-wide rate limit
// TaskQueueActivitiesPerSecond it was previously wired to.
MaxConcurrentActivityExecutionSize: int(temporalMaxParallelActivities),
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}),
triggers.NewListenerModule(
stack,
Expand All @@ -50,7 +67,7 @@ func workerOptions(cmd *cobra.Command) fx.Option {
true,
topics,
),
)
), nil
}

func newWorkerCommand() *cobra.Command {
Expand All @@ -61,8 +78,12 @@ func newWorkerCommand() *cobra.Command {
if err != nil {
return err
}
workerOptions, err := workerOptions(cmd)
if err != nil {
return err
}

return service.New(cmd.OutOrStdout(), commonOptions, workerOptions(cmd)).Run(cmd)
return service.New(cmd.OutOrStdout(), commonOptions, workerOptions).Run(cmd)
},
}
ret.Flags().String(stackURLFlag, "", "Stack url")
Expand Down
33 changes: 33 additions & 0 deletions cmd/worker_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package cmd

import (
"testing"

"github.com/formancehq/go-libs/v5/pkg/workflow/temporal"
"github.com/stretchr/testify/require"
)

func TestWorkerOptionsValidatesMaxParallelActivities(t *testing.T) {
for _, testCase := range []struct {
name string
value string
wantErr bool
}{
{name: "positive integer", value: "10"},
{name: "zero", value: "0", wantErr: true},
{name: "negative", value: "-1", wantErr: true},
{name: "fractional", value: "0.5", wantErr: true},
{name: "int overflow", value: "9223372036854775808", wantErr: true},
} {
t.Run(testCase.name, func(t *testing.T) {
cmd := newWorkerCommand()
require.NoError(t, cmd.Flags().Set(temporal.TemporalMaxParallelActivitiesFlag, testCase.value))
_, err := workerOptions(cmd)
if testCase.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
})
}
}
Loading
Loading