Repository: bacalhau-project/bacalhau (language: python)

Issue:
Node evicition policy cannot be configured in requester node store
The TTL is hard-coded to 10 Mins https://github.com/bacalhau-project/bacalhau/blob/main/pkg/node/node.go#L184
This may be a sane default for some use cases, but probably not all. We should allow this value to be configured by operators.
122. Update scale testing framework
With new tests for the cases in this phase.


Discussion / hints:

Fix patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index c2cd5f2672..31e7989b7c 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -144,13 +144,6 @@ jobs:
     - install_go:
         executor: << parameters.executor >>
 
-    - run:
-        name: Init tools
-        command: |
-          make init
-          go version
-          which go
-
     - run:
         name: "Setup BACALHAU_ENVIRONMENT environment variable"
         command: echo 'export BACALHAU_ENVIRONMENT=test' >> "$BASH_ENV"
@@ -384,7 +377,7 @@ jobs:
           TAG="<< pipeline.git.tag >>"
           echo "TAG = ${TAG}"
           find dist/
-          gh release upload $TAG dist/*
+          gh release upload $TAG dist/bacalhau_$TAG_*
 
   release_python:
     executor: linux-amd64
@@ -641,10 +634,15 @@ workflows:
     jobs:
     - build_swagger:
         name: build-swagger-spec
+        filters: &build_on_branch_and_tag_push
+          tags:
+            # only run on tags that look like release tags e.g. v0.1.2 or
+            # v0.1.3alpha19 (actually v0.1.3anything...)
+            only: /^v([0-9]+).([0-9]+).([0-9]+).*$/
     - build_python_packages:
         name: build-python-packages
-        requires:
-        - build-swagger-spec
+        requires: [build-swagger-spec]
+        filters: *build_on_branch_and_tag_push
     - release_python:
         name: release-python-packages
         requires:
@@ -652,21 +650,21 @@ workflows:
         - build-python-packages
         # This job will only run on tags (specifically starting with 'v.') and
         # will not run on branches
-        filters: &filters_release_only
+        filters: &build_on_release_only
           branches:
             ignore: /.*/ # don't run on any branches - only tags
-          tags:
-            # only run on tags that look like release tags e.g. v0.1.2 or
-            # v0.1.3alpha19 (actually v0.1.3anything...)
-            only: /^v([0-9]+).([0-9]+).([0-9]+).*$/
+          <<: *build_on_branch_and_tag_push
 
   build:
     jobs:
-    - build_webui
+    - build_webui:
+        filters: *build_on_branch_and_tag_push
     - build:
         requires: [build_webui]
+        filters: *build_on_branch_and_tag_push
     - docker:
         requires: [build]
+        filters: *build_on_branch_and_tag_push
     - release:
         requires: [build]
-        filters: *filters_release_only
+        filters: *build_on_release_only
diff --git a/.gitprecommit/golangci-lint.sh b/.gitprecommit/golangci-lint.sh
index f3df67ea56..ef539a6d9d 100755
--- a/.gitprecommit/golangci-lint.sh
+++ b/.gitprecommit/golangci-lint.sh
@@ -1,14 +1,3 @@
 #!/bin/bash
-if git rev-parse --verify HEAD >/dev/null 2>&1
-then
-    against=HEAD
-else
-    # Initial commit: diff against an empty tree object
-    EMPTY_TREE=$(git hash-object -t tree /dev/null)
-    against=${EMPTY_TREE}
-fi
 
-FILES=$(git diff --cached --name-only "${against}")
-if [[ -n "${FILES}" ]]; then
-    golangci-lint run --allow-parallel-runners --fix --new
-fi
\ No newline at end of file
+golangci-lint run --allow-parallel-runners --fix
\ No newline at end of file
diff --git a/Makefile b/Makefile
index 73c7ce3253..1d854def10 100644
--- a/Makefile
+++ b/Makefile
@@ -81,22 +81,6 @@ ifeq ($(PRECOMMIT_HOOKS_INSTALLED),)
 $(warning "Pre-commit is not installed in .git/hooks/pre-commit. Please run 'make install-pre-commit' to install it.")
 endif
 
-################################################################################
-# Target: swagger-docs
-################################################################################
-.PHONY: swagger-docs
-swagger-docs:
-	@echo "Building swagger docs..."
-	swag fmt -g "pkg/publicapi/server.go" && \
-	swag init \
-		--outputTypes "go,json" \
-		--parseDependency \
-		--parseInternal \
-		--parseDepth 1 \
-		-g "pkg/publicapi/server.go" \
-		--overridesFile .swaggo
-	@echo "Swagger docs built."
-
 ################################################################################
 # Target: build-python-apiclient
 ################################################################################
@@ -269,6 +253,24 @@ dist/${PACKAGE}.tar.gz: ${BINARY_PATH} | dist/
 dist/${PACKAGE}.tar.gz.signature.sha256: dist/${PACKAGE}.tar.gz | dist/
 	openssl dgst -sha256 -sign $(PRIVATE_KEY_FILE) -passin pass:"$(PRIVATE_KEY_PASSPHRASE)" $^ | openssl base64 -out $@
 
+################################################################################
+# Target: swagger-docs
+################################################################################
+.PHONY: swagger-docs
+swagger-docs: docs/swagger.json
+
+docs/swagger.json: ${PKG_FILES} .swaggo
+	@echo "Building swagger docs..."
+	swag init \
+		--outputTypes "json" \
+		--parseDependency \
+		--parseInternal \
+		--parseDepth 1 \
+		-g "pkg/publicapi/server.go" \
+		--overridesFile .swaggo && \
+	echo >> $@
+#   ^ add newline to appease linter
+	@echo "Swagger docs built."
 
 ################################################################################
 # Target: images
@@ -420,7 +422,7 @@ COVER_FILE := coverage/${PACKAGE}_$(subst ${COMMA},_,${TEST_BUILD_TAGS}).coverag
 .PHONY: test-and-report
 test-and-report: unittests.xml ${COVER_FILE}
 
-${COVER_FILE} unittests.xml ${TEST_OUTPUT_FILE_PREFIX}_unit.json: ${BINARY_PATH} $(dir ${COVER_FILE})
+${COVER_FILE} unittests.xml ${TEST_OUTPUT_FILE_PREFIX}_unit.json &: ${CMD_FILES} ${PKG_FILES} $(dir ${COVER_FILE})
 	gotestsum \
 		--jsonfile ${TEST_OUTPUT_FILE_PREFIX}_unit.json \
 		--junitfile unittests.xml \
diff --git a/apps/job-info-consumer/consumer/go.mod b/apps/job-info-consumer/consumer/go.mod
index ab7a72df07..4944fd830e 100644
--- a/apps/job-info-consumer/consumer/go.mod
+++ b/apps/job-info-consumer/consumer/go.mod
@@ -169,7 +169,7 @@ require (
 	github.com/klauspost/compress v1.16.5 // indirect
 	github.com/klauspost/cpuid/v2 v2.2.5 // indirect
 	github.com/koron/go-ssdp v0.0.4 // indirect
-	github.com/labstack/echo/v4 v4.11.1 // indirect
+	github.com/labstack/echo/v4 v4.11.3 // indirect
 	github.com/labstack/gommon v0.4.0 // indirect
 	github.com/leodido/go-urn v1.2.4 // indirect
 	github.com/libp2p/go-buffer-pool v0.1.0 // indirect
@@ -281,7 +281,7 @@ require (
 	go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.40.0 // indirect
 	go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.40.0 // indirect
 	go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.40.0 // indirect
-	go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0 // indirect
+	go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect
 	go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.18.0 // indirect
 	go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.18.0 // indirect
 	go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.14.0 // indirect
@@ -299,7 +299,7 @@ require (
 	go.uber.org/multierr v1.11.0 // indirect
 	go.uber.org/zap v1.26.0 // indirect
 	go4.org v0.0.0-20230225012048-214862532bf5 // indirect
-	golang.org/x/crypto v0.16.0 // indirect
+	golang.org/x/crypto v0.17.0 // indirect
 	golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect
 	golang.org/x/mod v0.13.0 // indirect
 	golang.org/x/net v0.19.0 // indirect
@@ -307,7 +307,7 @@ require (
 	golang.org/x/sync v0.5.0 // indirect
 	golang.org/x/sys v0.15.0 // indirect
 	golang.org/x/text v0.14.0 // indirect
-	golang.org/x/time v0.3.0 // indirect
+	golang.org/x/time v0.5.0 // indirect
 	golang.org/x/tools v0.14.0 // indirect
 	golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
 	gonum.org/v1/gonum v0.13.0 // indirect
diff --git a/apps/job-info-consumer/consumer/go.sum b/apps/job-info-consumer/consumer/go.sum
index b03e66802f..1d030445bf 100644
--- a/apps/job-info-consumer/consumer/go.sum
+++ b/apps/job-info-consumer/consumer/go.sum
@@ -640,8 +640,7 @@ github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/labstack/echo/v4 v4.11.1 h1:dEpLU2FLg4UVmvCGPuk/APjlH6GDpbEPti61srUUUs4=
-github.com/labstack/echo/v4 v4.11.1/go.mod h1:YuYRTSM3CHs2ybfrL8Px48bO6BAnYIN4l8wSTMP6BDQ=
+github.com/labstack/echo/v4 v4.11.3 h1:Upyu3olaqSHkCjs1EJJwQ3WId8b8b1hxbogyommKktM=
 github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
 github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
 github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
@@ -1108,7 +1107,7 @@ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.40.0 h1:q3F
 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.40.0/go.mod h1:rmx4n0uSIAkKBeQYkygcv9dENAlL2/tv3OSq68h1JAo=
 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.40.0 h1:SZaSbubADNhH2Gxm+1GaZ/cFsGiYefZoodMMX79AOd4=
 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.40.0/go.mod h1:N65FzQDfQH7NY7umgb0U+7ypGKVYKwwE24L6KXT4OA8=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0 h1:IAtl+7gua134xcV3NieDhJHjjOVeJhXAnYf/0hswjUY=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw=
 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.18.0 h1:yE32ay7mJG2leczfREEhoW3VfSZIvHaB+gvVo1o8DQ8=
 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.18.0 h1:6pu8ttx76BxHf+xz/H77AUZkPF3cwWzXqAUsXhVKI18=
 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.14.0 h1:sEL90JjOO/4yhquXl5zTAkLLsZ5+MycAgX99SDsxGc8=
@@ -1175,7 +1174,7 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0
 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
 golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
-golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
+golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -1412,8 +1411,7 @@ golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxb
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
-golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
diff --git a/cmd/cli/config/auto.go b/cmd/cli/config/auto.go
index 02eb4885bd..c38b36c5e0 100644
--- a/cmd/cli/config/auto.go
+++ b/cmd/cli/config/auto.go
@@ -21,6 +21,8 @@ const (
 	defaultJobPercentage        = 75
 	defaultDefaultJobPercentage = 75
 	defaultQueuePercentage      = defaultJobPercentage * 2
+
+	oneHundredPercent = 100
 )
 
 type autoSettings struct {
@@ -35,6 +37,7 @@ func autoFlags(settings *autoSettings) *pflag.FlagSet {
 	flags.IntVar(&settings.TotalPercentage,
 		"total-percentage",
 		defaultTotalPercentage,
+		//nolint:goconst
 		"Percentage expressed as a number from 1 to 100 representing "+
 			"total amount of resource the system can be using at one time in aggregate for all jobs "+
 			"(values over 100 will be rejected)")
@@ -62,13 +65,13 @@ func (a *autoSettings) validate() error {
 	if a.TotalPercentage == 0 {
 		return fmt.Errorf("total-percentage must be greater than 0")
 	}
-	if a.TotalPercentage > 100 {
+	if a.TotalPercentage > oneHundredPercent {
 		return fmt.Errorf("total-percentage cannot exceed 100")
 	}
 	if a.JobPercentage == 0 {
 		return fmt.Errorf("job-percentage must be greater than 0")
 	}
-	if a.JobPercentage > 100 {
+	if a.JobPercentage > oneHundredPercent {
 		return fmt.Errorf("job-percentage cannot exceed 100")
 	}
 	if a.JobPercentage > a.TotalPercentage {
@@ -77,7 +80,7 @@ func (a *autoSettings) validate() error {
 	if a.DefaultPercentage == 0 {
 		return fmt.Errorf("default-job-percentage must be greater than 0")
 	}
-	if a.DefaultPercentage > 100 {
+	if a.DefaultPercentage > oneHundredPercent {
 		return fmt.Errorf("default-job-percentage cannot exceed 100")
 	}
 	if a.DefaultPercentage > a.TotalPercentage {
diff --git a/cmd/cli/config/set.go b/cmd/cli/config/set.go
index 2d1042b96e..222ffd756c 100644
--- a/cmd/cli/config/set.go
+++ b/cmd/cli/config/set.go
@@ -33,37 +33,35 @@ func newSetCmd() *cobra.Command {
 	return showCmd
 }
 
-func setConfig(key string, values ...string) error {
-	// remove all spaces and make lowercase
-	key = sanitizeKey(key)
+func currentValue(key string) (interface{}, error) {
 	// get the default viper schema
 	viperSchema := NewViperWithDefaultConfig(config.ForEnvironment())
 	// get a list of all valid configuration keys, same list as returned by `config list`
 	liveKeys := viperSchema.AllKeys()
 	if !slices.Contains(liveKeys, key) {
-		return fmt.Errorf("invalid configuration key %q: not found", key)
+		return nil, fmt.Errorf("invalid configuration key %q: not found", key)
 	}
 
-	// there may me a config file present, we'll write to that if it exists.
-	configFile := viper.ConfigFileUsed()
+	//calling `Get` on this instance will return a default value from the config structure that we can type assert on.
+	return viperSchema.Get(key), nil
+}
 
-	// we create a new viper instance that we'll use to set values on and update the config.
+func getWriter(configFile string) (*viper.Viper, error) {
 	viperWriter := viper.New()
 	viperWriter.SetTypeByDefaultValue(true)
-	if configFile == "" {
-		// if there isn't a config file, we'll assume we add it to the current repo.
-		configFile = filepath.Join(viper.GetString("repo"), "config.yaml")
-	}
 	viperWriter.SetConfigFile(configFile)
 	// the instance has read in a copy of the config file
 	if err := viperWriter.ReadInConfig(); err != nil {
 		if !os.IsNotExist(err) {
-			return err
+			return nil, err
 		}
 	}
+	return viperWriter, nil
+}
 
-	//calling `Get` on this instance will return a default value from the config structure that we can type assert on.
-	curValue := viperSchema.Get(key)
+func setConfig(key string, values ...string) error {
+	// remove all spaces and make lowercase
+	key = sanitizeKey(key)
 
 	// we need special handling for the nodetype since its just a string, but carries implicit requirements in the config.
 	if strings.EqualFold(types.NodeType, key) {
@@ -72,8 +70,22 @@ func setConfig(key string, values ...string) error {
 		}
 	}
 
-	type parserFunc func(string) (any, error)
-	var parser parserFunc
+	curValue, err := currentValue(key)
+	if err != nil {
+		return err
+	}
+
+	// there may me a config file present, we'll write to that if it exists.
+	configFile := viper.ConfigFileUsed()
+	if configFile == "" {
+		// if there isn't a config file, we'll assume we add it to the current repo.
+		configFile = filepath.Join(viper.GetString("repo"), "config.yaml")
+	}
+
+	viperWriter, err := getWriter(configFile)
+	if err != nil {
+		return err
+	}
 
 	switch curValue.(type) {
 	case []string:
@@ -84,6 +96,32 @@ func setConfig(key string, values ...string) error {
 			return err
 		}
 		viperWriter.Set(key, sts)
+	}
+
+	parser, err := getParser(curValue, key)
+	if parser == nil || err != nil {
+		return viperWriter.WriteConfig()
+	}
+
+	value, err := singleValueOrError(values...)
+	if err != nil {
+		return fmt.Errorf("setting %q: %w", key, err)
+	}
+
+	configValue, err := parser(value)
+	if err != nil {
+		return fmt.Errorf("setting %q: %w", key, err)
+	}
+	viperWriter.Set(key, configValue)
+	return viperWriter.WriteConfig()
+}
+
+type parserFunc func(string) (any, error)
+
+func getParser(curValue interface{}, key string) (parserFunc, error) {
+	var parser parserFunc
+
+	switch curValue.(type) {
 	case string:
 		parser = func(s string) (any, error) { return s, nil } // identity by default
 	case bool:
@@ -93,7 +131,7 @@ func setConfig(key string, values ...string) error {
 	case uint, uint8, uint16, uint32, uint64:
 		parser = func(s string) (any, error) { return strconv.ParseUint(s, 10, 64) }
 	case float32, float64:
-		parser = func(s string) (any, error) { return strconv.ParseFloat(s, 10) }
+		parser = func(s string) (any, error) { return strconv.ParseFloat(s, 64) }
 	case types.Duration, time.Duration:
 		parser = func(s string) (any, error) { return time.ParseDuration(s) }
 	case model.JobSelectionDataLocality:
@@ -103,21 +141,8 @@ func setConfig(key string, values ...string) error {
 	case types.StorageType:
 		parser = func(s string) (any, error) { return types.ParseStorageType(s) }
 	default:
-		return fmt.Errorf("unsupported type %T for key: %q", curValue, key)
-	}
-
-	if parser != nil {
-		value, err := singleValueOrError(values...)
-		if err != nil {
-			return fmt.Errorf("setting %q: %w", key, err)
-		}
-
-		configValue, err := parser(value)
-		if err != nil {
-			return fmt.Errorf("setting %q: %w", key, err)
-		}
-		viperWriter.Set(key, configValue)
+		return nil, fmt.Errorf("unsupported type %T for key: %q", curValue, key)
 	}
 
-	return viperWriter.WriteConfig()
+	return parser, nil
 }
diff --git a/cmd/cli/create/create.go b/cmd/cli/create/create.go
index 341c725149..256a26c571 100644
--- a/cmd/cli/create/create.go
+++ b/cmd/cli/create/create.go
@@ -201,6 +201,7 @@ func create(cmd *cobra.Command, cmdArgs []string, OC *CreateOptions) error { //n
 
 	if !model.IsValidPublisher(j.Spec.PublisherSpec.Type) {
 		j.Spec.PublisherSpec = model.PublisherSpec{
+			//nolint:staticcheck // TODO: remove this when we have a proper publisher
 			Type: j.Spec.Publisher,
 		}
 	}
diff --git a/cmd/cli/devstack/devstack.go b/cmd/cli/devstack/devstack.go
index 08ab61205b..282221a3e6 100644
--- a/cmd/cli/devstack/devstack.go
+++ b/cmd/cli/devstack/devstack.go
@@ -71,6 +71,7 @@ func NewCmd() *cobra.Command {
 		"disable-features": configflags.DisabledFeatureFlags,
 		"capacity":         configflags.CapacityFlags,
 		"job-timeouts":     configflags.ComputeTimeoutFlags,
+		"translations":     configflags.JobTranslationFlags,
 	}
 
 	devstackCmd := &cobra.Command{
diff --git a/cmd/cli/exec/exec.go b/cmd/cli/exec/exec.go
index b03e89dea7..7fbaeaa071 100644
--- a/cmd/cli/exec/exec.go
+++ b/cmd/cli/exec/exec.go
@@ -6,8 +6,10 @@ import (
 	"fmt"
 	"os"
 	"path/filepath"
+	"strings"
 
 	"github.com/spf13/cobra"
+	shellescape "gopkg.in/alessio/shellescape.v1"
 	"k8s.io/kubectl/pkg/util/i18n"
 
 	"github.com/bacalhau-project/bacalhau/cmd/util"
@@ -120,6 +122,14 @@ func PrepareJob(cmd *cobra.Command, cmdArgs []string, unknownArgs []string, opti
 	// don't have a template, then we don't know how to submit that job type.
 	jobType = cmdArgs[0]
 
+	for i := range cmdArgs {
+		// If any parameters were quoted, we should make sure we try and add
+		// them back in after they were stripped for us.
+		if strings.Contains(cmdArgs[i], " ") {
+			cmdArgs[i] = shellescape.Quote(cmdArgs[i])
+		}
+	}
+
 	tpl, err := NewTemplateMap(embeddedFiles, "templates")
 	if err != nil {
 		return nil, fmt.Errorf("failed to find supported job types, templates missing")
@@ -198,6 +208,13 @@ func addInlineContent(ctx context.Context, codeLocation string, job *models.Job)
 		return err
 	}
 
+	target := "/code"
+
+	finfo, _ := os.Stat(absPath)
+	if !finfo.IsDir() {
+		target = fmt.Sprintf("/code/%s", finfo.Name())
+	}
+
 	specConfig, err := inline.NewStorage().Upload(ctx, absPath)
 	if err != nil {
 		return fmt.Errorf("failed to attach code '%s' to job submission: %w", codeLocation, err)
@@ -206,7 +223,7 @@ func addInlineContent(ctx context.Context, codeLocation string, job *models.Job)
 	job.Tasks[0].InputSources = append(job.Tasks[0].InputSources, &models.InputSource{
 		Source: &specConfig,
 		Alias:  "code",
-		Target: "/code",
+		Target: target,
 	})
 
 	return nil
diff --git a/cmd/cli/serve/serve.go b/cmd/cli/serve/serve.go
index 78cbef8f2c..277fa5a529 100644
--- a/cmd/cli/serve/serve.go
+++ b/cmd/cli/serve/serve.go
@@ -8,6 +8,7 @@ import (
 	"runtime"
 	"sort"
 	"strings"
+	"time"
 
 	"github.com/multiformats/go-multiaddr"
 
@@ -54,8 +55,8 @@ var (
 		# Start a public bacalhau requester node
 		bacalhau serve --peer env --private-internal-ipfs=false
 
-		# Start a public bacalhau node with the WebUI
-		bacalhau serve --webui
+		# Start a public bacalhau node with the WebUI on port 3000 (default:80)
+		bacalhau serve --web-ui --web-ui-port=3000
 `))
 )
 
@@ -106,6 +107,8 @@ func NewCmd() *cobra.Command {
 		"compute-store":    configflags.ComputeStorageFlags,
 		"requester-store":  configflags.RequesterJobStorageFlags,
 		"web-ui":           configflags.WebUIFlags,
+		"node-info-store":  configflags.NodeInfoStoreFlags,
+		"translations":     configflags.JobTranslationFlags,
 	}
 
 	serveCmd := &cobra.Command{
@@ -179,11 +182,6 @@ func serve(cmd *cobra.Command) error {
 		return err
 	}
 
-	startWebUI, err := config.Get[bool](types.NodeWebUI)
-	if err != nil {
-		return err
-	}
-
 	libp2pCfg, err := config.GetLibp2pConfig()
 	if err != nil {
 		return err
@@ -229,6 +227,11 @@ func serve(cmd *cobra.Command) error {
 		return err
 	}
 
+	nodeInfoStoreTTL, err := config.Get[time.Duration](types.NodeNodeInfoStoreTTL)
+	if err != nil {
+		return err
+	}
+
 	allowedListLocalPaths := getAllowListedLocalPathsConfig()
 
 	// TODO (forrest): [ux] in the future we should make this configurable to users.
@@ -248,6 +251,7 @@ func serve(cmd *cobra.Command) error {
 		Labels:                getNodeLabels(autoLabel),
 		AllowListedLocalPaths: allowedListLocalPaths,
 		FsRepo:                fsRepo,
+		NodeInfoStoreTTL:      nodeInfoStoreTTL,
 	}
 
 	if isRequesterNode {
@@ -278,13 +282,26 @@ func serve(cmd *cobra.Command) error {
 		return fmt.Errorf("error starting node: %w", err)
 	}
 
+	startWebUI, err := config.Get[bool](types.NodeWebUIEnabled)
+	if err != nil {
+		return err
+	}
+
 	// Start up Dashboard
 	if startWebUI {
+		listenPort, err := config.Get[int](types.NodeWebUIPort)
+		if err != nil {
+			return err
+		}
+
 		apiURL := standardNode.APIServer.GetURI().JoinPath("api", "v1")
 		go func() {
 			// Specifically leave the host blank. The app will just use whatever
 			// host it is served on and replace the port and path.
-			err := webui.ListenAndServe(ctx, "", apiURL.Port(), apiURL.Path)
+			apiPort := apiURL.Port()
+			apiPath := apiURL.Path
+
+			err := webui.ListenAndServe(ctx, "", apiPort, apiPath, listenPort)
 			if err != nil {
 				cmd.PrintErrln(err)
 			}
@@ -440,7 +457,7 @@ func AutoOutputLabels() map[string]string {
 		// Print the GPU names
 		for i, gpu := range resources.GPUs {
 			// Model label e.g. GPU-0: Tesla-T1
-			key := fmt.Sprintf("GPU-%d", gpu.Index)
+			key := fmt.Sprintf("GPU-%d", i)
 			name := strings.Replace(gpu.Name, " ", "-", -1) // Replace spaces with dashes
 			m[key] = name
 
diff --git a/cmd/cli/serve/util.go b/cmd/cli/serve/util.go
index 720c89c3b8..d61fa8ac15 100644
--- a/cmd/cli/serve/util.go
+++ b/cmd/cli/serve/util.go
@@ -64,6 +64,7 @@ func GetRequesterConfig() (node.RequesterConfig, error) {
 	if err := config.ForKey(types.NodeRequester, &cfg); err != nil {
 		return node.RequesterConfig{}, err
 	}
+
 	return node.NewRequesterConfigWith(node.RequesterConfigParams{
 		JobDefaults: transformer.JobDefaults{
 			ExecutionTimeout: time.Duration(cfg.JobDefaults.ExecutionTimeout),
@@ -89,6 +90,7 @@ func GetRequesterConfig() (node.RequesterConfig, error) {
 		WorkerEvalDequeueMaxBackoff:    time.Duration(cfg.Worker.WorkerEvalDequeueMaxBackoff),
 		S3PreSignedURLExpiration:       time.Duration(cfg.StorageProvider.S3.PreSignedURLExpiration),
 		S3PreSignedURLDisabled:         cfg.StorageProvider.S3.PreSignedURLDisabled,
+		TranslationEnabled:             cfg.TranslationEnabled,
 	})
 }
 
diff --git a/cmd/util/flags/configflags/ipfs.go b/cmd/util/flags/configflags/ipfs.go
index 666364c89e..044490beb3 100644
--- a/cmd/util/flags/configflags/ipfs.go
+++ b/cmd/util/flags/configflags/ipfs.go
@@ -39,4 +39,28 @@ var IPFSFlags = []Definition{
 		Description:          "path local Ipfs node will persist data to",
 		EnvironmentVariables: []string{"BACALHAU_SERVE_IPFS_PATH"},
 	},
+	{
+		FlagName:     "ipfs-profile",
+		ConfigPath:   types.NodeIPFSProfile,
+		DefaultValue: Default.Node.IPFS.Profile,
+		Description:  "profile for internal IPFS node",
+	},
+	{
+		FlagName:     "ipfs-swarm-listen-addresses",
+		ConfigPath:   types.NodeIPFSSwarmListenAddresses,
+		DefaultValue: Default.Node.IPFS.SwarmListenAddresses,
+		Description:  "addresses the internal IPFS node will listen on for swarm connections",
+	},
+	{
+		FlagName:     "ipfs-gateway-listen-addresses",
+		ConfigPath:   types.NodeIPFSGatewayListenAddresses,
+		DefaultValue: Default.Node.IPFS.GatewayListenAddresses,
+		Description:  "addresses the internal IPFS node will listen on for gateway connections",
+	},
+	{
+		FlagName:     "ipfs-api-listen-addresses",
+		ConfigPath:   types.NodeIPFSAPIListenAddresses,
+		DefaultValue: Default.Node.IPFS.APIListenAddresses,
+		Description:  "addresses the internal IPFS node will listen on for API connections",
+	},
 }
diff --git a/cmd/util/flags/configflags/job_translation.go b/cmd/util/flags/configflags/job_translation.go
new file mode 100644
index 0000000000..0817ce4b45
--- /dev/null
+++ b/cmd/util/flags/configflags/job_translation.go
@@ -0,0 +1,12 @@
+package configflags
+
+import "github.com/bacalhau-project/bacalhau/pkg/config/types"
+
+var JobTranslationFlags = []Definition{
+	{
+		FlagName:     "requester-job-translation-enabled",
+		DefaultValue: Default.Node.Requester.TranslationEnabled,
+		ConfigPath:   types.NodeRequesterTranslationEnabled,
+		Description:  `Whether jobs should be translated at the requester node or not. Default: false`,
+	},
+}
diff --git a/cmd/util/flags/configflags/node_info_store.go b/cmd/util/flags/configflags/node_info_store.go
new file mode 100644
index 0000000000..3122ef99f0
--- /dev/null
+++ b/cmd/util/flags/configflags/node_info_store.go
@@ -0,0 +1,15 @@
+package configflags
+
+import (
+	"github.com/bacalhau-project/bacalhau/pkg/config/types"
+)
+
+var NodeInfoStoreFlags = []Definition{
+	{
+		FlagName:     "node-info-store-ttl",
+		ConfigPath:   types.NodeNodeInfoStoreTTL,
+		DefaultValue: Default.Node.NodeInfoStoreTTL,
+		Description: `Sets the duration for which node information is retained in the node info store after which it
+is automatically removed from the store.`,
+	},
+}
diff --git a/cmd/util/flags/configflags/web_ui.go b/cmd/util/flags/configflags/web_ui.go
index ea6fc2f144..fd5bcf9a25 100644
--- a/cmd/util/flags/configflags/web_ui.go
+++ b/cmd/util/flags/configflags/web_ui.go
@@ -5,8 +5,14 @@ import "github.com/bacalhau-project/bacalhau/pkg/config/types"
 var WebUIFlags = []Definition{
 	{
 		FlagName:     "web-ui",
-		ConfigPath:   types.NodeWebUI,
-		DefaultValue: false,
+		ConfigPath:   types.NodeWebUIEnabled,
+		DefaultValue: Default.Node.WebUI.Enabled,
 		Description:  `Whether to start the web UI alongside the bacalhau node.`,
 	},
+	{
+		FlagName:     "web-ui-port",
+		ConfigPath:   types.NodeWebUIPort,
+		DefaultValue: Default.Node.WebUI.Port,
+		Description:  `The port number to listen on for web-ui connections.`,
+	},
 }
diff --git a/docker/custom-job-images/Makefile b/docker/custom-job-images/Makefile
index 9c87a3cc5e..be0e80ed9b 100644
--- a/docker/custom-job-images/Makefile
+++ b/docker/custom-job-images/Makefile
@@ -20,6 +20,6 @@ duckdb-local:
 
 
 python-test:
-	docker run --rm -it  -v $(shell pwd)/python/test/single-file:/code  bacalhauproject/exec-python-3.11:0.1  python  /build/launcher.py -- python hello.py
-	docker run --rm -it  -v $(shell pwd)/python/test/multi-file-reqtxt:/code  bacalhauproject/exec-python-3.11:0.1  python  /build/launcher.py -- python main.py
-	docker run --rm -it  -v $(shell pwd)/python/test/multi-file-poetry:/code  bacalhauproject/exec-python-3.11:0.1  python  /build/launcher.py -- poetry run mfp
\ No newline at end of file
+	docker run --rm -it  -v $(shell pwd)/python/test/single-file:/code  bacalhauproject/exec-python-3.11:0.5  python  /build/launcher.py -- python hello.py
+	docker run --rm -it  -v $(shell pwd)/python/test/multi-file-reqtxt:/code  bacalhauproject/exec-python-3.11:0.5  python  /build/launcher.py -- python main.py
+	docker run --rm -it  -v $(shell pwd)/python/test/multi-file-poetry:/code  bacalhauproject/exec-python-3.11:0.5  python  /build/launcher.py -- poetry run mfp
\ No newline at end of file
diff --git a/docker/custom-job-images/python/Dockerfile b/docker/custom-job-images/python/Dockerfile
index 11a58a2443..2c8c8b45fe 100644
--- a/docker/custom-job-images/python/Dockerfile
+++ b/docker/custom-job-images/python/Dockerfile
@@ -27,10 +27,9 @@ RUN python -mpip install --upgrade pip
 RUN python -mpip install poetry 
 
 COPY base_requirements.txt /build
-COPY launcher.py /build
 RUN python -mpip install -r /build/base_requirements.txt
 
-
+COPY launcher.py /build
 CMD ["/build/launcher.py"]
 
 LABEL org.opencontainers.image.source https://github.com/bacalhau-project/bacalhau-images
diff --git a/docker/custom-job-images/python/Makefile b/docker/custom-job-images/python/Makefile
index a8adbca3ee..5a6859dc4f 100644
--- a/docker/custom-job-images/python/Makefile
+++ b/docker/custom-job-images/python/Makefile
@@ -1,13 +1,13 @@
 MACHINE = $(shell uname -m)
 USERNAME ?= bacalhauproject
-VERSION ?= 0.1
+VERSION ?= 0.5
 
 ifeq ($(MACHINE),x86_64)
     MACHINE := amd64
 endif
 
 local:
-	@echo - Building local python $(VERSION)
+	@echo - Building local python $(VERSION) - $(MACHINE)
 	docker buildx build \
 		--platform linux/$(MACHINE) \
 		-t $(USERNAME)/exec-python-3.11:$(VERSION) \
diff --git a/docker/custom-job-images/python/launcher.py b/docker/custom-job-images/python/launcher.py
index 34735297b5..6ae4c412da 100755
--- a/docker/custom-job-images/python/launcher.py
+++ b/docker/custom-job-images/python/launcher.py
@@ -15,27 +15,45 @@
 )
 
 CODE_DIR = "/code"  # The mounted code folder
-WORKING_DIR = "/app"  # Created by the shutil.copytree
 OUTPUT_DIR = "/outputs"  # The output folder
 
 
 def main():
-    # Unpack the contents of /code to the working directory which
-    # will create that working_directory, ignoring the files that
-    # match the globs in IGNORE
-    ignore_pattern = shutil.ignore_patterns(*IGNORE)
-    shutil.copytree(CODE_DIR, WORKING_DIR, ignore=ignore_pattern)
-    os.chdir(WORKING_DIR)
-
-    # Figure out how to install requirements
-    for f in (
-        single_file,
-        pyproject,
-        requirements_txt,
-        setup_py,
-    ):
-        if f():
-            break
+    working_dir = "/app"  # Created by the shutil.copytree
+
+    # it's possible we haven't been sent any code (and we're running via -c)
+    # so let's support not sending code.
+    if os.path.exists(CODE_DIR):
+        # Unpack the contents of /code to the working directory which
+        # will create that working_directory, ignoring the files that
+        # match the globs in IGNORE
+        ignore_pattern = shutil.ignore_patterns(*IGNORE)
+        shutil.copytree(CODE_DIR, working_dir, ignore=ignore_pattern)
+        os.chdir(working_dir)
+
+        # The inline attachments will have adding the last part of the
+        # path when adding a directory, and so WORKING_DIR won't contain
+        # the code, it'll contain that directory. In these cases we'll
+        # just change the WORKING_DIR.
+        wd_list = os.listdir(working_dir)
+        if len(wd_list) == 1:
+            pth = os.path.join(working_dir, wd_list[0])
+            if os.path.isdir(pth):
+                working_dir = pth
+
+        # Figure out how to install requirements
+        for f in (
+            single_file,
+            pyproject,
+            requirements_txt,
+            setup_py,
+        ):
+            if f(working_dir):
+                break
+    else:
+        # We will use the current directory as the working directory as
+        # we won't have created /app with the copy
+        working_dir = os.curdir
 
     # Run the program in that working directory
     past = False
@@ -47,7 +65,7 @@ def main():
             past = True
 
     cmd = " ".join(args)
-    proc = subprocess.run(cmd, capture_output=False, shell=True, cwd=WORKING_DIR)
+    proc = subprocess.run(cmd, capture_output=False, shell=True, cwd=working_dir)
 
 
 def to_requirements_log(stdoutBytes, stderrBytes):
@@ -60,16 +78,17 @@ def to_requirements_log(stdoutBytes, stderrBytes):
             f.write(stderrBytes.decode("utf-8"))
 
 
-def single_file():
+def single_file(working_dir):
     """
     If we only find a single file ready to be deployed, we'll read pip install instrcutions
     from the module doc (if it exists).
     """
     installed = 0
     doclines = []
-    files = glob("*.py", root_dir=WORKING_DIR)
+    files = glob("*.py", root_dir=working_dir)
+
     if len(files) == 1:
-        with open(os.path.join(WORKING_DIR, files[0])) as f:
+        with open(os.path.join(working_dir, files[0])) as f:
             mod = ast.parse(f.read())
             if not mod:
                 return False
@@ -84,7 +103,7 @@ def single_file():
         line = line.strip()
         if line.startswith("pip"):
             proc = subprocess.run(
-                f"python -m{line}", capture_output=True, shell=True, cwd=WORKING_DIR
+                f"python -m{line}", capture_output=True, shell=True, cwd=working_dir
             )
             to_requirements_log(proc.stdout, proc.stderr)
 
@@ -93,13 +112,13 @@ def single_file():
     return installed > 0
 
 
-def pyproject():
+def pyproject(working_dir):
     """
     If there is a pyproject.toml we'll check to see if it is a poetry app, and if
     so then we will get poetry to install dependencies.  If not then we will attempt
     to pip install them.
     """
-    pth = os.path.join(WORKING_DIR, "pyproject.toml")
+    pth = os.path.join(working_dir, "pyproject.toml")
     if not os.path.exists(pth):
         return False
 
@@ -113,13 +132,13 @@ def pyproject():
     if not is_poetry:
         cmd = f"python -mpip install {pth}"
 
-    proc = subprocess.run(cmd, capture_output=True, shell=True, cwd=WORKING_DIR)
+    proc = subprocess.run(cmd, capture_output=True, shell=True, cwd=working_dir)
     to_requirements_log(proc.stdout, proc.stderr)
 
     return True
 
 
-def requirements_txt():
+def requirements_txt(working_dir):
     """
     Look for a requirements file (or several) based on common names to load the
     dependencies from
@@ -127,13 +146,13 @@ def requirements_txt():
     installed = 0
     files = ("dev-requirements.txt", "requirements-dev.txt", "requirements.txt")
     for f in files:
-        pth = os.path.join(WORKING_DIR, f)
+        pth = os.path.join(working_dir, f)
         if os.path.exists(pth):
             proc = subprocess.run(
                 f"python -mpip install -r {f}",
                 capture_output=True,
                 shell=True,
-                cwd=WORKING_DIR,
+                cwd=working_dir,
             )
             to_requirements_log(proc.stdout, proc.stderr)
 
@@ -142,17 +161,17 @@ def requirements_txt():
     return installed > 0
 
 
-def setup_py():
+def setup_py(working_dir):
     """
     Look for a setup.py file as a last resort and try to install it locally
     """
-    pth = os.path.join(WORKING_DIR, "setup.py")
+    pth = os.path.join(working_dir, "setup.py")
     if os.path.exists(pth):
         proc = subprocess.run(
             f"python -m pip install -e .",
             capture_output=True,
             shell=True,
-            cwd=WORKING_DIR,
+            cwd=working_dir,
         )
         to_requirements_log(proc.stdout, proc.stderr)
         return True
diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md
new file mode 100644
index 0000000000..5a394c7c05
--- /dev/null
+++ b/docs/DEVELOPMENT.md
@@ -0,0 +1,30 @@
+**Instructions**
+- Set environment variables:
+```
+export PYTHONVER='3.11.7'
+export GOLANGCILINTVER='v1.51.2'
+```
+
+- Install asdf: `brew install asdf`
+- Install asdf python plug-in: `asdf plugin add python`
+- Install python: `asdf local python $PYTHONVER`
+- Install golangci-lint: `curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin $GOLANGCILINTVER`
+
+**Common Errors**
+- Using alternatives to `npm` - we have explored using `bun` but `prettier` did not work properly with it.
+
+- We use `pre-commit` to run pre-commit hooks. If you run into an error like the below, it is likely because you are using 3.12+ (which as of the end of 2023, pre-commit does not support).
+```
+[INFO] Installing environment for https://github.com/pre-commit/pre-commit-hooks.
+[INFO] Once installed this environment will be reused.
+[INFO] This may take a few minutes...
+An unexpected error has occurred: CalledProcessError: command: ('/home/daaronch/.cache/pre-commit/repowmf7smz7/py_env-python3.12/bin/python', '-mpip', 'install', '.')
+return code: 2
+stdout: (none)
+stderr:
+    ERROR: Exception:
+    Traceback (most recent call last):
+      File "/home/daaronch/.cache/pre-commit/repowmf7smz7/py_env-python3.12/lib/python3.12/site-packages/pip/_internal/cli/base_command.py", line 160, in exc_logging_wrapper
+        status = run_func(*args)
+                 ^^^^^^^^^^^^^^^
+```
diff --git a/go.mod b/go.mod
index 907b38c46a..a6013bae87 100644
--- a/go.mod
+++ b/go.mod
@@ -38,7 +38,7 @@ require (
 	github.com/ipld/go-ipld-prime v0.21.0
 	github.com/jedib0t/go-pretty/v6 v6.4.9
 	github.com/joho/godotenv v1.5.1
-	github.com/labstack/echo/v4 v4.11.1
+	github.com/labstack/echo/v4 v4.11.3
 	github.com/libp2p/go-libp2p v0.27.8
 	github.com/libp2p/go-libp2p-pubsub v0.9.3
 	github.com/mattn/go-isatty v0.0.19
@@ -66,7 +66,7 @@ require (
 	go.opentelemetry.io/otel v1.21.0
 	go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.40.0
 	go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.40.0
-	go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0
+	go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0
 	go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.18.0
 	go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.18.0
 	go.opentelemetry.io/otel/metric v1.21.0
@@ -77,8 +77,9 @@ require (
 	go.uber.org/mock v0.3.0
 	go.uber.org/multierr v1.11.0
 	go.uber.org/zap v1.26.0
-	golang.org/x/crypto v0.16.0
+	golang.org/x/crypto v0.17.0
 	golang.org/x/exp v0.0.0-20230321023759-10a507213a29
+	gopkg.in/alessio/shellescape.v1 v1.0.0-20170105083845-52074bc9df61
 	k8s.io/apimachinery v0.28.4
 	k8s.io/kubectl v0.28.4
 	sigs.k8s.io/yaml v1.4.0
@@ -87,6 +88,7 @@ require (
 require (
 	dario.cat/mergo v1.0.0 // indirect
 	github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect
+	github.com/alessio/shellescape v1.4.2 // indirect
 	github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.13 // indirect
 	github.com/aws/aws-sdk-go-v2/credentials v1.13.42 // indirect
 	github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.12 // indirect
@@ -340,7 +342,7 @@ require (
 	golang.org/x/sys v0.15.0 // indirect
 	golang.org/x/term v0.15.0 // indirect
 	golang.org/x/text v0.14.0 // indirect
-	golang.org/x/time v0.3.0
+	golang.org/x/time v0.5.0
 	golang.org/x/tools v0.14.0 // indirect
 	golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
diff --git a/go.sum b/go.sum
index 242cd60f31..f9e49748db 100644
--- a/go.sum
+++ b/go.sum
@@ -77,6 +77,8 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF
 github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
 github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc=
 github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
+github.com/alessio/shellescape v1.4.2 h1:MHPfaU+ddJ0/bYWpgIeUnQUqKrlJ1S7BfEYPM4uEoM0=
+github.com/alessio/shellescape v1.4.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
 github.com/alexbrainman/goissue34681 v0.0.0-20191006012335-3fc7a47baff5 h1:iW0a5ljuFxkLGPNem5Ui+KBjFJzKg4Fv2fnxe4dvzpM=
 github.com/alexbrainman/goissue34681 v0.0.0-20191006012335-3fc7a47baff5/go.mod h1:Y2QMoi1vgtOIfc+6DhrMOGkLoGzqSV2rKp4Sm+opsyA=
 github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
@@ -686,8 +688,8 @@ github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/labstack/echo/v4 v4.11.1 h1:dEpLU2FLg4UVmvCGPuk/APjlH6GDpbEPti61srUUUs4=
-github.com/labstack/echo/v4 v4.11.1/go.mod h1:YuYRTSM3CHs2ybfrL8Px48bO6BAnYIN4l8wSTMP6BDQ=
+github.com/labstack/echo/v4 v4.11.3 h1:Upyu3olaqSHkCjs1EJJwQ3WId8b8b1hxbogyommKktM=
+github.com/labstack/echo/v4 v4.11.3/go.mod h1:UcGuQ8V6ZNRmSweBIJkPvGfwCMIlFmiqrPqiEBfPYws=
 github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
 github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
 github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
@@ -1189,8 +1191,8 @@ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.40.0 h1:q3F
 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.40.0/go.mod h1:rmx4n0uSIAkKBeQYkygcv9dENAlL2/tv3OSq68h1JAo=
 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.40.0 h1:SZaSbubADNhH2Gxm+1GaZ/cFsGiYefZoodMMX79AOd4=
 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.40.0/go.mod h1:N65FzQDfQH7NY7umgb0U+7ypGKVYKwwE24L6KXT4OA8=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0 h1:IAtl+7gua134xcV3NieDhJHjjOVeJhXAnYf/0hswjUY=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0/go.mod h1:w+pXobnBzh95MNIkeIuAKcHe/Uu/CX2PKIvBP6ipKRA=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg=
 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.18.0 h1:yE32ay7mJG2leczfREEhoW3VfSZIvHaB+gvVo1o8DQ8=
 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.18.0/go.mod h1:G17FHPDLt74bCI7tJ4CMitEk4BXTYG4FW6XUpkPBXa4=
 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.18.0 h1:6pu8ttx76BxHf+xz/H77AUZkPF3cwWzXqAUsXhVKI18=
@@ -1262,8 +1264,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0
 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
 golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
-golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
-golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
+golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
+golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -1507,8 +1509,8 @@ golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxb
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
-golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
+golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -1695,6 +1697,8 @@ google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw
 google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
 google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+gopkg.in/alessio/shellescape.v1 v1.0.0-20170105083845-52074bc9df61 h1:8ajkpB4hXVftY5ko905id+dOnmorcS2CHNxxHLLDcFM=
+gopkg.in/alessio/shellescape.v1 v1.0.0-20170105083845-52074bc9df61/go.mod h1:IfMagxm39Ys4ybJrDb7W3Ob8RwxftP0Yy+or/NVz1O8=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/go.work.sum b/go.work.sum
index 7c338676c2..22d3b8f860 100644
--- a/go.work.sum
+++ b/go.work.sum
@@ -871,6 +871,7 @@ github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3
 github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw=
 github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
 github.com/Kubuxu/go-os-helper v0.0.1 h1:EJiD2VUQyh5A9hWJLmc6iWg6yIcJ7jpBcwC8GMGXfDk=
+github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y=
 github.com/MakeNowJust/heredoc/v2 v2.0.1 h1:rlCHh70XXXv7toz95ajQWOWQnN4WNLt0TdpZYIR/J6A=
 github.com/Microsoft/hcsshim v0.9.2 h1:wB06W5aYFfUB3IvootYAY2WnOmIdgPGfqSI6tufQNnY=
 github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3 h1:4FA+QBaydEHlwxg0lMN3rhwoDaQy6LKhVWR4qvq4BuA=
@@ -878,7 +879,9 @@ github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46 h1:lsxEuwrXEAo
 github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
 github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
 github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
+github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
 github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
+github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
 github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs=
 github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
 github.com/Shopify/sarama v1.30.0 h1:TOZL6r37xJBDEMLx4yjB77jxbZYXPaDow08TSK6vIL0=
@@ -909,6 +912,7 @@ github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHG
 github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
 github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
 github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg=
+github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 github.com/apache/arrow/go/arrow v0.0.0-20211013220434-5962184e7a30 h1:HGREIyk0QRPt70R69Gm1JFHDgoiyYpCyuGE8E9k/nf0=
 github.com/apache/arrow/go/v10 v10.0.1 h1:n9dERvixoC/1JjDmBcs9FPaEryoANa2sCgVFo6ez9cI=
 github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0=
@@ -1003,6 +1007,7 @@ github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054 h1:uH66TXeswKn5P
 github.com/checkpoint-restore/go-criu/v4 v4.1.0 h1:WW2B2uxx9KWF6bGlHqhm8Okiafwwx7Y2kcpn8lCpjgo=
 github.com/checkpoint-restore/go-criu/v5 v5.3.0 h1:wpFFOoomK3389ue2lAb0Boag6XPht5QYpipxmSNL4d8=
 github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
+github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
 github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
 github.com/chzyer/logex v1.2.0 h1:+eqR0HfOetur4tgnC8ftU5imRnhi4te+BadWS95c5AM=
 github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY=
@@ -1128,18 +1133,22 @@ github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96 h1:cenwrSVm+Z7QL
 github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ=
 github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
 github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/dvsekhvalnov/jose2go v1.5.0 h1:3j8ya4Z4kMCwT5nXIKFSV84YS+HdqSSO0VsTQxaLAeM=
 github.com/dvsekhvalnov/jose2go v1.5.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU=
 github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
 github.com/eapache/go-resiliency v1.2.0 h1:v7g92e/KSN71Rq7vSThKaWIq68fL4YHvWyiUKorFR1Q=
 github.com/eapache/go-resiliency v1.3.0 h1:RRL0nge+cWGlxXbUzJ7yMcq6w2XBEr19dCN6HECGaT0=
 github.com/eapache/go-resiliency v1.3.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho=
 github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw=
+github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
 github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
+github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
 github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712 h1:aaQcKT9WumO6JEJcRyTqFVq4XUZiUcKR2/GI31TOcz8=
 github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
 github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw=
 github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
 github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
+github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
 github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM=
 github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
 github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
@@ -1218,6 +1227,7 @@ github.com/go-fonts/liberation v0.3.0/go.mod h1:jdJ+cqF+F4SUL2V+qxBth8fvBpBDS7yl
 github.com/go-fonts/stix v0.1.0 h1:UlZlgrvvmT/58o573ot7NFw0vZasZ5I6bcIft/oMdgg=
 github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
 github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo=
+github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe6ZudgnXrq0barxBImvnnJoMEhXAzcbM0I=
 github.com/go-ini/ini v1.25.4 h1:Mujh4R/dH6YL8bxuISne3xX2+qcQ9p0IxKAP6ExWoUo=
@@ -1233,6 +1243,7 @@ github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7
 github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
 github.com/go-pdf/fpdf v0.6.0 h1:MlgtGIfsdMEEQJr2le6b/HNr1ZlQwxyWr77r2aj2U/8=
 github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
+github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
 github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
 github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
@@ -1491,6 +1502,7 @@ github.com/ipfs/go-ipfs-blockstore v0.1.4/go.mod h1:Jxm3XMVjh6R17WvxFEiyKBLUGr86
 github.com/ipfs/go-ipfs-blockstore v0.2.1/go.mod h1:jGesd8EtCM3/zPgx+qr0/feTXGUeRai6adgwC+Q+JvE=
 github.com/ipfs/go-ipfs-blockstore v1.1.2/go.mod h1:w51tNR9y5+QXB0wkNcHt4O2aSZjTdqaEWaQdSxEyUOY=
 github.com/ipfs/go-ipfs-blockstore v1.2.0/go.mod h1:eh8eTFLiINYNSNawfZOC7HOxNTxpB1PFuA5E1m/7exE=
+github.com/ipfs/go-ipfs-blocksutil v0.0.1/go.mod h1:Yq4M86uIOmxmGPUHv/uI7uKqZNtLb449gwKqXjIsnRk=
 github.com/ipfs/go-ipfs-chunker v0.0.1/go.mod h1:tWewYK0we3+rMbOh7pPFGDyypCtvGcBFymgY4rSDLAw=
 github.com/ipfs/go-ipfs-ds-help v0.1.1/go.mod h1:SbBafGJuGsPI/QL3j9Fc5YPLeAu+SzOkI0gFwAg+mOs=
 github.com/ipfs/go-ipfs-exchange-interface v0.1.0/go.mod h1:ych7WPlyHqFvCi/uQI48zLZuAWVP5iTQPXEfVaw5WEI=
@@ -1629,6 +1641,7 @@ github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJA
 github.com/jcmturner/gokrb5/v8 v8.4.2 h1:6ZIM6b/JJN0X8UM43ZOM6Z4SJzla+a/u7scXFJzodkA=
 github.com/jcmturner/gokrb5/v8 v8.4.3 h1:iTonLeSJOn7MVUtyMT+arAn5AKAPrkilzhGw8wE/Tq8=
 github.com/jcmturner/gokrb5/v8 v8.4.3/go.mod h1:dqRwJGXznQrzw6cWmyo6kH+E7jksEQG/CyVWsJEsJO0=
+github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
 github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
 github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1 h1:ujPKutqRlJtcfWk6toYVYagwra7HQHbXOaS171b4Tg8=
 github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
@@ -2116,6 +2129,7 @@ github.com/onsi/gomega v1.27.0/go.mod h1:i189pavgK95OSIipFBa74gC2V4qrQuvjuyGEr3G
 github.com/onsi/gomega v1.27.3/go.mod h1:5vG284IBtfDAmDyrK+eGyZmUgUlmi+Wngqo557cZ6Gw=
 github.com/onsi/gomega v1.27.4/go.mod h1:riYq/GJKh8hhoM01HN6Vmuy93AarCXCBGpvFDK3q3fQ=
 github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4=
+github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
 github.com/opencontainers/runc v1.1.0 h1:O9+X96OcDjkmmZyfaG996kV7yq8HsoU2h1XRRQcefG8=
@@ -2206,10 +2220,12 @@ github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6O
 github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s=
+github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4 h1:BN/Nyn2nWMoqGRA7G7paDNDqTXE30mXGqzzybrfo05w=
 github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=
 github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
 github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
+github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
 github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY=
 github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
 github.com/rs/zerolog v1.21.0/go.mod h1:ZPhntP/xmq1nnND05hhpAh2QMhSsA4UN3MGZ6O2J3hM=
@@ -2423,6 +2439,7 @@ go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.13.0/go.mod h1:rqbht/L
 go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.14.0/go.mod h1:UFG7EBMRdXyFstOwH028U0sVf+AvukSGhF0g8+dmNG8=
 go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0/go.mod h1:vLarbg68dH2Wa77g71zmKQqlQ8+8Rq3GRG31uc0WcWI=
 go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.17.0 h1:eU0ffpYuEY7eQ75K+nKr9CI5KcY8h+GPk/9DDlEO1NI=
+go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.17.0/go.mod h1:9P5RK5JS2sjKepuCkqFwPp3etwV/57E0eigLw18Mn1k=
 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.14.0/go.mod h1:HrbCVv40OOLTABmOn1ZWty6CHXkU8DK/Urc43tHug70=
 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0/go.mod h1:5w41DY6S9gZrbjuq6Y+753e96WfPha5IcsOSZTtullM=
 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.14.0/go.mod h1:+N7zNjIJv4K+DeX67XXET0P+eIciESgaFDBqh+ZJFS4=
@@ -2663,6 +2680,8 @@ k8s.io/cli-runtime v0.28.0 h1:Tcz1nnccXZDNIzoH6EwjCs+7ezkUGhorzCweEvlVOFg=
 k8s.io/cli-runtime v0.28.1 h1:7Njc4eD5kaO4tYdSYVJJEs54koYD/vT6gxOq8dEVf9g=
 k8s.io/cli-runtime v0.28.2 h1:64meB2fDj10/ThIMEJLO29a1oujSm0GQmKzh1RtA/uk=
 k8s.io/cli-runtime v0.28.2/go.mod h1:bTpGOvpdsPtDKoyfG4EG041WIyFZLV9qq4rPlkyYfDA=
+k8s.io/cli-runtime v0.28.4 h1:IW3aqSNFXiGDllJF4KVYM90YX4cXPGxuCxCVqCD8X+Q=
+k8s.io/cli-runtime v0.28.4/go.mod h1:MLGRB7LWTIYyYR3d/DOgtUC8ihsAPA3P8K8FDNIqJ0k=
 k8s.io/code-generator v0.19.7 h1:kM/68Y26Z/u//TFc1ggVVcg62te8A2yQh57jBfD0FWQ=
 k8s.io/component-base v0.26.1 h1:4ahudpeQXHZL5kko+iDHqLj/FSGAEUnSVO0EBbgDd+4=
 k8s.io/component-base v0.27.0 h1:g3/FkscH8Uqg9SiDCEfhfhTVwKiVo4T2+iBwUqiFkMg=
@@ -2670,12 +2689,16 @@ k8s.io/component-base v0.28.0 h1:HQKy1enJrOeJlTlN4a6dU09wtmXaUvThC0irImfqyxI=
 k8s.io/component-base v0.28.1 h1:LA4AujMlK2mr0tZbQDZkjWbdhTV5bRyEyAFe0TJxlWg=
 k8s.io/component-base v0.28.2 h1:Yc1yU+6AQSlpJZyvehm/NkJBII72rzlEsd6MkBQ+G0E=
 k8s.io/component-base v0.28.2/go.mod h1:4IuQPQviQCg3du4si8GpMrhAIegxpsgPngPRR/zWpzc=
+k8s.io/component-base v0.28.4 h1:c/iQLWPdUgI90O+T9TeECg8o7N3YJTiuz2sKxILYcYo=
+k8s.io/component-base v0.28.4/go.mod h1:m9hR0uvqXDybiGL2nf/3Lf0MerAfQXzkfWhUY58JUbU=
 k8s.io/component-helpers v0.26.1 h1:Y5h1OYUJTGyHZlSAsc7mcfNsWF08S/MlrQyF/vn93mU=
 k8s.io/component-helpers v0.27.0 h1:rymQGJc4s30hHeb5VGuPdht8gKIPecj+Bw2FOJSavE4=
 k8s.io/component-helpers v0.28.0 h1:ubHUiEF7H/DOx4471pHHsLlH3EGu8jlEvnld5PS4KdI=
 k8s.io/component-helpers v0.28.1 h1:ts/vykhyUmPLhUl/hdLdf+a4BWA0giQ3f25HAIhl+RI=
 k8s.io/component-helpers v0.28.2 h1:r/XJ265PMirW9EcGXr/F+2yWrLPo2I69KdvcY/h9HAo=
 k8s.io/component-helpers v0.28.2/go.mod h1:pF1R5YWQ+sgf0i6EbVm+MQCzkYuqutDUibdrkvAa6aI=
+k8s.io/component-helpers v0.28.4 h1:+X9VXT5+jUsRdC26JyMZ8Fjfln7mSjgumafocE509C4=
+k8s.io/component-helpers v0.28.4/go.mod h1:8LzMalOQ0K10tkBJWBWq8h0HTI9HDPx4WT3QvTFn9Ro=
 k8s.io/cri-api v0.23.1 h1:0DHL/hpTf4Fp+QkUXFefWcp1fhjXr9OlNdY9X99c+O8=
 k8s.io/gengo v0.0.0-20201113003025-83324d819ded h1:JApXBKYyB7l9xx+DK7/+mFjC7A9Bt5A93FPvFD0HIFE=
 k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c h1:GohjlNKauSai7gN4wsJkeZ3WAJx4Sh+oT/b5IYn5suA=
@@ -2689,6 +2712,8 @@ k8s.io/metrics v0.28.0 h1:rO+zfTT2A5GvCdRD44vFAQgdz8Sa6OMsNYkEGpBQz0k=
 k8s.io/metrics v0.28.1 h1:Q0AsAEZKlAzhqrvfoGyHjz2qAFlef0SqfGJ1YWJ+ITU=
 k8s.io/metrics v0.28.2 h1:Z/oMk5SmiT/Ji1SaWOPfW2l9W831BLO9/XxDq9iS3ak=
 k8s.io/metrics v0.28.2/go.mod h1:QTIIdjMrq+KodO+rmp6R9Pr1LZO8kTArNtkWoQXw0sw=
+k8s.io/metrics v0.28.4 h1:u36fom9+6c8jX2sk8z58H0hFaIUfrPWbXIxN7GT2blk=
+k8s.io/metrics v0.28.4/go.mod h1:bBqAJxH20c7wAsTQxDXOlVqxGMdce49d7WNr1WeaLac=
 lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA=
 lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
 lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
diff --git a/ops/aws/canary/lambda/go.mod b/ops/aws/canary/lambda/go.mod
index 6853561fe6..50017226e0 100644
--- a/ops/aws/canary/lambda/go.mod
+++ b/ops/aws/canary/lambda/go.mod
@@ -169,7 +169,7 @@ require (
 	github.com/klauspost/compress v1.16.5 // indirect
 	github.com/klauspost/cpuid/v2 v2.2.5 // indirect
 	github.com/koron/go-ssdp v0.0.4 // indirect
-	github.com/labstack/echo/v4 v4.11.1 // indirect
+	github.com/labstack/echo/v4 v4.11.3 // indirect
 	github.com/labstack/gommon v0.4.0 // indirect
 	github.com/leodido/go-urn v1.2.4 // indirect
 	github.com/libp2p/go-buffer-pool v0.1.0 // indirect
@@ -284,7 +284,7 @@ require (
 	go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.40.0 // indirect
 	go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.40.0 // indirect
 	go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.40.0 // indirect
-	go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0 // indirect
+	go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect
 	go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.18.0 // indirect
 	go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.18.0 // indirect
 	go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.14.0 // indirect
@@ -302,7 +302,7 @@ require (
 	go.uber.org/multierr v1.11.0 // indirect
 	go.uber.org/zap v1.26.0 // indirect
 	go4.org v0.0.0-20230225012048-214862532bf5 // indirect
-	golang.org/x/crypto v0.16.0 // indirect
+	golang.org/x/crypto v0.17.0 // indirect
 	golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect
 	golang.org/x/mod v0.13.0 // indirect
 	golang.org/x/net v0.19.0 // indirect
@@ -310,7 +310,7 @@ require (
 	golang.org/x/sync v0.5.0 // indirect
 	golang.org/x/sys v0.15.0 // indirect
 	golang.org/x/text v0.14.0 // indirect
-	golang.org/x/time v0.3.0 // indirect
+	golang.org/x/time v0.5.0 // indirect
 	golang.org/x/tools v0.14.0 // indirect
 	golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
 	gonum.org/v1/gonum v0.13.0 // indirect
diff --git a/ops/aws/canary/lambda/go.sum b/ops/aws/canary/lambda/go.sum
index 6aed408d9c..114aeab4b1 100644
--- a/ops/aws/canary/lambda/go.sum
+++ b/ops/aws/canary/lambda/go.sum
@@ -656,8 +656,7 @@ github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/labstack/echo/v4 v4.11.1 h1:dEpLU2FLg4UVmvCGPuk/APjlH6GDpbEPti61srUUUs4=
-github.com/labstack/echo/v4 v4.11.1/go.mod h1:YuYRTSM3CHs2ybfrL8Px48bO6BAnYIN4l8wSTMP6BDQ=
+github.com/labstack/echo/v4 v4.11.3 h1:Upyu3olaqSHkCjs1EJJwQ3WId8b8b1hxbogyommKktM=
 github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
 github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
 github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
@@ -1131,7 +1130,7 @@ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.40.0 h1:q3F
 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.40.0/go.mod h1:rmx4n0uSIAkKBeQYkygcv9dENAlL2/tv3OSq68h1JAo=
 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.40.0 h1:SZaSbubADNhH2Gxm+1GaZ/cFsGiYefZoodMMX79AOd4=
 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.40.0/go.mod h1:N65FzQDfQH7NY7umgb0U+7ypGKVYKwwE24L6KXT4OA8=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0 h1:IAtl+7gua134xcV3NieDhJHjjOVeJhXAnYf/0hswjUY=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw=
 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.18.0 h1:yE32ay7mJG2leczfREEhoW3VfSZIvHaB+gvVo1o8DQ8=
 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.18.0 h1:6pu8ttx76BxHf+xz/H77AUZkPF3cwWzXqAUsXhVKI18=
 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.14.0 h1:sEL90JjOO/4yhquXl5zTAkLLsZ5+MycAgX99SDsxGc8=
@@ -1198,7 +1197,7 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0
 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
 golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
-golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
+golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -1436,8 +1435,7 @@ golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxb
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
-golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
diff --git a/pkg/bidstrategy/mocks.go b/pkg/bidstrategy/mocks.go
index 8c963d8849..ddc5828a3a 100644
--- a/pkg/bidstrategy/mocks.go
+++ b/pkg/bidstrategy/mocks.go
@@ -1,6 +1,10 @@
 // Code generated by MockGen. DO NOT EDIT.
 // Source: type.go
-
+//
+// Generated by this command:
+//
+//	mockgen --source type.go --destination mocks.go --package bidstrategy
+//
 // Package bidstrategy is a generated GoMock package.
 package bidstrategy
 
@@ -45,7 +49,7 @@ func (m *MockBidStrategy) ShouldBid(ctx context.Context, request BidStrategyRequ
 }
 
 // ShouldBid indicates an expected call of ShouldBid.
-func (mr *MockBidStrategyMockRecorder) ShouldBid(ctx, request interface{}) *gomock.Call {
+func (mr *MockBidStrategyMockRecorder) ShouldBid(ctx, request any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ShouldBid", reflect.TypeOf((*MockBidStrategy)(nil).ShouldBid), ctx, request)
 }
@@ -60,7 +64,7 @@ func (m *MockBidStrategy) ShouldBidBasedOnUsage(ctx context.Context, request Bid
 }
 
 // ShouldBidBasedOnUsage indicates an expected call of ShouldBidBasedOnUsage.
-func (mr *MockBidStrategyMockRecorder) ShouldBidBasedOnUsage(ctx, request, usage interface{}) *gomock.Call {
+func (mr *MockBidStrategyMockRecorder) ShouldBidBasedOnUsage(ctx, request, usage any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ShouldBidBasedOnUsage", reflect.TypeOf((*MockBidStrategy)(nil).ShouldBidBasedOnUsage), ctx, request, usage)
 }
@@ -98,7 +102,7 @@ func (m *MockSemanticBidStrategy) ShouldBid(ctx context.Context, request BidStra
 }
 
 // ShouldBid indicates an expected call of ShouldBid.
-func (mr *MockSemanticBidStrategyMockRecorder) ShouldBid(ctx, request interface{}) *gomock.Call {
+func (mr *MockSemanticBidStrategyMockRecorder) ShouldBid(ctx, request any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ShouldBid", reflect.TypeOf((*MockSemanticBidStrategy)(nil).ShouldBid), ctx, request)
 }
@@ -136,7 +140,7 @@ func (m *MockResourceBidStrategy) ShouldBidBasedOnUsage(ctx context.Context, req
 }
 
 // ShouldBidBasedOnUsage indicates an expected call of ShouldBidBasedOnUsage.
-func (mr *MockResourceBidStrategyMockRecorder) ShouldBidBasedOnUsage(ctx, request, usage interface{}) *gomock.Call {
+func (mr *MockResourceBidStrategyMockRecorder) ShouldBidBasedOnUsage(ctx, request, usage any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ShouldBidBasedOnUsage", reflect.TypeOf((*MockResourceBidStrategy)(nil).ShouldBidBasedOnUsage), ctx, request, usage)
 }
diff --git a/pkg/compute/executor.go b/pkg/compute/executor.go
index 957dc6baca..37e2d16935 100644
--- a/pkg/compute/executor.go
+++ b/pkg/compute/executor.go
@@ -63,7 +63,10 @@ func NewBaseExecutor(params BaseExecutorParams) *BaseExecutor {
 	}
 }
 
-func prepareInputVolumes(ctx context.Context, strgprovider storage.StorageProvider, storageDirectory string, inputSources ...*models.InputSource) (
+func prepareInputVolumes(
+	ctx context.Context,
+	strgprovider storage.StorageProvider,
+	storageDirectory string, inputSources ...*models.InputSource) (
 	[]storage.PreparedStorage, func(context.Context) error, error) {
 	inputVolumes, err := storage.ParallelPrepareStorage(ctx, strgprovider, storageDirectory, inputSources...)
 	if err != nil {
@@ -74,7 +77,10 @@ func prepareInputVolumes(ctx context.Context, strgprovider storage.StorageProvid
 	}, nil
 }
 
-func prepareWasmVolumes(ctx context.Context, strgprovider storage.StorageProvider, storageDirectory string, wasmEngine wasmmodels.EngineSpec) (
+func prepareWasmVolumes(
+	ctx context.Context,
+	strgprovider storage.StorageProvider,
+	storageDirectory string, wasmEngine wasmmodels.EngineSpec) (
 	map[string][]storage.PreparedStorage, func(context.Context) error, error) {
 	importModuleVolumes, err := storage.ParallelPrepareStorage(ctx, strgprovider, storageDirectory, wasmEngine.ImportModules...)
 	if err != nil {
@@ -209,31 +215,31 @@ func (r *StartResult) Cleanup(ctx context.Context) error {
 	return nil
 }
 
-func (e *BaseExecutor) Start(ctx context.Context, execution *models.Execution) (result *StartResult) {
-	result = new(StartResult)
+func (e *BaseExecutor) Start(ctx context.Context, execution *models.Execution) *StartResult {
+	result := new(StartResult)
 	jobExecutor, err := e.executors.Get(ctx, execution.Job.Task().Engine.Type)
 	if err != nil {
 		result.Err = fmt.Errorf("getting executor %s: %w", execution.Job.Task().Engine, err)
-		return
+		return result
 	}
 
 	resultFolder, err := e.resultsPath.PrepareResultsDir(execution.ID)
 	if err != nil {
 		result.Err = fmt.Errorf("preparing results path: %w", err)
-		return
+		return result
 	}
 
 	executionStorage := filepath.Join(e.storageDirectory, execution.JobID, execution.ID)
 	if err := os.MkdirAll(executionStorage, StorageDirectoryPerms); err != nil {
 		result.Err = fmt.Errorf("preparing storage path: %w", err)
-		return
+		return result
 	}
 
 	args, cleanup, err := PrepareRunArguments(ctx, e.Storages, executionStorage, execution, resultFolder)
 	result.cleanup = cleanup
 	if err != nil {
 		result.Err = fmt.Errorf("preparing arguments: %w", err)
-		return
+		return result
 	}
 
 	if err := e.store.UpdateExecutionState(ctx, store.UpdateExecutionStateRequest{
@@ -245,14 +251,14 @@ func (e *BaseExecutor) Start(ctx context.Context, execution *models.Execution) (
 		NewState: store.ExecutionStateRunning,
 	}); err != nil {
 		result.Err = fmt.Errorf("updating execution state from expected: %s to: %s", store.ExecutionStateBidAccepted, store.ExecutionStateRunning)
-		return
+		return result
 	}
 
 	log.Ctx(ctx).Debug().Msg("starting execution")
 
 	if e.failureInjection.IsBadActor {
 		result.Err = fmt.Errorf("i am a baaad node. i failed execution %s", execution.ID)
-		return
+		return result
 	}
 
 	if err := jobExecutor.Start(ctx, args); err != nil {
diff --git a/pkg/compute/executor_buffer.go b/pkg/compute/executor_buffer.go
index c44113b4b3..c089df54bd 100644
--- a/pkg/compute/executor_buffer.go
+++ b/pkg/compute/executor_buffer.go
@@ -73,7 +73,9 @@ func NewExecutorBuffer(params ExecutorBufferParams) *ExecutorBuffer {
 }
 
 // Run enqueues the execution and tries to run it if there is enough capacity.
-func (s *ExecutorBuffer) Run(ctx context.Context, localExecutionState store.LocalExecutionState) (err error) {
+func (s *ExecutorBuffer) Run(ctx context.Context, localExecutionState store.LocalExecutionState) error {
+	var err error
+
 	s.mu.Lock()
 	defer s.mu.Unlock()
 	execution := localExecutionState.Execution
@@ -95,20 +97,20 @@ func (s *ExecutorBuffer) Run(ctx context.Context, localExecutionState store.Loca
 	// have not reached this backend in the first place, and should have been rejected by the frontend when asked to bid
 	if !s.runningCapacity.IsWithinLimits(ctx, *execution.TotalAllocatedResources()) {
 		err = fmt.Errorf("not enough capacity to run job")
-		return
+		return err
 	}
 
 	if s.queuedTasks.Contains(execution.ID) {
 		err = fmt.Errorf("execution %s already enqueued", execution.ID)
-		return
+		return err
 	}
 	if _, ok := s.running[execution.ID]; ok {
 		err = fmt.Errorf("execution %s already running", execution.ID)
-		return
+		return err
 	}
 	if added := s.enqueuedCapacity.AddIfHasCapacity(ctx, *execution.TotalAllocatedResources()); added == nil {
 		err = fmt.Errorf("not enough capacity to enqueue job")
-		return
+		return err
 	} else {
 		// Update the execution to include all the resources that have
 		// actually been allocated. Effectively this is picking the GPU(s)
diff --git a/pkg/compute/mocks.go b/pkg/compute/mocks.go
index 6d123680ef..3f9cfb7664 100644
--- a/pkg/compute/mocks.go
+++ b/pkg/compute/mocks.go
@@ -1,6 +1,10 @@
 // Code generated by MockGen. DO NOT EDIT.
 // Source: types.go
-
+//
+// Generated by this command:
+//
+//	mockgen --source types.go --destination mocks.go --package compute
+//
 // Package compute is a generated GoMock package.
 package compute
 
@@ -45,7 +49,7 @@ func (m *MockEndpoint) AskForBid(arg0 context.Context, arg1 AskForBidRequest) (A
 }
 
 // AskForBid indicates an expected call of AskForBid.
-func (mr *MockEndpointMockRecorder) AskForBid(arg0, arg1 interface{}) *gomock.Call {
+func (mr *MockEndpointMockRecorder) AskForBid(arg0, arg1 any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AskForBid", reflect.TypeOf((*MockEndpoint)(nil).AskForBid), arg0, arg1)
 }
@@ -60,7 +64,7 @@ func (m *MockEndpoint) BidAccepted(arg0 context.Context, arg1 BidAcceptedRequest
 }
 
 // BidAccepted indicates an expected call of BidAccepted.
-func (mr *MockEndpointMockRecorder) BidAccepted(arg0, arg1 interface{}) *gomock.Call {
+func (mr *MockEndpointMockRecorder) BidAccepted(arg0, arg1 any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BidAccepted", reflect.TypeOf((*MockEndpoint)(nil).BidAccepted), arg0, arg1)
 }
@@ -75,7 +79,7 @@ func (m *MockEndpoint) BidRejected(arg0 context.Context, arg1 BidRejectedRequest
 }
 
 // BidRejected indicates an expected call of BidRejected.
-func (mr *MockEndpointMockRecorder) BidRejected(arg0, arg1 interface{}) *gomock.Call {
+func (mr *MockEndpointMockRecorder) BidRejected(arg0, arg1 any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BidRejected", reflect.TypeOf((*MockEndpoint)(nil).BidRejected), arg0, arg1)
 }
@@ -90,7 +94,7 @@ func (m *MockEndpoint) CancelExecution(arg0 context.Context, arg1 CancelExecutio
 }
 
 // CancelExecution indicates an expected call of CancelExecution.
-func (mr *MockEndpointMockRecorder) CancelExecution(arg0, arg1 interface{}) *gomock.Call {
+func (mr *MockEndpointMockRecorder) CancelExecution(arg0, arg1 any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CancelExecution", reflect.TypeOf((*MockEndpoint)(nil).CancelExecution), arg0, arg1)
 }
@@ -105,7 +109,7 @@ func (m *MockEndpoint) ExecutionLogs(arg0 context.Context, arg1 ExecutionLogsReq
 }
 
 // ExecutionLogs indicates an expected call of ExecutionLogs.
-func (mr *MockEndpointMockRecorder) ExecutionLogs(arg0, arg1 interface{}) *gomock.Call {
+func (mr *MockEndpointMockRecorder) ExecutionLogs(arg0, arg1 any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExecutionLogs", reflect.TypeOf((*MockEndpoint)(nil).ExecutionLogs), arg0, arg1)
 }
@@ -142,7 +146,7 @@ func (m *MockExecutor) Cancel(ctx context.Context, localExecutionState store.Loc
 }
 
 // Cancel indicates an expected call of Cancel.
-func (mr *MockExecutorMockRecorder) Cancel(ctx, localExecutionState interface{}) *gomock.Call {
+func (mr *MockExecutorMockRecorder) Cancel(ctx, localExecutionState any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Cancel", reflect.TypeOf((*MockExecutor)(nil).Cancel), ctx, localExecutionState)
 }
@@ -156,7 +160,7 @@ func (m *MockExecutor) Run(ctx context.Context, localExecutionState store.LocalE
 }
 
 // Run indicates an expected call of Run.
-func (mr *MockExecutorMockRecorder) Run(ctx, localExecutionState interface{}) *gomock.Call {
+func (mr *MockExecutorMockRecorder) Run(ctx, localExecutionState any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Run", reflect.TypeOf((*MockExecutor)(nil).Run), ctx, localExecutionState)
 }
@@ -191,7 +195,7 @@ func (m *MockCallback) OnBidComplete(ctx context.Context, result BidResult) {
 }
 
 // OnBidComplete indicates an expected call of OnBidComplete.
-func (mr *MockCallbackMockRecorder) OnBidComplete(ctx, result interface{}) *gomock.Call {
+func (mr *MockCallbackMockRecorder) OnBidComplete(ctx, result any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnBidComplete", reflect.TypeOf((*MockCallback)(nil).OnBidComplete), ctx, result)
 }
@@ -203,7 +207,7 @@ func (m *MockCallback) OnCancelComplete(ctx context.Context, result CancelResult
 }
 
 // OnCancelComplete indicates an expected call of OnCancelComplete.
-func (mr *MockCallbackMockRecorder) OnCancelComplete(ctx, result interface{}) *gomock.Call {
+func (mr *MockCallbackMockRecorder) OnCancelComplete(ctx, result any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnCancelComplete", reflect.TypeOf((*MockCallback)(nil).OnCancelComplete), ctx, result)
 }
@@ -215,7 +219,7 @@ func (m *MockCallback) OnComputeFailure(ctx context.Context, err ComputeError) {
 }
 
 // OnComputeFailure indicates an expected call of OnComputeFailure.
-func (mr *MockCallbackMockRecorder) OnComputeFailure(ctx, err interface{}) *gomock.Call {
+func (mr *MockCallbackMockRecorder) OnComputeFailure(ctx, err any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnComputeFailure", reflect.TypeOf((*MockCallback)(nil).OnComputeFailure), ctx, err)
 }
@@ -227,7 +231,7 @@ func (m *MockCallback) OnRunComplete(ctx context.Context, result RunResult) {
 }
 
 // OnRunComplete indicates an expected call of OnRunComplete.
-func (mr *MockCallbackMockRecorder) OnRunComplete(ctx, result interface{}) *gomock.Call {
+func (mr *MockCallbackMockRecorder) OnRunComplete(ctx, result any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnRunComplete", reflect.TypeOf((*MockCallback)(nil).OnRunComplete), ctx, result)
 }
diff --git a/pkg/compute/store/mocks.go b/pkg/compute/store/mocks.go
index b2b6df3716..6dc2b46dde 100644
--- a/pkg/compute/store/mocks.go
+++ b/pkg/compute/store/mocks.go
@@ -1,6 +1,10 @@
 // Code generated by MockGen. DO NOT EDIT.
 // Source: types.go
-
+//
+// Generated by this command:
+//
+//	mockgen --source types.go --destination mocks.go --package store
+//
 // Package store is a generated GoMock package.
 package store
 
@@ -43,7 +47,7 @@ func (m *MockExecutionStore) Close(ctx context.Context) error {
 }
 
 // Close indicates an expected call of Close.
-func (mr *MockExecutionStoreMockRecorder) Close(ctx interface{}) *gomock.Call {
+func (mr *MockExecutionStoreMockRecorder) Close(ctx any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockExecutionStore)(nil).Close), ctx)
 }
@@ -57,7 +61,7 @@ func (m *MockExecutionStore) CreateExecution(ctx context.Context, execution Loca
 }
 
 // CreateExecution indicates an expected call of CreateExecution.
-func (mr *MockExecutionStoreMockRecorder) CreateExecution(ctx, execution interface{}) *gomock.Call {
+func (mr *MockExecutionStoreMockRecorder) CreateExecution(ctx, execution any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateExecution", reflect.TypeOf((*MockExecutionStore)(nil).CreateExecution), ctx, execution)
 }
@@ -71,7 +75,7 @@ func (m *MockExecutionStore) DeleteExecution(ctx context.Context, id string) err
 }
 
 // DeleteExecution indicates an expected call of DeleteExecution.
-func (mr *MockExecutionStoreMockRecorder) DeleteExecution(ctx, id interface{}) *gomock.Call {
+func (mr *MockExecutionStoreMockRecorder) DeleteExecution(ctx, id any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteExecution", reflect.TypeOf((*MockExecutionStore)(nil).DeleteExecution), ctx, id)
 }
@@ -86,7 +90,7 @@ func (m *MockExecutionStore) GetExecution(ctx context.Context, id string) (Local
 }
 
 // GetExecution indicates an expected call of GetExecution.
-func (mr *MockExecutionStoreMockRecorder) GetExecution(ctx, id interface{}) *gomock.Call {
+func (mr *MockExecutionStoreMockRecorder) GetExecution(ctx, id any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetExecution", reflect.TypeOf((*MockExecutionStore)(nil).GetExecution), ctx, id)
 }
@@ -101,7 +105,7 @@ func (m *MockExecutionStore) GetExecutionCount(ctx context.Context, state LocalE
 }
 
 // GetExecutionCount indicates an expected call of GetExecutionCount.
-func (mr *MockExecutionStoreMockRecorder) GetExecutionCount(ctx, state interface{}) *gomock.Call {
+func (mr *MockExecutionStoreMockRecorder) GetExecutionCount(ctx, state any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetExecutionCount", reflect.TypeOf((*MockExecutionStore)(nil).GetExecutionCount), ctx, state)
 }
@@ -116,7 +120,7 @@ func (m *MockExecutionStore) GetExecutionHistory(ctx context.Context, id string)
 }
 
 // GetExecutionHistory indicates an expected call of GetExecutionHistory.
-func (mr *MockExecutionStoreMockRecorder) GetExecutionHistory(ctx, id interface{}) *gomock.Call {
+func (mr *MockExecutionStoreMockRecorder) GetExecutionHistory(ctx, id any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetExecutionHistory", reflect.TypeOf((*MockExecutionStore)(nil).GetExecutionHistory), ctx, id)
 }
@@ -131,7 +135,7 @@ func (m *MockExecutionStore) GetExecutions(ctx context.Context, jobID string) ([
 }
 
 // GetExecutions indicates an expected call of GetExecutions.
-func (mr *MockExecutionStoreMockRecorder) GetExecutions(ctx, jobID interface{}) *gomock.Call {
+func (mr *MockExecutionStoreMockRecorder) GetExecutions(ctx, jobID any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetExecutions", reflect.TypeOf((*MockExecutionStore)(nil).GetExecutions), ctx, jobID)
 }
@@ -146,7 +150,7 @@ func (m *MockExecutionStore) GetLiveExecutions(ctx context.Context) ([]LocalExec
 }
 
 // GetLiveExecutions indicates an expected call of GetLiveExecutions.
-func (mr *MockExecutionStoreMockRecorder) GetLiveExecutions(ctx interface{}) *gomock.Call {
+func (mr *MockExecutionStoreMockRecorder) GetLiveExecutions(ctx any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLiveExecutions", reflect.TypeOf((*MockExecutionStore)(nil).GetLiveExecutions), ctx)
 }
@@ -160,7 +164,7 @@ func (m *MockExecutionStore) UpdateExecutionState(ctx context.Context, request U
 }
 
 // UpdateExecutionState indicates an expected call of UpdateExecutionState.
-func (mr *MockExecutionStoreMockRecorder) UpdateExecutionState(ctx, request interface{}) *gomock.Call {
+func (mr *MockExecutionStoreMockRecorder) UpdateExecutionState(ctx, request any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateExecutionState", reflect.TypeOf((*MockExecutionStore)(nil).UpdateExecutionState), ctx, request)
 }
diff --git a/pkg/config/configenv/dev.go b/pkg/config/configenv/dev.go
index afcf942346..90c4fb44bd 100644
--- a/pkg/config/configenv/dev.go
+++ b/pkg/config/configenv/dev.go
@@ -36,6 +36,7 @@ var Development = types.BacalhauConfig{
 		},
 		DownloadURLRequestTimeout: types.Duration(300 * time.Second),
 		VolumeSizeRequestTimeout:  types.Duration(2 * time.Minute),
+		NodeInfoStoreTTL:          types.Duration(10 * time.Minute),
 		DownloadURLRequestRetries: 3,
 		LoggingMode:               logger.LogModeDefault,
 		Type:                      []string{"requester"},
@@ -61,9 +62,17 @@ var Development = types.BacalhauConfig{
 				"/ip4/35.245.221.171/tcp/4001/p2p/12D3KooWRBYMhTF6MNh6eN84xcZtg6EX2wJguqEtRTNq4C7aytbu",
 				"/ip4/35.245.221.171/udp/4001/quic/p2p/12D3KooWRBYMhTF6MNh6eN84xcZtg6EX2wJguqEtRTNq4C7aytbu",
 			},
+			Profile:                "flatfs",
+			SwarmListenAddresses:   []string{"/ip4/0.0.0.0/tcp/0", "/ip6/::1/tcp/0"},
+			GatewayListenAddresses: []string{"/ip4/0.0.0.0/tcp/0", "/ip6/::1/tcp/0"},
+			APIListenAddresses:     []string{"/ip4/0.0.0.0/tcp/0", "/ip6/::1/tcp/0"},
 		},
 		Compute:   DevelopmentComputeConfig,
 		Requester: DevelopmentRequesterConfig,
+		WebUI: types.WebUIConfig{
+			Enabled: false,
+			Port:    80,
+		},
 	},
 }
 
diff --git a/pkg/config/configenv/local.go b/pkg/config/configenv/local.go
index 30f8038c37..8600a584cf 100644
--- a/pkg/config/configenv/local.go
+++ b/pkg/config/configenv/local.go
@@ -33,6 +33,7 @@ var Local = types.BacalhauConfig{
 		BootstrapAddresses:        []string{},
 		DownloadURLRequestTimeout: types.Duration(300 * time.Second),
 		VolumeSizeRequestTimeout:  types.Duration(2 * time.Minute),
+		NodeInfoStoreTTL:          types.Duration(10 * time.Minute),
 		DownloadURLRequestRetries: 3,
 		LoggingMode:               logger.LogModeDefault,
 		Type:                      []string{"requester"},
@@ -49,12 +50,20 @@ var Local = types.BacalhauConfig{
 			PeerConnect: "none",
 		},
 		IPFS: types.IpfsConfig{
-			Connect:         "",
-			PrivateInternal: true,
-			SwarmAddresses:  []string{},
+			Connect:                "",
+			PrivateInternal:        true,
+			SwarmAddresses:         []string{},
+			Profile:                "flatfs",
+			SwarmListenAddresses:   []string{"/ip4/0.0.0.0/tcp/0", "/ip6/::1/tcp/0"},
+			GatewayListenAddresses: []string{"/ip4/0.0.0.0/tcp/0", "/ip6/::1/tcp/0"},
+			APIListenAddresses:     []string{"/ip4/0.0.0.0/tcp/0", "/ip6/::1/tcp/0"},
 		},
 		Compute:   LocalComputeConfig,
 		Requester: LocalRequesterConfig,
+		WebUI: types.WebUIConfig{
+			Enabled: false,
+			Port:    80,
+		},
 	},
 }
 
diff --git a/pkg/config/configenv/production.go b/pkg/config/configenv/production.go
index 05781c2f86..bbe4c6b2e9 100644
--- a/pkg/config/configenv/production.go
+++ b/pkg/config/configenv/production.go
@@ -38,6 +38,7 @@ var Production = types.BacalhauConfig{
 		},
 		DownloadURLRequestTimeout: types.Duration(300 * time.Second),
 		VolumeSizeRequestTimeout:  types.Duration(2 * time.Minute),
+		NodeInfoStoreTTL:          types.Duration(10 * time.Minute),
 		DownloadURLRequestRetries: 3,
 		LoggingMode:               logger.LogModeDefault,
 		Type:                      []string{"requester"},
@@ -69,9 +70,17 @@ var Production = types.BacalhauConfig{
 				"/ip4/35.245.41.51/tcp/4001/p2p/12D3KooWJM8j97yoDTb7B9xV1WpBXakT4Zof3aMgFuSQQH56rCXa",
 				"/ip4/35.245.41.51/udp/4001/quic/p2p/12D3KooWJM8j97yoDTb7B9xV1WpBXakT4Zof3aMgFuSQQH56rCXa",
 			},
+			Profile:                "flatfs",
+			SwarmListenAddresses:   []string{"/ip4/0.0.0.0/tcp/0", "/ip6/::1/tcp/0"},
+			GatewayListenAddresses: []string{"/ip4/0.0.0.0/tcp/0", "/ip6/::1/tcp/0"},
+			APIListenAddresses:     []string{"/ip4/0.0.0.0/tcp/0", "/ip6/::1/tcp/0"},
 		},
 		Compute:   ProductionComputeConfig,
 		Requester: ProductionRequesterConfig,
+		WebUI: types.WebUIConfig{
+			Enabled: false,
+			Port:    80,
+		},
 	},
 }
 
diff --git a/pkg/config/configenv/staging.go b/pkg/config/configenv/staging.go
index 7e83ac5e79..6cf6c7ee26 100644
--- a/pkg/config/configenv/staging.go
+++ b/pkg/config/configenv/staging.go
@@ -38,6 +38,7 @@ var Staging = types.BacalhauConfig{
 		},
 		DownloadURLRequestTimeout: types.Duration(300 * time.Second),
 		VolumeSizeRequestTimeout:  types.Duration(2 * time.Minute),
+		NodeInfoStoreTTL:          types.Duration(10 * time.Minute),
 		DownloadURLRequestRetries: 3,
 		LoggingMode:               logger.LogModeDefault,
 		Type:                      []string{"requester"},
@@ -67,9 +68,17 @@ var Staging = types.BacalhauConfig{
 				"/ip4/35.245.247.85/tcp/4001/p2p/12D3KooWEztGEJtqtzy7th2d7cTw2iR4CQCPHFUYvj66rhh9Cf7h",
 				"/ip4/35.245.247.85/udp/4001/quic/p2p/12D3KooWEztGEJtqtzy7th2d7cTw2iR4CQCPHFUYvj66rhh9Cf7h",
 			},
+			Profile:                "flatfs",
+			SwarmListenAddresses:   []string{"/ip4/0.0.0.0/tcp/0", "/ip6/::1/tcp/0"},
+			GatewayListenAddresses: []string{"/ip4/0.0.0.0/tcp/0", "/ip6/::1/tcp/0"},
+			APIListenAddresses:     []string{"/ip4/0.0.0.0/tcp/0", "/ip6/::1/tcp/0"},
 		},
 		Compute:   StagingComputeConfig,
 		Requester: StagingRequesterConfig,
+		WebUI: types.WebUIConfig{
+			Enabled: false,
+			Port:    80,
+		},
 	},
 }
 
diff --git a/pkg/config/types/gen_viper/generate.go b/pkg/config/types/gen_viper/generate.go
index 8997c831a1..7bc385c0c2 100644
--- a/pkg/config/types/gen_viper/generate.go
+++ b/pkg/config/types/gen_viper/generate.go
@@ -56,6 +56,7 @@ func generateSetDefaults(t reflect.Type, prefix string, path []string, value ref
 		if field.Type.Kind() == reflect.Struct {
 			// Inserting the viper.SetDefault for the nested struct path.
 			constantNameForStruct := strings.ReplaceAll(newPrefix, ".", "")
+			//nolint:goconst
 			defaultValueForStruct := "cfg." + strings.Join(newPath, ".")
 			fmt.Fprintf(writer, "\tp.Viper.%s(%s, %s)\n", methodName, constantNameForStruct, defaultValueForStruct)
 
diff --git a/pkg/config/types/generated_constants.go b/pkg/config/types/generated_constants.go
index 183abcca92..0591fc0139 100644
--- a/pkg/config/types/generated_constants.go
+++ b/pkg/config/types/generated_constants.go
@@ -28,6 +28,10 @@ const NodeIPFSPrivateInternal = "Node.IPFS.PrivateInternal"
 const NodeIPFSSwarmAddresses = "Node.IPFS.SwarmAddresses"
 const NodeIPFSSwarmKeyPath = "Node.IPFS.SwarmKeyPath"
 const NodeIPFSServePath = "Node.IPFS.ServePath"
+const NodeIPFSProfile = "Node.IPFS.Profile"
+const NodeIPFSSwarmListenAddresses = "Node.IPFS.SwarmListenAddresses"
+const NodeIPFSGatewayListenAddresses = "Node.IPFS.GatewayListenAddresses"
+const NodeIPFSAPIListenAddresses = "Node.IPFS.APIListenAddresses"
 const NodeCompute = "Node.Compute"
 const NodeComputeCapacity = "Node.Compute.Capacity"
 const NodeComputeCapacityIgnorePhysicalResourceLimits = "Node.Compute.Capacity.IgnorePhysicalResourceLimits"
@@ -87,6 +91,7 @@ const NodeRequesterNodeRankRandomnessRange = "Node.Requester.NodeRankRandomnessR
 const NodeRequesterOverAskForBidsFactor = "Node.Requester.OverAskForBidsFactor"
 const NodeRequesterFailureInjectionConfig = "Node.Requester.FailureInjectionConfig"
 const NodeRequesterFailureInjectionConfigIsBadActor = "Node.Requester.FailureInjectionConfig.IsBadActor"
+const NodeRequesterTranslationEnabled = "Node.Requester.TranslationEnabled"
 const NodeRequesterEvaluationBroker = "Node.Requester.EvaluationBroker"
 const NodeRequesterEvaluationBrokerEvalBrokerVisibilityTimeout = "Node.Requester.EvaluationBroker.EvalBrokerVisibilityTimeout"
 const NodeRequesterEvaluationBrokerEvalBrokerInitialRetryDelay = "Node.Requester.EvaluationBroker.EvalBrokerInitialRetryDelay"
@@ -105,6 +110,7 @@ const NodeBootstrapAddresses = "Node.BootstrapAddresses"
 const NodeDownloadURLRequestRetries = "Node.DownloadURLRequestRetries"
 const NodeDownloadURLRequestTimeout = "Node.DownloadURLRequestTimeout"
 const NodeVolumeSizeRequestTimeout = "Node.VolumeSizeRequestTimeout"
+const NodeNodeInfoStoreTTL = "Node.NodeInfoStoreTTL"
 const NodeExecutorPluginPath = "Node.ExecutorPluginPath"
 const NodeComputeStoragePath = "Node.ComputeStoragePath"
 const NodeLoggingMode = "Node.LoggingMode"
@@ -117,6 +123,8 @@ const NodeDisabledFeaturesPublishers = "Node.DisabledFeatures.Publishers"
 const NodeDisabledFeaturesStorages = "Node.DisabledFeatures.Storages"
 const NodeLabels = "Node.Labels"
 const NodeWebUI = "Node.WebUI"
+const NodeWebUIEnabled = "Node.WebUI.Enabled"
+const NodeWebUIPort = "Node.WebUI.Port"
 const User = "User"
 const UserKeyPath = "User.KeyPath"
 const UserLibp2pKeyPath = "User.Libp2pKeyPath"
diff --git a/pkg/config/types/generated_viper_defaults.go b/pkg/config/types/generated_viper_defaults.go
index fa870753b7..7e0ac97181 100644
--- a/pkg/config/types/generated_viper_defaults.go
+++ b/pkg/config/types/generated_viper_defaults.go
@@ -1,4 +1,3 @@
-
 // CODE GENERATED BY pkg/config/types/gen_viper DO NOT EDIT
 
 package types
@@ -51,6 +50,10 @@ func SetDefaults(cfg BacalhauConfig, opts ...SetOption) {
 	p.Viper.SetDefault(NodeIPFSSwarmAddresses, cfg.Node.IPFS.SwarmAddresses)
 	p.Viper.SetDefault(NodeIPFSSwarmKeyPath, cfg.Node.IPFS.SwarmKeyPath)
 	p.Viper.SetDefault(NodeIPFSServePath, cfg.Node.IPFS.ServePath)
+	p.Viper.SetDefault(NodeIPFSProfile, cfg.Node.IPFS.Profile)
+	p.Viper.SetDefault(NodeIPFSSwarmListenAddresses, cfg.Node.IPFS.SwarmListenAddresses)
+	p.Viper.SetDefault(NodeIPFSGatewayListenAddresses, cfg.Node.IPFS.GatewayListenAddresses)
+	p.Viper.SetDefault(NodeIPFSAPIListenAddresses, cfg.Node.IPFS.APIListenAddresses)
 	p.Viper.SetDefault(NodeCompute, cfg.Node.Compute)
 	p.Viper.SetDefault(NodeComputeCapacity, cfg.Node.Compute.Capacity)
 	p.Viper.SetDefault(NodeComputeCapacityIgnorePhysicalResourceLimits, cfg.Node.Compute.Capacity.IgnorePhysicalResourceLimits)
@@ -110,6 +113,7 @@ func SetDefaults(cfg BacalhauConfig, opts ...SetOption) {
 	p.Viper.SetDefault(NodeRequesterOverAskForBidsFactor, cfg.Node.Requester.OverAskForBidsFactor)
 	p.Viper.SetDefault(NodeRequesterFailureInjectionConfig, cfg.Node.Requester.FailureInjectionConfig)
 	p.Viper.SetDefault(NodeRequesterFailureInjectionConfigIsBadActor, cfg.Node.Requester.FailureInjectionConfig.IsBadActor)
+	p.Viper.SetDefault(NodeRequesterTranslationEnabled, cfg.Node.Requester.TranslationEnabled)
 	p.Viper.SetDefault(NodeRequesterEvaluationBroker, cfg.Node.Requester.EvaluationBroker)
 	p.Viper.SetDefault(NodeRequesterEvaluationBrokerEvalBrokerVisibilityTimeout, cfg.Node.Requester.EvaluationBroker.EvalBrokerVisibilityTimeout.AsTimeDuration())
 	p.Viper.SetDefault(NodeRequesterEvaluationBrokerEvalBrokerInitialRetryDelay, cfg.Node.Requester.EvaluationBroker.EvalBrokerInitialRetryDelay.AsTimeDuration())
@@ -128,6 +132,7 @@ func SetDefaults(cfg BacalhauConfig, opts ...SetOption) {
 	p.Viper.SetDefault(NodeDownloadURLRequestRetries, cfg.Node.DownloadURLRequestRetries)
 	p.Viper.SetDefault(NodeDownloadURLRequestTimeout, cfg.Node.DownloadURLRequestTimeout.AsTimeDuration())
 	p.Viper.SetDefault(NodeVolumeSizeRequestTimeout, cfg.Node.VolumeSizeRequestTimeout.AsTimeDuration())
+	p.Viper.SetDefault(NodeNodeInfoStoreTTL, cfg.Node.NodeInfoStoreTTL.AsTimeDuration())
 	p.Viper.SetDefault(NodeExecutorPluginPath, cfg.Node.ExecutorPluginPath)
 	p.Viper.SetDefault(NodeComputeStoragePath, cfg.Node.ComputeStoragePath)
 	p.Viper.SetDefault(NodeLoggingMode, cfg.Node.LoggingMode)
@@ -140,6 +145,8 @@ func SetDefaults(cfg BacalhauConfig, opts ...SetOption) {
 	p.Viper.SetDefault(NodeDisabledFeaturesStorages, cfg.Node.DisabledFeatures.Storages)
 	p.Viper.SetDefault(NodeLabels, cfg.Node.Labels)
 	p.Viper.SetDefault(NodeWebUI, cfg.Node.WebUI)
+	p.Viper.SetDefault(NodeWebUIEnabled, cfg.Node.WebUI.Enabled)
+	p.Viper.SetDefault(NodeWebUIPort, cfg.Node.WebUI.Port)
 	p.Viper.SetDefault(User, cfg.User)
 	p.Viper.SetDefault(UserKeyPath, cfg.User.KeyPath)
 	p.Viper.SetDefault(UserLibp2pKeyPath, cfg.User.Libp2pKeyPath)
@@ -188,6 +195,10 @@ func Set(cfg BacalhauConfig, opts ...SetOption) {
 	p.Viper.Set(NodeIPFSSwarmAddresses, cfg.Node.IPFS.SwarmAddresses)
 	p.Viper.Set(NodeIPFSSwarmKeyPath, cfg.Node.IPFS.SwarmKeyPath)
 	p.Viper.Set(NodeIPFSServePath, cfg.Node.IPFS.ServePath)
+	p.Viper.Set(NodeIPFSProfile, cfg.Node.IPFS.Profile)
+	p.Viper.Set(NodeIPFSSwarmListenAddresses, cfg.Node.IPFS.SwarmListenAddresses)
+	p.Viper.Set(NodeIPFSGatewayListenAddresses, cfg.Node.IPFS.GatewayListenAddresses)
+	p.Viper.Set(NodeIPFSAPIListenAddresses, cfg.Node.IPFS.APIListenAddresses)
 	p.Viper.Set(NodeCompute, cfg.Node.Compute)
 	p.Viper.Set(NodeComputeCapacity, cfg.Node.Compute.Capacity)
 	p.Viper.Set(NodeComputeCapacityIgnorePhysicalResourceLimits, cfg.Node.Compute.Capacity.IgnorePhysicalResourceLimits)
@@ -247,6 +258,7 @@ func Set(cfg BacalhauConfig, opts ...SetOption) {
 	p.Viper.Set(NodeRequesterOverAskForBidsFactor, cfg.Node.Requester.OverAskForBidsFactor)
 	p.Viper.Set(NodeRequesterFailureInjectionConfig, cfg.Node.Requester.FailureInjectionConfig)
 	p.Viper.Set(NodeRequesterFailureInjectionConfigIsBadActor, cfg.Node.Requester.FailureInjectionConfig.IsBadActor)
+	p.Viper.Set(NodeRequesterTranslationEnabled, cfg.Node.Requester.TranslationEnabled)
 	p.Viper.Set(NodeRequesterEvaluationBroker, cfg.Node.Requester.EvaluationBroker)
 	p.Viper.Set(NodeRequesterEvaluationBrokerEvalBrokerVisibilityTimeout, cfg.Node.Requester.EvaluationBroker.EvalBrokerVisibilityTimeout.AsTimeDuration())
 	p.Viper.Set(NodeRequesterEvaluationBrokerEvalBrokerInitialRetryDelay, cfg.Node.Requester.EvaluationBroker.EvalBrokerInitialRetryDelay.AsTimeDuration())
@@ -265,6 +277,7 @@ func Set(cfg BacalhauConfig, opts ...SetOption) {
 	p.Viper.Set(NodeDownloadURLRequestRetries, cfg.Node.DownloadURLRequestRetries)
 	p.Viper.Set(NodeDownloadURLRequestTimeout, cfg.Node.DownloadURLRequestTimeout.AsTimeDuration())
 	p.Viper.Set(NodeVolumeSizeRequestTimeout, cfg.Node.VolumeSizeRequestTimeout.AsTimeDuration())
+	p.Viper.Set(NodeNodeInfoStoreTTL, cfg.Node.NodeInfoStoreTTL.AsTimeDuration())
 	p.Viper.Set(NodeExecutorPluginPath, cfg.Node.ExecutorPluginPath)
 	p.Viper.Set(NodeComputeStoragePath, cfg.Node.ComputeStoragePath)
 	p.Viper.Set(NodeLoggingMode, cfg.Node.LoggingMode)
@@ -277,6 +290,8 @@ func Set(cfg BacalhauConfig, opts ...SetOption) {
 	p.Viper.Set(NodeDisabledFeaturesStorages, cfg.Node.DisabledFeatures.Storages)
 	p.Viper.Set(NodeLabels, cfg.Node.Labels)
 	p.Viper.Set(NodeWebUI, cfg.Node.WebUI)
+	p.Viper.Set(NodeWebUIEnabled, cfg.Node.WebUI.Enabled)
+	p.Viper.Set(NodeWebUIPort, cfg.Node.WebUI.Port)
 	p.Viper.Set(User, cfg.User)
 	p.Viper.Set(UserKeyPath, cfg.User.KeyPath)
 	p.Viper.Set(UserLibp2pKeyPath, cfg.User.Libp2pKeyPath)
diff --git a/pkg/config/types/node.go b/pkg/config/types/node.go
index e61ca23ffc..2ac487f9fc 100644
--- a/pkg/config/types/node.go
+++ b/pkg/config/types/node.go
@@ -23,6 +23,7 @@ type NodeConfig struct {
 	DownloadURLRequestRetries int      `yaml:"DownloadURLRequestRetries"`
 	DownloadURLRequestTimeout Duration `yaml:"DownloadURLRequestTimeout"`
 	VolumeSizeRequestTimeout  Duration `yaml:"VolumeSizeRequestTimeout"`
+	NodeInfoStoreTTL          Duration `yaml:"NodeInfoStoreTTL"`
 
 	ExecutorPluginPath string `yaml:"ExecutorPluginPath"`
 
@@ -40,7 +41,8 @@ type NodeConfig struct {
 	// Labels to apply to the node that can be used for node selection and filtering
 	Labels map[string]string `yaml:"Labels"`
 
-	WebUI bool `yaml:"WebUI"`
+	// Configuration for the web UI
+	WebUI WebUIConfig `yaml:"WebUI"`
 }
 
 type APIConfig struct {
@@ -52,6 +54,11 @@ type APIConfig struct {
 	TLS TLSConfiguration `yaml:"TLS"`
 }
 
+type WebUIConfig struct {
+	Enabled bool `yaml:"Enabled"`
+	Port    int  `yaml:"Port"`
+}
+
 type TLSConfiguration struct {
 	// AutoCert specifies a hostname for a certificate to be obtained via ACME.
 	// This is only used by the server, and only by the requester node when it
@@ -83,11 +90,17 @@ type IpfsConfig struct {
 	// Whether the in-process IPFS should automatically discover other IPFS nodes
 	PrivateInternal bool `yaml:"PrivateInternal"`
 	// IPFS multiaddresses that the in-process IPFS should connect to
+	// TODO call this Peers, its peers the node will try and stay connected to.
 	SwarmAddresses []string `yaml:"SwarmAddresses"`
 	// Optional IPFS swarm key required to connect to a private IPFS swarm
 	SwarmKeyPath string `yaml:"SwarmKeyPath"`
 	// Path of the IPFS repo
 	ServePath string `yaml:"ServePath"`
+
+	Profile                string   `yaml:"Profile"`
+	SwarmListenAddresses   []string `yaml:"SwarmListenAddresses"`
+	GatewayListenAddresses []string `yaml:"GatewayListenAddresses"`
+	APIListenAddresses     []string `yaml:"APIListenAddresses"`
 }
 
 // Due to a bug in Viper (https://github.com/spf13/viper/issues/380), string
diff --git a/pkg/config/types/requester.go b/pkg/config/types/requester.go
index de21adac5b..bd4dee6b5d 100644
--- a/pkg/config/types/requester.go
+++ b/pkg/config/types/requester.go
@@ -17,6 +17,8 @@ type RequesterConfig struct {
 	OverAskForBidsFactor               uint                                  `yaml:"OverAskForBidsFactor"`
 	FailureInjectionConfig             model.FailureInjectionRequesterConfig `yaml:"FailureInjectionConfig"`
 
+	TranslationEnabled bool `yaml:"TranslationEnabled"`
+
 	EvaluationBroker EvaluationBrokerConfig `yaml:"EvaluationBroker"`
 	Worker           WorkerConfig           `yaml:"Worker"`
 	StorageProvider  StorageProviderConfig  `yaml:"StorageProvider"`
diff --git a/pkg/devstack/devstack.go b/pkg/devstack/devstack.go
index 527837b908..1f348ef721 100644
--- a/pkg/devstack/devstack.go
+++ b/pkg/devstack/devstack.go
@@ -106,9 +106,9 @@ func Setup(
 		isComputeNode := (totalNodeCount - i) <= computeNodeCount
 		log.Ctx(ctx).Debug().Msgf(`Creating Node #%d as {RequesterNode: %t, ComputeNode: %t}`, i+1, isRequesterNode, isComputeNode)
 
-		//////////////////////////////////////
+		// ////////////////////////////////////
 		// IPFS
-		//////////////////////////////////////
+		// ////////////////////////////////////
 
 		var ipfsSwarmAddresses []string
 		if i > 0 {
@@ -127,9 +127,9 @@ func Setup(
 			return nil, fmt.Errorf("failed to create ipfs node: %w", err)
 		}
 
-		//////////////////////////////////////
+		// ////////////////////////////////////
 		// libp2p
-		//////////////////////////////////////
+		// ////////////////////////////////////
 		var libp2pPeer []multiaddr.Multiaddr
 		libp2pPort, err := freeport.GetFreePort()
 		if err != nil {
@@ -180,18 +180,18 @@ func Setup(
 		// add NodeID to logging context
 		ctx = logger.ContextWithNodeIDLogger(ctx, libp2pHost.ID().String())
 
-		//////////////////////////////////////
+		// ////////////////////////////////////
 		// port for API
-		//////////////////////////////////////
+		// ////////////////////////////////////
 		apiPort := uint16(0)
 		if os.Getenv("PREDICTABLE_API_PORT") != "" {
 			const startPort = 20000
 			apiPort = uint16(startPort + i)
 		}
 
-		//////////////////////////////////////
+		// ////////////////////////////////////
 		// Create and Run Node
-		//////////////////////////////////////
+		// ////////////////////////////////////
 
 		// here is where we can parse string based CLI stackConfig
 		// into more meaningful model.FailureInjectionConfig values
@@ -231,6 +231,7 @@ func Setup(
 			AllowListedLocalPaths:     stackConfig.AllowListedLocalPaths,
 			NodeInfoPublisherInterval: nodeInfoPublisherInterval,
 			FsRepo:                    fsRepo,
+			NodeInfoStoreTTL:          stackConfig.NodeInfoStoreTTL,
 		}
 
 		if isRequesterNode {
@@ -293,9 +294,9 @@ func createIPFSNode(ctx context.Context,
 	ipfsSwarmAddresses []string) (*ipfs.Node, error) {
 	ctx, span := system.NewSpan(ctx, system.GetTracer(), "pkg/devstack.createIPFSNode")
 	defer span.End()
-	//////////////////////////////////////
+	// ////////////////////////////////////
 	// IPFS
-	//////////////////////////////////////
+	// ////////////////////////////////////
 	return ipfs.NewNodeWithConfig(ctx, cm, types.IpfsConfig{SwarmAddresses: ipfsSwarmAddresses, PrivateInternal: !publicIPFSMode})
 }
 
diff --git a/pkg/devstack/option.go b/pkg/devstack/option.go
index 43236fc7c2..7828e011c9 100644
--- a/pkg/devstack/option.go
+++ b/pkg/devstack/option.go
@@ -2,6 +2,7 @@ package devstack
 
 import (
 	"fmt"
+	"time"
 
 	"github.com/hashicorp/go-multierror"
 	"github.com/rs/zerolog"
@@ -43,6 +44,7 @@ func defaultDevStackConfig() (*DevStackConfig, error) {
 		DisabledFeatures:           node.FeatureConfig{},
 		AllowListedLocalPaths:      nil,
 		ExecutorPlugins:            false,
+		NodeInfoStoreTTL:           10 * time.Minute,
 	}, nil
 }
 
@@ -66,6 +68,7 @@ type DevStackConfig struct {
 	AllowListedLocalPaths      []string // Local paths that are allowed to be mounted into jobs
 	NodeInfoPublisherInterval  routing.NodeInfoPublisherIntervalConfig
 	ExecutorPlugins            bool // when true pluggable executors will be used.
+	NodeInfoStoreTTL           time.Duration
 }
 
 func (o *DevStackConfig) MarshalZerologObject(e *zerolog.Event) {
diff --git a/pkg/eventhandler/mock_eventhandler/mock_handlers.go b/pkg/eventhandler/mock_eventhandler/mock_handlers.go
index 8b60b460c4..899aebb6d6 100644
--- a/pkg/eventhandler/mock_eventhandler/mock_handlers.go
+++ b/pkg/eventhandler/mock_eventhandler/mock_handlers.go
@@ -1,6 +1,10 @@
 // Code generated by MockGen. DO NOT EDIT.
 // Source: interfaces.go
-
+//
+// Generated by this command:
+//
+//	mockgen --source interfaces.go --destination mock_eventhandler/mock_handlers.go --package mock_eventhandler
+//
 // Package mock_eventhandler is a generated GoMock package.
 package mock_eventhandler
 
@@ -44,7 +48,7 @@ func (m *MockJobEventHandler) HandleJobEvent(ctx context.Context, event model.Jo
 }
 
 // HandleJobEvent indicates an expected call of HandleJobEvent.
-func (mr *MockJobEventHandlerMockRecorder) HandleJobEvent(ctx, event interface{}) *gomock.Call {
+func (mr *MockJobEventHandlerMockRecorder) HandleJobEvent(ctx, event any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleJobEvent", reflect.TypeOf((*MockJobEventHandler)(nil).HandleJobEvent), ctx, event)
 }
diff --git a/pkg/executor/docker/models/types.go b/pkg/executor/docker/models/types.go
index 3529f2be2d..19279dd901 100644
--- a/pkg/executor/docker/models/types.go
+++ b/pkg/executor/docker/models/types.go
@@ -45,6 +45,7 @@ func (c EngineSpec) ToMap() map[string]interface{} {
 
 func DecodeSpec(spec *models.SpecConfig) (EngineSpec, error) {
 	if !spec.IsType(models.EngineDocker) {
+		//nolint:goconst
 		return EngineSpec{}, errors.New("invalid docker engine type. expected " + models.EngineDocker + ", but received: " + spec.Type)
 	}
 	inputParams := spec.Params
diff --git a/pkg/executor/wasm/handler.go b/pkg/executor/wasm/handler.go
index 85e1c9ca2d..77fd67d671 100644
--- a/pkg/executor/wasm/handler.go
+++ b/pkg/executor/wasm/handler.go
@@ -96,7 +96,7 @@ func (h *executionHandler) run(ctx context.Context) {
 		adapter = opentelemetry.NewOTelAdapter(&conf)
 		adapter.UseCustomClient(traceClient)
 		adapter.Start(ctx)
-		defer adapter.StopWithContext(ctx, true)
+		defer func() { _ = adapter.StopWithContext(ctx, true) }()
 	}
 
 	tracingEngine := tracedRuntime{Runtime: h.runtime, adapter: adapter}
diff --git a/pkg/executor/wasm/models/types.go b/pkg/executor/wasm/models/types.go
index 6e139f4ee5..daa84b5154 100644
--- a/pkg/executor/wasm/models/types.go
+++ b/pkg/executor/wasm/models/types.go
@@ -70,6 +70,7 @@ type legacyEngineSpec struct {
 
 func DecodeSpec(spec *models.SpecConfig) (EngineSpec, error) {
 	if !spec.IsType(models.EngineWasm) {
+		//nolint:goconst
 		return EngineSpec{}, errors.New("invalid wasm engine type. expected " + models.EngineWasm + ", but received: " + spec.Type)
 	}
 
diff --git a/pkg/executor/wasm/trace.go b/pkg/executor/wasm/trace.go
index a6c99fd6b6..8dfe625773 100644
--- a/pkg/executor/wasm/trace.go
+++ b/pkg/executor/wasm/trace.go
@@ -144,7 +144,9 @@ func (t tracedFunction) Call(ctx context.Context, params ...uint64) ([]uint64, e
 	defer span.End()
 
 	if t.traceCtx != nil {
-		t.traceCtx.SetTraceId(span.SpanContext().TraceID().String())
+		if err := t.traceCtx.SetTraceId(span.SpanContext().TraceID().String()); err != nil {
+			return nil, err
+		}
 	}
 	return telemetry.RecordErrorOnSpanTwo[[]uint64](span)(t.Function.Call(ctx, params...))
 }
@@ -161,7 +163,9 @@ func (t tracedFunction) CallWithStack(ctx context.Context, stack []uint64) error
 	)
 	defer span.End()
 	if t.traceCtx != nil {
-		t.traceCtx.SetTraceId(span.SpanContext().TraceID().String())
+		if err := t.traceCtx.SetTraceId(span.SpanContext().TraceID().String()); err != nil {
+			return err
+		}
 	}
 	return telemetry.RecordErrorOnSpan(span)(t.Function.CallWithStack(ctx, stack))
 }
diff --git a/pkg/ipfs/config.go b/pkg/ipfs/config.go
new file mode 100644
index 0000000000..702ca88869
--- /dev/null
+++ b/pkg/ipfs/config.go
@@ -0,0 +1,139 @@
+package ipfs
+
+import (
+	"fmt"
+	"io"
+
+	"github.com/ipfs/kubo/config"
+
+	bac_config "github.com/bacalhau-project/bacalhau/pkg/config"
+	"github.com/bacalhau-project/bacalhau/pkg/config/types"
+)
+
+func buildIPFSConfig(cfg types.IpfsConfig) (*config.Config, error) {
+	// NB(forrest): these empty checks are here because some tests pass
+	// partial configs to these methods, and we cannot depend on the config being
+	// fully populated with default values.
+	profileName := cfg.Profile
+	if profileName == "" {
+		profileName = "flatfs"
+	}
+
+	swarmListenAddresses := cfg.SwarmListenAddresses
+	if len(swarmListenAddresses) == 0 {
+		swarmListenAddresses = []string{"/ip4/0.0.0.0/tcp/0", "/ip6/::1/tcp/0"}
+	}
+
+	gatewayListenAddresses := cfg.GatewayListenAddresses
+	if len(gatewayListenAddresses) == 0 {
+		gatewayListenAddresses = []string{"/ip4/0.0.0.0/tcp/0", "/ip6/::1/tcp/0"}
+	}
+
+	apiListenAddresses := cfg.APIListenAddresses
+	if len(apiListenAddresses) == 0 {
+		apiListenAddresses = []string{"/ip4/0.0.0.0/tcp/0", "/ip6/::1/tcp/0"}
+	}
+
+	profile := config.Profiles[profileName]
+	transformers := []config.Transformer{
+		withSwarmListenAddresses(swarmListenAddresses...),
+		withGatewayListenAddresses(gatewayListenAddresses...),
+		withAPIListenAddresses(apiListenAddresses...),
+	}
+
+	// If we're in local mode, then we need to manually change the config to
+	// serve an IPFS swarm client on some local port. Else, make sure we are
+	// only serving the API on a local connection
+	if cfg.PrivateInternal {
+		profile = config.Profiles["test"]
+		transformers = append(transformers,
+			// disable autonat, UPnP, hole-punching and relays
+			withLocalOnly(),
+			// only listen for swarm connections on local endpoint.
+			withSwarmListenAddresses("/ip4/127.0.0.1/tcp/0"),
+		)
+	}
+	if len(cfg.SwarmAddresses) > 0 {
+		privateSwarm := cfg.SwarmKeyPath != ""
+		transformers = append(transformers,
+			withSwarm(cfg.GetSwarmAddresses(), privateSwarm))
+	}
+
+	ipfsConfig, err := config.Init(io.Discard, defaultKeypairSize)
+	if err != nil {
+		return nil, fmt.Errorf("failed to initialize config: %w", err)
+	}
+
+	if err := profile.Transform(ipfsConfig); err != nil {
+		return nil, err
+	}
+
+	for _, transformer := range transformers {
+		if err := transformer(ipfsConfig); err != nil {
+			return nil, err
+		}
+	}
+	return ipfsConfig, nil
+}
+
+// withLocalOnly switches off networking services that might connect to public nodes
+func withLocalOnly() config.Transformer {
+	return func(c *config.Config) error {
+		c.AutoNAT.ServiceMode = config.AutoNATServiceDisabled
+		c.Swarm.EnableHolePunching = config.False
+		c.Swarm.RelayClient.Enabled = config.False
+		c.Swarm.RelayService.Enabled = config.False
+		c.Swarm.Transports.Network.Relay = config.False
+		return nil
+	}
+}
+
+func withAPIListenAddresses(addrs ...string) config.Transformer {
+	return func(c *config.Config) error {
+		c.Addresses.API = addrs
+		return nil
+	}
+}
+
+func withGatewayListenAddresses(addrs ...string) config.Transformer {
+	return func(c *config.Config) error {
+		c.Addresses.Gateway = addrs
+		return nil
+	}
+}
+
+func withSwarmListenAddresses(addrs ...string) config.Transformer {
+	// TODO(forrest): deprecate this someday.
+	preferredAddr := bac_config.PreferredAddress()
+	if preferredAddr != "" {
+		return func(c *config.Config) error {
+			c.Addresses.Swarm = []string{fmt.Sprintf("/ip4/%s/tcp/0", preferredAddr)}
+			return nil
+		}
+	}
+	return func(c *config.Config) error {
+		c.Addresses.Swarm = addrs
+		return nil
+	}
+}
+
+// withSwarm will cause IPFS to continuously connect to the swarm.
+// If the swarm is private, don't bootstrap with public nodes, only with swarm nodes.
+func withSwarm(addrs []string, private bool) config.Transformer {
+	return func(c *config.Config) error {
+		// establish peering with the passed nodes. This is different than
+		// bootstrapping or manually connecting to peers, and kubo will
+		// create sticky connections with these nodes and reconnect if the
+		// connection is lost
+		// https://github.com/ipfs/kubo/blob/master/docs/config.md#peering
+		swarmPeers, err := ParsePeersString(addrs)
+		if err != nil {
+			return fmt.Errorf("failed to parse peer addresses: %w", err)
+		}
+		c.Peering = config.Peering{Peers: swarmPeers}
+		if private {
+			c.Bootstrap = addrs
+		}
+		return nil
+	}
+}
diff --git a/pkg/ipfs/node.go b/pkg/ipfs/node.go
index f4a483934b..69713bdf31 100644
--- a/pkg/ipfs/node.go
+++ b/pkg/ipfs/node.go
@@ -3,7 +3,6 @@ package ipfs
 import (
 	"context"
 	"fmt"
-	"io"
 	"net"
 	"os"
 	"path/filepath"
@@ -17,14 +16,12 @@ import (
 	manet "github.com/multiformats/go-multiaddr/net"
 	"github.com/pkg/errors"
 
-	bac_config "github.com/bacalhau-project/bacalhau/pkg/config"
 	"github.com/bacalhau-project/bacalhau/pkg/config/types"
 	"github.com/bacalhau-project/bacalhau/pkg/system"
 
 	"github.com/rs/zerolog/log"
 
 	"github.com/ipfs/kubo/commands"
-	"github.com/ipfs/kubo/config"
 	"github.com/ipfs/kubo/core"
 	"github.com/ipfs/kubo/core/coreapi"
 	"github.com/ipfs/kubo/core/corehttp"
@@ -74,7 +71,7 @@ func NewNodeWithConfig(ctx context.Context, cm *system.CleanupManager, cfg types
 		return nil, err
 	}
 
-	api, ipfsNode, repoPath, err := createNode(ctx, cm, cfg)
+	api, ipfsNode, repoPath, err := createNode(ctx, cfg)
 	if err != nil {
 		return nil, fmt.Errorf("failed to create ipfs node: %w", err)
 	}
@@ -174,8 +171,8 @@ func (n *Node) Close(ctx context.Context) error {
 		}
 	}
 
-	// don't delete repo if we've setup BACALHAU_SERVE_IPFS_PATH
-	if n.RepoPath != "" && n.cfg.ServePath != "" {
+	// delete repo if user didn't specify a repo path.
+	if n.cfg.ServePath == "" {
 		if err := os.RemoveAll(n.RepoPath); err != nil {
 			errs = multierror.Append(errs, fmt.Errorf("failed to clean up repo directory: %w", err))
 		}
@@ -184,26 +181,42 @@ func (n *Node) Close(ctx context.Context) error {
 }
 
 // createNode spawns a new IPFS node using a temporary repo path.
-func createNode(ctx context.Context, _ *system.CleanupManager, ipfsConfig types.IpfsConfig) (icore.CoreAPI, *core.IpfsNode, string, error) {
-	var err error
-	repoPath := ipfsConfig.ServePath
-	if repoPath == "" {
-		repoPath, err = os.MkdirTemp("", "ipfs-tmp")
-	} else {
-		err = os.MkdirAll(repoPath, PvtIpfsFolderPerm)
-	}
+func createNode(ctx context.Context, cfg types.IpfsConfig) (icore.CoreAPI, *core.IpfsNode,
+	string, error) {
+	// generate an IPFS configuration
+	ipfsCfg, err := buildIPFSConfig(cfg)
 	if err != nil {
-		return nil, nil, "", fmt.Errorf("failed to create repo dir: %w", err)
+		return nil, nil, "", fmt.Errorf("failed to build IPFS config")
 	}
 
-	if err = createRepo(repoPath, ipfsConfig); err != nil {
-		return nil, nil, "", fmt.Errorf("failed to create repo: %w", err)
+	// we need to check if the user wants a custom repo path and if there is already a repo present there.
+	repoPath := cfg.ServePath
+	if repoPath == "" {
+		// user doesn't want a custom repo path, we will make temporary repo that's removed when the node calls Close()
+		repoPath, err = os.MkdirTemp("", "ipfs-tmp")
+		if err != nil {
+			return nil, nil, "", fmt.Errorf("failed to make temporary IPFS repo: %w", err)
+		}
+		if err := fsrepo.Init(repoPath, ipfsCfg); err != nil {
+			return nil, nil, "", fmt.Errorf("failed to initialize IPFS repo at %s: %w", repoPath, err)
+		}
+	} else {
+		// user wants deterministic repo path, check if one is already present
+		if _, err := os.Stat(repoPath); err != nil {
+			if os.IsNotExist(err) {
+				if err := fsrepo.Init(repoPath, ipfsCfg); err != nil {
+					return nil, nil, "", fmt.Errorf("failed to initialize IPFS repo at %s: %w", repoPath, err)
+				}
+			} else {
+				return nil, nil, "", err
+			}
+		}
 	}
 
 	// If we have a swarm key, copy it into the repo
-	if ipfsConfig.SwarmKeyPath != "" {
+	if cfg.SwarmKeyPath != "" {
 		destinationPath := filepath.Join(repoPath, "swarm.key")
-		err = copyFile(ipfsConfig.SwarmKeyPath, destinationPath)
+		err = copyFile(cfg.SwarmKeyPath, destinationPath)
 		if err != nil {
 			return nil, nil, "", fmt.Errorf("failed to copy swarm key: %w", err)
 		} else {
@@ -216,6 +229,23 @@ func createNode(ctx context.Context, _ *system.CleanupManager, ipfsConfig types.
 		return nil, nil, "", fmt.Errorf("failed to open repo: %w", err)
 	}
 
+	// if the user provided an IPFS repo then we need to copy the peerID and private key
+	// from the existing repo to the configuration we built. This will allow the embedded IPFS node to keep
+	// its identity across restarts.
+	if cfg.ServePath != "" {
+		repoCfg, err := repo.Config()
+		if err != nil {
+			return nil, nil, "", err
+		}
+		ipfsCfg.Identity.PeerID = repoCfg.Identity.PeerID
+		ipfsCfg.Identity.PrivKey = repoCfg.Identity.PrivKey
+	}
+
+	// write the configuration we built to the IPFS repo.
+	if err := repo.SetConfig(ipfsCfg); err != nil {
+		return nil, nil, "", err
+	}
+
 	nodeOptions := &core.BuildCfg{
 		Repo:    repo,
 		Online:  true,
@@ -323,112 +353,6 @@ func connectToPeers(ctx context.Context, api icore.CoreAPI, node *core.IpfsNode,
 	return anyErr
 }
 
-// Switch off networking services that might connect to public nodes
-var localOnlyProfile = config.Profile{
-	Transform: func(c *config.Config) error {
-		c.AutoNAT.ServiceMode = config.AutoNATServiceDisabled
-		c.Swarm.EnableHolePunching = config.False
-		c.Swarm.RelayClient.Enabled = config.False
-		c.Swarm.RelayService.Enabled = config.False
-		c.Swarm.Transports.Network.Relay = config.False
-		return nil
-	},
-}
-
-// Serve the IPFS HTTP API on a local-only address
-var localAPIProfile = config.Profile{
-	Transform: func(c *config.Config) error {
-		c.Addresses.API = []string{"/ip4/127.0.0.1/tcp/0"}
-		return nil
-	},
-}
-
-// Serve the IPFS services on random ports to ensure no port clashes
-var randomPortsProfile = config.Profile{
-	Transform: func(c *config.Config) error {
-		c.Addresses.API = []string{"/ip4/0.0.0.0/tcp/0", "/ip6/::1/tcp/0"}
-		c.Addresses.Gateway = []string{"/ip4/0.0.0.0/tcp/0", "/ip6/::1/tcp/0"}
-		c.Addresses.Swarm = []string{"/ip4/0.0.0.0/tcp/0", "/ip6/::1/tcp/0"}
-		return nil
-	},
-}
-
-// Only serve the swarm on the preferred listen address.
-func preferredAddressProfile(preferredAddress string) config.Profile {
-	return config.Profile{
-		Transform: func(c *config.Config) error {
-			c.Addresses.Swarm = []string{fmt.Sprintf("/ip4/%s/tcp/0", preferredAddress)}
-			return nil
-		},
-	}
-}
-
-// Continuously connect to the swarm. If the swarm is private, don't bootstrap
-// with public nodes, only with swarm nodes.
-func connectToSwarmProfile(swarmAddrs []string, privateSwarm bool) config.Profile {
-	return config.Profile{
-		Transform: func(c *config.Config) error {
-			// establish peering with the passed nodes. This is different than
-			// bootstrapping or manually connecting to peers, and kubo will
-			// create sticky connections with these nodes and reconnect if the
-			// connection is lost
-			// https://github.com/ipfs/kubo/blob/master/docs/config.md#peering
-			swarmPeers, err := ParsePeersString(swarmAddrs)
-			if err != nil {
-				return fmt.Errorf("failed to parse peer addresses: %w", err)
-			}
-			c.Peering = config.Peering{Peers: swarmPeers}
-			if privateSwarm {
-				c.Bootstrap = swarmAddrs
-			}
-			return nil
-		},
-	}
-}
-
-// createRepo creates an IPFS repository in a given directory.
-func createRepo(path string, nodeConfig types.IpfsConfig) error {
-	cfg, err := config.Init(io.Discard, defaultKeypairSize)
-	if err != nil {
-		return fmt.Errorf("failed to initialize config: %w", err)
-	}
-
-	profiles := []config.Profile{
-		config.Profiles["flatfs"],
-		randomPortsProfile,
-		localAPIProfile,
-	}
-
-	// If we're in local mode, then we need to manually change the config to
-	// serve an IPFS swarm client on some local port. Else, make sure we are
-	// only serving the API on a local connection
-	if nodeConfig.PrivateInternal {
-		profiles = append(profiles, config.Profiles["test"], localOnlyProfile)
-	}
-
-	if nodeConfig.SwarmAddresses != nil {
-		privateSwarm := nodeConfig.SwarmKeyPath != ""
-		profiles = append(profiles, connectToSwarmProfile(nodeConfig.GetSwarmAddresses(), privateSwarm))
-	}
-
-	if preferredAddress := bac_config.PreferredAddress(); preferredAddress != "" {
-		profiles = append(profiles, preferredAddressProfile(preferredAddress))
-	}
-
-	for _, transformer := range profiles {
-		if err = transformer.Transform(cfg); err != nil {
-			return err
-		}
-	}
-
-	err = fsrepo.Init(path, cfg)
-	if err != nil {
-		return fmt.Errorf("failed to init ipfs repo: %w", err)
-	}
-
-	return nil
-}
-
 // loadPlugins initializes and injects the standard set of ipfs plugins.
 func loadPlugins(cm *system.CleanupManager) error {
 	// We use a temporary folder for the plugin loader so that when it
diff --git a/pkg/job/factory.go b/pkg/job/factory.go
index d82dd8e28e..548602b51a 100644
--- a/pkg/job/factory.go
+++ b/pkg/job/factory.go
@@ -11,6 +11,7 @@ type SpecOpt func(s *model.Spec) error
 
 func WithPublisher(p model.PublisherSpec) SpecOpt {
 	return func(s *model.Spec) error {
+		//nolint:staticcheck // TODO: remove this when we have a proper publisher
 		s.Publisher = p.Type
 		s.PublisherSpec = p
 		return nil
diff --git a/pkg/jobstore/mocks.go b/pkg/jobstore/mocks.go
index d53291a2fd..c8782c6b90 100644
--- a/pkg/jobstore/mocks.go
+++ b/pkg/jobstore/mocks.go
@@ -1,6 +1,10 @@
 // Code generated by MockGen. DO NOT EDIT.
 // Source: types.go
-
+//
+// Generated by this command:
+//
+//	mockgen --source types.go --destination mocks.go --package jobstore
+//
 // Package jobstore is a generated GoMock package.
 package jobstore
 
@@ -44,7 +48,7 @@ func (m *MockStore) Close(ctx context.Context) error {
 }
 
 // Close indicates an expected call of Close.
-func (mr *MockStoreMockRecorder) Close(ctx interface{}) *gomock.Call {
+func (mr *MockStoreMockRecorder) Close(ctx any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockStore)(nil).Close), ctx)
 }
@@ -58,7 +62,7 @@ func (m *MockStore) CreateEvaluation(ctx context.Context, eval models.Evaluation
 }
 
 // CreateEvaluation indicates an expected call of CreateEvaluation.
-func (mr *MockStoreMockRecorder) CreateEvaluation(ctx, eval interface{}) *gomock.Call {
+func (mr *MockStoreMockRecorder) CreateEvaluation(ctx, eval any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateEvaluation", reflect.TypeOf((*MockStore)(nil).CreateEvaluation), ctx, eval)
 }
@@ -72,7 +76,7 @@ func (m *MockStore) CreateExecution(ctx context.Context, execution models.Execut
 }
 
 // CreateExecution indicates an expected call of CreateExecution.
-func (mr *MockStoreMockRecorder) CreateExecution(ctx, execution interface{}) *gomock.Call {
+func (mr *MockStoreMockRecorder) CreateExecution(ctx, execution any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateExecution", reflect.TypeOf((*MockStore)(nil).CreateExecution), ctx, execution)
 }
@@ -86,7 +90,7 @@ func (m *MockStore) CreateJob(ctx context.Context, j models.Job) error {
 }
 
 // CreateJob indicates an expected call of CreateJob.
-func (mr *MockStoreMockRecorder) CreateJob(ctx, j interface{}) *gomock.Call {
+func (mr *MockStoreMockRecorder) CreateJob(ctx, j any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateJob", reflect.TypeOf((*MockStore)(nil).CreateJob), ctx, j)
 }
@@ -100,7 +104,7 @@ func (m *MockStore) DeleteEvaluation(ctx context.Context, id string) error {
 }
 
 // DeleteEvaluation indicates an expected call of DeleteEvaluation.
-func (mr *MockStoreMockRecorder) DeleteEvaluation(ctx, id interface{}) *gomock.Call {
+func (mr *MockStoreMockRecorder) DeleteEvaluation(ctx, id any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteEvaluation", reflect.TypeOf((*MockStore)(nil).DeleteEvaluation), ctx, id)
 }
@@ -114,7 +118,7 @@ func (m *MockStore) DeleteJob(ctx context.Context, jobID string) error {
 }
 
 // DeleteJob indicates an expected call of DeleteJob.
-func (mr *MockStoreMockRecorder) DeleteJob(ctx, jobID interface{}) *gomock.Call {
+func (mr *MockStoreMockRecorder) DeleteJob(ctx, jobID any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteJob", reflect.TypeOf((*MockStore)(nil).DeleteJob), ctx, jobID)
 }
@@ -129,7 +133,7 @@ func (m *MockStore) GetEvaluation(ctx context.Context, id string) (models.Evalua
 }
 
 // GetEvaluation indicates an expected call of GetEvaluation.
-func (mr *MockStoreMockRecorder) GetEvaluation(ctx, id interface{}) *gomock.Call {
+func (mr *MockStoreMockRecorder) GetEvaluation(ctx, id any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEvaluation", reflect.TypeOf((*MockStore)(nil).GetEvaluation), ctx, id)
 }
@@ -144,7 +148,7 @@ func (m *MockStore) GetExecutions(ctx context.Context, jobID string) ([]models.E
 }
 
 // GetExecutions indicates an expected call of GetExecutions.
-func (mr *MockStoreMockRecorder) GetExecutions(ctx, jobID interface{}) *gomock.Call {
+func (mr *MockStoreMockRecorder) GetExecutions(ctx, jobID any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetExecutions", reflect.TypeOf((*MockStore)(nil).GetExecutions), ctx, jobID)
 }
@@ -159,7 +163,7 @@ func (m *MockStore) GetInProgressJobs(ctx context.Context) ([]models.Job, error)
 }
 
 // GetInProgressJobs indicates an expected call of GetInProgressJobs.
-func (mr *MockStoreMockRecorder) GetInProgressJobs(ctx interface{}) *gomock.Call {
+func (mr *MockStoreMockRecorder) GetInProgressJobs(ctx any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInProgressJobs", reflect.TypeOf((*MockStore)(nil).GetInProgressJobs), ctx)
 }
@@ -174,7 +178,7 @@ func (m *MockStore) GetJob(ctx context.Context, id string) (models.Job, error) {
 }
 
 // GetJob indicates an expected call of GetJob.
-func (mr *MockStoreMockRecorder) GetJob(ctx, id interface{}) *gomock.Call {
+func (mr *MockStoreMockRecorder) GetJob(ctx, id any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetJob", reflect.TypeOf((*MockStore)(nil).GetJob), ctx, id)
 }
@@ -189,7 +193,7 @@ func (m *MockStore) GetJobHistory(ctx context.Context, jobID string, options Job
 }
 
 // GetJobHistory indicates an expected call of GetJobHistory.
-func (mr *MockStoreMockRecorder) GetJobHistory(ctx, jobID, options interface{}) *gomock.Call {
+func (mr *MockStoreMockRecorder) GetJobHistory(ctx, jobID, options any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetJobHistory", reflect.TypeOf((*MockStore)(nil).GetJobHistory), ctx, jobID, options)
 }
@@ -204,7 +208,7 @@ func (m *MockStore) GetJobs(ctx context.Context, query JobQuery) ([]models.Job,
 }
 
 // GetJobs indicates an expected call of GetJobs.
-func (mr *MockStoreMockRecorder) GetJobs(ctx, query interface{}) *gomock.Call {
+func (mr *MockStoreMockRecorder) GetJobs(ctx, query any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetJobs", reflect.TypeOf((*MockStore)(nil).GetJobs), ctx, query)
 }
@@ -218,7 +222,7 @@ func (m *MockStore) UpdateExecution(ctx context.Context, request UpdateExecution
 }
 
 // UpdateExecution indicates an expected call of UpdateExecution.
-func (mr *MockStoreMockRecorder) UpdateExecution(ctx, request interface{}) *gomock.Call {
+func (mr *MockStoreMockRecorder) UpdateExecution(ctx, request any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateExecution", reflect.TypeOf((*MockStore)(nil).UpdateExecution), ctx, request)
 }
@@ -232,7 +236,7 @@ func (m *MockStore) UpdateJobState(ctx context.Context, request UpdateJobStateRe
 }
 
 // UpdateJobState indicates an expected call of UpdateJobState.
-func (mr *MockStoreMockRecorder) UpdateJobState(ctx, request interface{}) *gomock.Call {
+func (mr *MockStoreMockRecorder) UpdateJobState(ctx, request any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateJobState", reflect.TypeOf((*MockStore)(nil).UpdateJobState), ctx, request)
 }
@@ -246,7 +250,7 @@ func (m *MockStore) Watch(ctx context.Context, types StoreWatcherType, events St
 }
 
 // Watch indicates an expected call of Watch.
-func (mr *MockStoreMockRecorder) Watch(ctx, types, events interface{}) *gomock.Call {
+func (mr *MockStoreMockRecorder) Watch(ctx, types, events any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Watch", reflect.TypeOf((*MockStore)(nil).Watch), ctx, types, events)
 }
diff --git a/pkg/lib/backoff/mock_backoff.go b/pkg/lib/backoff/mock_backoff.go
index e29882f0b1..784b788f65 100644
--- a/pkg/lib/backoff/mock_backoff.go
+++ b/pkg/lib/backoff/mock_backoff.go
@@ -1,6 +1,10 @@
 // Code generated by MockGen. DO NOT EDIT.
 // Source: types.go
-
+//
+// Generated by this command:
+//
+//	mockgen --source types.go --destination mock_backoff.go --package backoff
+//
 // Package backoff is a generated GoMock package.
 package backoff
 
@@ -41,7 +45,7 @@ func (m *MockBackoff) Backoff(ctx context.Context, attempts int) {
 }
 
 // Backoff indicates an expected call of Backoff.
-func (mr *MockBackoffMockRecorder) Backoff(ctx, attempts interface{}) *gomock.Call {
+func (mr *MockBackoffMockRecorder) Backoff(ctx, attempts any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Backoff", reflect.TypeOf((*MockBackoff)(nil).Backoff), ctx, attempts)
 }
diff --git a/pkg/models/constants.go b/pkg/models/constants.go
index ca952f1e82..fac87da4cc 100644
--- a/pkg/models/constants.go
+++ b/pkg/models/constants.go
@@ -61,4 +61,9 @@ const (
 	MetaRequesterID        = "bacalhau.org/requester.id"
 	MetaRequesterPublicKey = "bacalhau.org/requester.publicKey"
 	MetaClientID           = "bacalhau.org/client.id"
+
+	// Job provenance metadata used to track the origin of a job where
+	// it may have been translated from another job.
+	MetaDerivedFrom  = "bacalhau.org/derivedFrom"
+	MetaTranslatedBy = "bacalhau.org/translatedBy"
 )
diff --git a/pkg/models/engine.go b/pkg/models/engine.go
new file mode 100644
index 0000000000..fcae6c970e
--- /dev/null
+++ b/pkg/models/engine.go
@@ -0,0 +1,5 @@
+package models
+
+func IsDefaultEngineType(kind string) bool {
+	return kind == EngineDocker || kind == EngineNoop || kind == EngineWasm
+}
diff --git a/pkg/models/task_builder.go b/pkg/models/task_builder.go
index fff7d8f8f2..4756b1eb84 100644
--- a/pkg/models/task_builder.go
+++ b/pkg/models/task_builder.go
@@ -27,6 +27,15 @@ func (b *TaskBuilder) Engine(engine *SpecConfig) *TaskBuilder {
 	return b
 }
 
+func (b *TaskBuilder) Meta(key string, value string) *TaskBuilder {
+	if b.task.Meta == nil {
+		b.task.Meta = make(map[string]string)
+	}
+
+	b.task.Meta[key] = value
+	return b
+}
+
 func (b *TaskBuilder) Publisher(publisher *SpecConfig) *TaskBuilder {
 	b.task.Publisher = publisher
 	return b
diff --git a/pkg/node/config_defaults.go b/pkg/node/config_defaults.go
index 21bab6552b..a0de20399a 100644
--- a/pkg/node/config_defaults.go
+++ b/pkg/node/config_defaults.go
@@ -54,6 +54,8 @@ var DefaultRequesterConfig = RequesterConfigParams{
 
 	S3PreSignedURLDisabled:   false,
 	S3PreSignedURLExpiration: 30 * time.Minute,
+
+	TranslationEnabled: false,
 }
 
 var TestRequesterConfig = RequesterConfigParams{
@@ -78,6 +80,8 @@ var TestRequesterConfig = RequesterConfigParams{
 	WorkerEvalDequeueBaseBackoff: 20 * time.Millisecond,
 	WorkerEvalDequeueMaxBackoff:  200 * time.Millisecond,
 
+	TranslationEnabled: false,
+
 	S3PreSignedURLDisabled:   false,
 	S3PreSignedURLExpiration: 30 * time.Minute,
 }
diff --git a/pkg/node/config_requester.go b/pkg/node/config_requester.go
index b12f36d335..ee025b0094 100644
--- a/pkg/node/config_requester.go
+++ b/pkg/node/config_requester.go
@@ -41,6 +41,9 @@ type RequesterConfigParams struct {
 	WorkerEvalDequeueBaseBackoff time.Duration
 	WorkerEvalDequeueMaxBackoff  time.Duration
 
+	// Should the orchestrator attempt to translate jobs?
+	TranslationEnabled bool
+
 	S3PreSignedURLDisabled   bool
 	S3PreSignedURLExpiration time.Duration
 }
diff --git a/pkg/node/node.go b/pkg/node/node.go
index b65d8d9f4b..31c9991cc5 100644
--- a/pkg/node/node.go
+++ b/pkg/node/node.go
@@ -5,11 +5,6 @@ import (
 	"fmt"
 	"time"
 
-	"github.com/bacalhau-project/bacalhau/pkg/config"
-	"github.com/bacalhau-project/bacalhau/pkg/models"
-	"github.com/bacalhau-project/bacalhau/pkg/publicapi"
-	"github.com/bacalhau-project/bacalhau/pkg/publicapi/endpoint/agent"
-	"github.com/bacalhau-project/bacalhau/pkg/publicapi/endpoint/shared"
 	"github.com/imdario/mergo"
 	"github.com/labstack/echo/v4"
 	libp2p_pubsub "github.com/libp2p/go-libp2p-pubsub"
@@ -18,6 +13,11 @@ import (
 	routedhost "github.com/libp2p/go-libp2p/p2p/host/routed"
 	"github.com/libp2p/go-libp2p/p2p/protocol/identify"
 
+	"github.com/bacalhau-project/bacalhau/pkg/models"
+	"github.com/bacalhau-project/bacalhau/pkg/publicapi"
+	"github.com/bacalhau-project/bacalhau/pkg/publicapi/endpoint/agent"
+	"github.com/bacalhau-project/bacalhau/pkg/publicapi/endpoint/shared"
+
 	pkgconfig "github.com/bacalhau-project/bacalhau/pkg/config"
 	"github.com/bacalhau-project/bacalhau/pkg/ipfs"
 	"github.com/bacalhau-project/bacalhau/pkg/pubsub"
@@ -60,7 +60,7 @@ type NodeConfig struct {
 	NodeInfoPublisherInterval   routing.NodeInfoPublisherIntervalConfig
 	DependencyInjector          NodeDependencyInjector
 	AllowListedLocalPaths       []string
-	WebUI                       bool
+	NodeInfoStoreTTL            time.Duration
 
 	FsRepo *repo.FsRepo
 }
@@ -181,7 +181,7 @@ func NewNode(
 
 	// node info store that is used for both discovering compute nodes, as to find addresses of other nodes for routing requests.
 	nodeInfoStore := inmemory.NewNodeInfoStore(inmemory.NodeInfoStoreParams{
-		TTL: 10 * time.Minute,
+		TTL: config.NodeInfoStoreTTL,
 	})
 	routedHost := routedhost.Wrap(config.Host, nodeInfoStore)
 
@@ -254,7 +254,6 @@ func NewNode(
 	}
 
 	if config.IsComputeNode {
-
 		storagePath := pkgconfig.GetStoragePath()
 
 		// setup compute node
@@ -346,7 +345,7 @@ func (n *Node) IsComputeNode() bool {
 }
 
 func newLibp2pPubSub(ctx context.Context, nodeConfig NodeConfig) (*libp2p_pubsub.PubSub, error) {
-	tracer, err := libp2p_pubsub.NewJSONTracer(config.GetLibp2pTracerPath())
+	tracer, err := libp2p_pubsub.NewJSONTracer(pkgconfig.GetLibp2pTracerPath())
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/node/requester.go b/pkg/node/requester.go
index 83884da4b9..13e89efcff 100644
--- a/pkg/node/requester.go
+++ b/pkg/node/requester.go
@@ -19,6 +19,7 @@ import (
 	"github.com/bacalhau-project/bacalhau/pkg/pubsub/libp2p"
 	"github.com/bacalhau-project/bacalhau/pkg/requester/pubsub/jobinfo"
 	s3helper "github.com/bacalhau-project/bacalhau/pkg/s3"
+	"github.com/bacalhau-project/bacalhau/pkg/translation"
 	"github.com/bacalhau-project/bacalhau/pkg/util"
 	libp2p_pubsub "github.com/libp2p/go-libp2p-pubsub"
 	"github.com/libp2p/go-libp2p/core/crypto"
@@ -245,6 +246,11 @@ func NewRequesterNode(
 		DefaultJobExecutionTimeout: requesterConfig.JobDefaults.ExecutionTimeout,
 	})
 
+	var translationProvider translation.TranslatorProvider
+	if requesterConfig.TranslationEnabled {
+		translationProvider = translation.NewStandardTranslatorsProvider()
+	}
+
 	endpointV2 := orchestrator.NewBaseEndpoint(&orchestrator.BaseEndpointParams{
 		ID:               host.ID().String(),
 		EvaluationBroker: evalBroker,
@@ -258,6 +264,7 @@ func NewRequesterNode(
 			transformer.RequesterInfo(host.ID().String(), marshaledPublicKey),
 			transformer.NewInlineStoragePinner(storageProviders),
 		},
+		TaskTranslator:    translationProvider,
 		ResultTransformer: resultTransformers,
 	})
 
diff --git a/pkg/orchestrator/endpoint.go b/pkg/orchestrator/endpoint.go
index 31f1782533..4bfe196cca 100644
--- a/pkg/orchestrator/endpoint.go
+++ b/pkg/orchestrator/endpoint.go
@@ -10,6 +10,7 @@ import (
 	"github.com/bacalhau-project/bacalhau/pkg/model"
 	"github.com/bacalhau-project/bacalhau/pkg/models"
 	"github.com/bacalhau-project/bacalhau/pkg/orchestrator/transformer"
+	"github.com/bacalhau-project/bacalhau/pkg/translation"
 	"github.com/google/uuid"
 	"github.com/rs/zerolog/log"
 )
@@ -21,6 +22,7 @@ type BaseEndpointParams struct {
 	EventEmitter      EventEmitter
 	ComputeProxy      compute.Endpoint
 	JobTransformer    transformer.JobTransformer
+	TaskTranslator    translation.TranslatorProvider
 	ResultTransformer transformer.ResultTransformer
 }
 
@@ -31,6 +33,7 @@ type BaseEndpoint struct {
 	eventEmitter      EventEmitter
 	computeProxy      compute.Endpoint
 	jobTransformer    transformer.JobTransformer
+	taskTranslator    translation.TranslatorProvider
 	resultTransformer transformer.ResultTransformer
 }
 
@@ -42,6 +45,7 @@ func NewBaseEndpoint(params *BaseEndpointParams) *BaseEndpoint {
 		eventEmitter:      params.EventEmitter,
 		computeProxy:      params.ComputeProxy,
 		jobTransformer:    params.JobTransformer,
+		taskTranslator:    params.TaskTranslator,
 		resultTransformer: params.ResultTransformer,
 	}
 }
@@ -60,6 +64,33 @@ func (e *BaseEndpoint) SubmitJob(ctx context.Context, request *SubmitJobRequest)
 		return nil, err
 	}
 
+	// We will only perform task translation in the orchestrator if we were provided with a provider
+	// that can give translators to perform the translation.
+	if e.taskTranslator != nil {
+		// Before we create an evaluation for the job, we want to check that none of the job's tasks
+		// need translating from a custom job type to a known job type (docker, wasm). If they do,
+		// then we will perform the translation and create the evaluation for the new job instead.
+		translatedJob, err := translation.Translate(ctx, e.taskTranslator, job)
+		if err != nil {
+			return nil, err
+		}
+
+		// If we have translated the job (i.e. at least one task was translated) then we will switch
+		// to using the translated job after we have saved it in the jobstore. This results in us
+		// sending the translated job ID to the user for tracking their job, although it will contain
+		// a reference to the job they submitted.  This may cause confusion and we may in future want
+		// to move to versioning of jobs so that we can present both to the user should they request
+		// it.
+		if translatedJob != nil {
+			translatedJob.Meta[models.MetaDerivedFrom] = job.ID
+
+			job = translatedJob
+			if err := e.store.CreateJob(ctx, *job); err != nil {
+				return nil, err
+			}
+		}
+	}
+
 	eval := &models.Evaluation{
 		ID:          uuid.NewString(),
 		JobID:       job.ID,
diff --git a/pkg/orchestrator/mocks.go b/pkg/orchestrator/mocks.go
index eb58801b1d..0c114232d8 100644
--- a/pkg/orchestrator/mocks.go
+++ b/pkg/orchestrator/mocks.go
@@ -1,6 +1,10 @@
 // Code generated by MockGen. DO NOT EDIT.
 // Source: interfaces.go
-
+//
+// Generated by this command:
+//
+//	mockgen --source interfaces.go --destination mocks.go --package orchestrator
+//
 // Package orchestrator is a generated GoMock package.
 package orchestrator
 
@@ -45,7 +49,7 @@ func (m *MockEvaluationBroker) Ack(evalID, receiptHandle string) error {
 }
 
 // Ack indicates an expected call of Ack.
-func (mr *MockEvaluationBrokerMockRecorder) Ack(evalID, receiptHandle interface{}) *gomock.Call {
+func (mr *MockEvaluationBrokerMockRecorder) Ack(evalID, receiptHandle any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Ack", reflect.TypeOf((*MockEvaluationBroker)(nil).Ack), evalID, receiptHandle)
 }
@@ -61,7 +65,7 @@ func (m *MockEvaluationBroker) Dequeue(types []string, timeout time.Duration) (*
 }
 
 // Dequeue indicates an expected call of Dequeue.
-func (mr *MockEvaluationBrokerMockRecorder) Dequeue(types, timeout interface{}) *gomock.Call {
+func (mr *MockEvaluationBrokerMockRecorder) Dequeue(types, timeout any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Dequeue", reflect.TypeOf((*MockEvaluationBroker)(nil).Dequeue), types, timeout)
 }
@@ -75,7 +79,7 @@ func (m *MockEvaluationBroker) Enqueue(evaluation *models.Evaluation) error {
 }
 
 // Enqueue indicates an expected call of Enqueue.
-func (mr *MockEvaluationBrokerMockRecorder) Enqueue(evaluation interface{}) *gomock.Call {
+func (mr *MockEvaluationBrokerMockRecorder) Enqueue(evaluation any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Enqueue", reflect.TypeOf((*MockEvaluationBroker)(nil).Enqueue), evaluation)
 }
@@ -89,7 +93,7 @@ func (m *MockEvaluationBroker) EnqueueAll(evaluation map[*models.Evaluation]stri
 }
 
 // EnqueueAll indicates an expected call of EnqueueAll.
-func (mr *MockEvaluationBrokerMockRecorder) EnqueueAll(evaluation interface{}) *gomock.Call {
+func (mr *MockEvaluationBrokerMockRecorder) EnqueueAll(evaluation any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnqueueAll", reflect.TypeOf((*MockEvaluationBroker)(nil).EnqueueAll), evaluation)
 }
@@ -104,7 +108,7 @@ func (m *MockEvaluationBroker) Inflight(evaluationID string) (string, bool) {
 }
 
 // Inflight indicates an expected call of Inflight.
-func (mr *MockEvaluationBrokerMockRecorder) Inflight(evaluationID interface{}) *gomock.Call {
+func (mr *MockEvaluationBrokerMockRecorder) Inflight(evaluationID any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Inflight", reflect.TypeOf((*MockEvaluationBroker)(nil).Inflight), evaluationID)
 }
@@ -118,7 +122,7 @@ func (m *MockEvaluationBroker) InflightExtend(evaluationID, receiptHandle string
 }
 
 // InflightExtend indicates an expected call of InflightExtend.
-func (mr *MockEvaluationBrokerMockRecorder) InflightExtend(evaluationID, receiptHandle interface{}) *gomock.Call {
+func (mr *MockEvaluationBrokerMockRecorder) InflightExtend(evaluationID, receiptHandle any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InflightExtend", reflect.TypeOf((*MockEvaluationBroker)(nil).InflightExtend), evaluationID, receiptHandle)
 }
@@ -132,7 +136,7 @@ func (m *MockEvaluationBroker) Nack(evalID, receiptHandle string) error {
 }
 
 // Nack indicates an expected call of Nack.
-func (mr *MockEvaluationBrokerMockRecorder) Nack(evalID, receiptHandle interface{}) *gomock.Call {
+func (mr *MockEvaluationBrokerMockRecorder) Nack(evalID, receiptHandle any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Nack", reflect.TypeOf((*MockEvaluationBroker)(nil).Nack), evalID, receiptHandle)
 }
@@ -169,7 +173,7 @@ func (m *MockScheduler) Process(ctx context.Context, eval *models.Evaluation) er
 }
 
 // Process indicates an expected call of Process.
-func (mr *MockSchedulerMockRecorder) Process(ctx, eval interface{}) *gomock.Call {
+func (mr *MockSchedulerMockRecorder) Process(ctx, eval any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Process", reflect.TypeOf((*MockScheduler)(nil).Process), ctx, eval)
 }
@@ -221,7 +225,7 @@ func (m *MockSchedulerProvider) Scheduler(jobType string) (Scheduler, error) {
 }
 
 // Scheduler indicates an expected call of Scheduler.
-func (mr *MockSchedulerProviderMockRecorder) Scheduler(jobType interface{}) *gomock.Call {
+func (mr *MockSchedulerProviderMockRecorder) Scheduler(jobType any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Scheduler", reflect.TypeOf((*MockSchedulerProvider)(nil).Scheduler), jobType)
 }
@@ -258,7 +262,7 @@ func (m *MockPlanner) Process(ctx context.Context, plan *models.Plan) error {
 }
 
 // Process indicates an expected call of Process.
-func (mr *MockPlannerMockRecorder) Process(ctx, plan interface{}) *gomock.Call {
+func (mr *MockPlannerMockRecorder) Process(ctx, plan any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Process", reflect.TypeOf((*MockPlanner)(nil).Process), ctx, plan)
 }
@@ -296,7 +300,7 @@ func (m *MockNodeDiscoverer) FindNodes(ctx context.Context, job models.Job) ([]m
 }
 
 // FindNodes indicates an expected call of FindNodes.
-func (mr *MockNodeDiscovererMockRecorder) FindNodes(ctx, job interface{}) *gomock.Call {
+func (mr *MockNodeDiscovererMockRecorder) FindNodes(ctx, job any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindNodes", reflect.TypeOf((*MockNodeDiscoverer)(nil).FindNodes), ctx, job)
 }
@@ -311,7 +315,7 @@ func (m *MockNodeDiscoverer) ListNodes(ctx context.Context) ([]models.NodeInfo,
 }
 
 // ListNodes indicates an expected call of ListNodes.
-func (mr *MockNodeDiscovererMockRecorder) ListNodes(ctx interface{}) *gomock.Call {
+func (mr *MockNodeDiscovererMockRecorder) ListNodes(ctx any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListNodes", reflect.TypeOf((*MockNodeDiscoverer)(nil).ListNodes), ctx)
 }
@@ -349,7 +353,7 @@ func (m *MockNodeRanker) RankNodes(ctx context.Context, job models.Job, nodes []
 }
 
 // RankNodes indicates an expected call of RankNodes.
-func (mr *MockNodeRankerMockRecorder) RankNodes(ctx, job, nodes interface{}) *gomock.Call {
+func (mr *MockNodeRankerMockRecorder) RankNodes(ctx, job, nodes any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RankNodes", reflect.TypeOf((*MockNodeRanker)(nil).RankNodes), ctx, job, nodes)
 }
@@ -387,7 +391,7 @@ func (m *MockNodeSelector) AllMatchingNodes(ctx context.Context, job *models.Job
 }
 
 // AllMatchingNodes indicates an expected call of AllMatchingNodes.
-func (mr *MockNodeSelectorMockRecorder) AllMatchingNodes(ctx, job interface{}) *gomock.Call {
+func (mr *MockNodeSelectorMockRecorder) AllMatchingNodes(ctx, job any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AllMatchingNodes", reflect.TypeOf((*MockNodeSelector)(nil).AllMatchingNodes), ctx, job)
 }
@@ -402,7 +406,7 @@ func (m *MockNodeSelector) AllNodes(ctx context.Context) ([]models.NodeInfo, err
 }
 
 // AllNodes indicates an expected call of AllNodes.
-func (mr *MockNodeSelectorMockRecorder) AllNodes(ctx interface{}) *gomock.Call {
+func (mr *MockNodeSelectorMockRecorder) AllNodes(ctx any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AllNodes", reflect.TypeOf((*MockNodeSelector)(nil).AllNodes), ctx)
 }
@@ -417,7 +421,7 @@ func (m *MockNodeSelector) TopMatchingNodes(ctx context.Context, job *models.Job
 }
 
 // TopMatchingNodes indicates an expected call of TopMatchingNodes.
-func (mr *MockNodeSelectorMockRecorder) TopMatchingNodes(ctx, job, desiredCount interface{}) *gomock.Call {
+func (mr *MockNodeSelectorMockRecorder) TopMatchingNodes(ctx, job, desiredCount any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TopMatchingNodes", reflect.TypeOf((*MockNodeSelector)(nil).TopMatchingNodes), ctx, job, desiredCount)
 }
@@ -454,7 +458,7 @@ func (m *MockRetryStrategy) ShouldRetry(ctx context.Context, request RetryReques
 }
 
 // ShouldRetry indicates an expected call of ShouldRetry.
-func (mr *MockRetryStrategyMockRecorder) ShouldRetry(ctx, request interface{}) *gomock.Call {
+func (mr *MockRetryStrategyMockRecorder) ShouldRetry(ctx, request any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ShouldRetry", reflect.TypeOf((*MockRetryStrategy)(nil).ShouldRetry), ctx, request)
 }
diff --git a/pkg/orchestrator/scheduler/batch_service_job.go b/pkg/orchestrator/scheduler/batch_service_job.go
index 86f890b485..6f353d480d 100644
--- a/pkg/orchestrator/scheduler/batch_service_job.go
+++ b/pkg/orchestrator/scheduler/batch_service_job.go
@@ -8,6 +8,7 @@ import (
 	"github.com/bacalhau-project/bacalhau/pkg/lib/math"
 	"github.com/bacalhau-project/bacalhau/pkg/models"
 	"github.com/bacalhau-project/bacalhau/pkg/orchestrator"
+	"github.com/bacalhau-project/bacalhau/pkg/util/idgen"
 	"github.com/google/uuid"
 	"github.com/rs/zerolog/log"
 )
@@ -125,7 +126,7 @@ func (b *BatchServiceJobScheduler) createMissingExecs(
 		execution := &models.Execution{
 			JobID:        job.ID,
 			Job:          job,
-			ID:           "e-" + uuid.NewString(),
+			ID:           idgen.ExecutionIDPrefix + uuid.NewString(),
 			Namespace:    job.Namespace,
 			ComputeState: models.NewExecutionState(models.ExecutionStateNew),
 			DesiredState: models.NewExecutionDesiredState(models.ExecutionDesiredStatePending),
diff --git a/pkg/orchestrator/scheduler/daemon_job.go b/pkg/orchestrator/scheduler/daemon_job.go
index 84782917ca..38ab4f8a74 100644
--- a/pkg/orchestrator/scheduler/daemon_job.go
+++ b/pkg/orchestrator/scheduler/daemon_job.go
@@ -7,6 +7,7 @@ import (
 	"github.com/bacalhau-project/bacalhau/pkg/jobstore"
 	"github.com/bacalhau-project/bacalhau/pkg/models"
 	"github.com/bacalhau-project/bacalhau/pkg/orchestrator"
+	"github.com/bacalhau-project/bacalhau/pkg/util/idgen"
 	"github.com/google/uuid"
 	"github.com/rs/zerolog/log"
 )
@@ -100,7 +101,7 @@ func (b *DaemonJobScheduler) createMissingExecs(
 		execution := &models.Execution{
 			JobID:        job.ID,
 			Job:          job,
-			ID:           "e-" + uuid.NewString(),
+			ID:           idgen.ExecutionIDPrefix + uuid.NewString(),
 			Namespace:    job.Namespace,
 			ComputeState: models.NewExecutionState(models.ExecutionStateNew),
 			DesiredState: models.NewExecutionDesiredState(models.ExecutionDesiredStateRunning),
diff --git a/pkg/orchestrator/scheduler/ops_job.go b/pkg/orchestrator/scheduler/ops_job.go
index 71f43d07a3..fcc71bfd4f 100644
--- a/pkg/orchestrator/scheduler/ops_job.go
+++ b/pkg/orchestrator/scheduler/ops_job.go
@@ -8,6 +8,7 @@ import (
 	"github.com/bacalhau-project/bacalhau/pkg/jobstore"
 	"github.com/bacalhau-project/bacalhau/pkg/models"
 	"github.com/bacalhau-project/bacalhau/pkg/orchestrator"
+	"github.com/bacalhau-project/bacalhau/pkg/util/idgen"
 	"github.com/google/uuid"
 	"github.com/rs/zerolog/log"
 )
@@ -110,7 +111,7 @@ func (b *OpsJobScheduler) createMissingExecs(
 		execution := &models.Execution{
 			JobID:        job.ID,
 			Job:          job,
-			ID:           "e-" + uuid.NewString(),
+			ID:           idgen.ExecutionIDPrefix + uuid.NewString(),
 			Namespace:    job.Namespace,
 			ComputeState: models.NewExecutionState(models.ExecutionStateNew),
 			DesiredState: models.NewExecutionDesiredState(models.ExecutionDesiredStateRunning),
diff --git a/pkg/publicapi/client/v2/errors.go b/pkg/publicapi/client/v2/errors.go
index 1476f2fd21..8dd8cff9b7 100644
--- a/pkg/publicapi/client/v2/errors.go
+++ b/pkg/publicapi/client/v2/errors.go
@@ -87,16 +87,22 @@ type unexpectedResponseErrorOption func(*UnexpectedResponseError)
 // withError allows the addition of a Go error that may have been encountered
 // while processing the response. For example, if there is an error constructing
 // the gzip reader to process a gzip-encoded response body.
+//
+//nolint:unused
 func withError(e error) unexpectedResponseErrorOption {
 	return func(u *UnexpectedResponseError) { u.err = e }
 }
 
 // withBody overwrites the Body value with the provided custom value
+//
+//nolint:unused
 func withBody(b string) unexpectedResponseErrorOption {
 	return func(u *UnexpectedResponseError) { u.body = b }
 }
 
 // withStatusText overwrites the StatusText value the provided custom value
+//
+//nolint:unused
 func withStatusText(st string) unexpectedResponseErrorOption {
 	return func(u *UnexpectedResponseError) { u.statusText = st }
 }
@@ -140,6 +146,8 @@ func fromHTTPResponse(resp *http.Response) unexpectedResponseErrorSource {
 // fromStatusCode attempts to resolve the status code to status text using
 // the resolving function provided inside of the NewUnexpectedResponseError
 // implementation.
+//
+//nolint:unused
 func fromStatusCode(sc int) unexpectedResponseErrorSource {
 	return func() *UnexpectedResponseError { return &UnexpectedResponseError{statusCode: sc} }
 }
diff --git a/pkg/publicapi/endpoint/requester/endpoints_cancel.go b/pkg/publicapi/endpoint/requester/endpoints_cancel.go
index acdd5b065b..0bf467954f 100644
--- a/pkg/publicapi/endpoint/requester/endpoints_cancel.go
+++ b/pkg/publicapi/endpoint/requester/endpoints_cancel.go
@@ -16,20 +16,20 @@ import (
 	"github.com/pkg/errors"
 )
 
-//	@ID				pkg/requester/publicapi/cancel
-//	@Summary		Cancels the job with the job-id specified in the body payload.
-//	@Description	Cancels a job specified by `id` as long as that job belongs to `client_id`.
-//	@Description	Returns the current jobstate after the cancel request has been processed.
-//	@Tags			Job
-//	@Accept			json
-//	@Produce		json
-//	@Param			CancelRequest	body		legacymodels.CancelRequest	true	" "
-//	@Success		200				{object}	legacymodels.CancelResponse
-//	@Failure		400				{object}	string
-//	@Failure		401				{object}	string
-//	@Failure		403				{object}	string
-//	@Failure		500				{object}	string
-//	@Router			/api/v1/requester/cancel [post]
+// @ID				pkg/requester/publicapi/cancel
+// @Summary		Cancels the job with the job-id specified in the body payload.
+// @Description	Cancels a job specified by `id` as long as that job belongs to `client_id`.
+// @Description	Returns the current jobstate after the cancel request has been processed.
+// @Tags			Job
+// @Accept			json
+// @Produce		json
+// @Param			CancelRequest	body		legacymodels.CancelRequest	true	" "
+// @Success		200				{object}	legacymodels.CancelResponse
+// @Failure		400				{object}	string
+// @Failure		401				{object}	string
+// @Failure		403				{object}	string
+// @Failure		500				{object}	string
+// @Router			/api/v1/requester/cancel [post]
 func (s *Endpoint) cancel(c echo.Context) error {
 	ctx := c.Request().Context()
 	jobCancelPayload, err := signatures.UnmarshalSigned[model.JobCancelPayload](ctx, c.Request().Body)
diff --git a/pkg/publicapi/endpoint/requester/endpoints_events.go b/pkg/publicapi/endpoint/requester/endpoints_events.go
index 8cda91c9d5..00c92d945c 100644
--- a/pkg/publicapi/endpoint/requester/endpoints_events.go
+++ b/pkg/publicapi/endpoint/requester/endpoints_events.go
@@ -10,17 +10,17 @@ import (
 	"github.com/labstack/echo/v4"
 )
 
-//	@ID				pkg/requester/publicapi/events
-//	@Summary		Returns the events related to the job-id passed in the body payload. Useful for troubleshooting.
-//	@Description	Events (e.g. Created, Bid, BidAccepted, ..., ResultsAccepted, ResultsPublished) are useful to track the progress of a job.
-//	@Tags			Job
-//	@Accept			json
-//	@Produce		json
-//	@Param			EventsRequest	body		legacymodels.EventsRequest	true	"Request must specify a `client_id`. To retrieve your `client_id`, you can do the following: (1) submit a dummy job to Bacalhau (or use one you created before), (2) run `bacalhau describe <job-id>` and fetch the `ClientID` field."
-//	@Success		200				{object}	legacymodels.EventsResponse
-//	@Failure		400				{object}	string
-//	@Failure		500				{object}	string
-//	@Router			/api/v1/requester/events [post]
+// @ID				pkg/requester/publicapi/events
+// @Summary		Returns the events related to the job-id passed in the body payload. Useful for troubleshooting.
+// @Description	Events (e.g. Created, Bid, BidAccepted, ..., ResultsAccepted, ResultsPublished) are useful to track the progress of a job.
+// @Tags			Job
+// @Accept			json
+// @Produce		json
+// @Param			EventsRequest	body		legacymodels.EventsRequest	true	"Request must specify a `client_id`. To retrieve your `client_id`, you can do the following: (1) submit a dummy job to Bacalhau (or use one you created before), (2) run `bacalhau describe <job-id>` and fetch the `ClientID` field."
+// @Success		200				{object}	legacymodels.EventsResponse
+// @Failure		400				{object}	string
+// @Failure		500				{object}	string
+// @Router			/api/v1/requester/events [post]
 //
 //nolint:lll
 //nolint:dupl
diff --git a/pkg/publicapi/endpoint/requester/endpoints_list.go b/pkg/publicapi/endpoint/requester/endpoints_list.go
index 93691a78f1..67324a5f04 100644
--- a/pkg/publicapi/endpoint/requester/endpoints_list.go
+++ b/pkg/publicapi/endpoint/requester/endpoints_list.go
@@ -13,19 +13,19 @@ import (
 	"github.com/rs/zerolog/log"
 )
 
-//	@ID				pkg/requester/publicapi/list
-//	@Summary		Simply lists jobs.
-//	@Description	Returns the first (sorted) #`max_jobs` jobs that belong to the `client_id` passed in the body payload (by default).
-//	@Description	If `return_all` is set to true, it returns all jobs on the Bacalhau network.
-//	@Description	If `id` is set, it returns only the job with that ID.
-//	@Tags			Job
-//	@Accept			json
-//	@Produce		json
-//	@Param			ListRequest	body		legacymodels.ListRequest	true	"Set `return_all` to `true` to return all jobs on the network (may degrade performance, use with care!)."
-//	@Success		200			{object}	legacymodels.ListResponse
-//	@Failure		400			{object}	string
-//	@Failure		500			{object}	string
-//	@Router			/api/v1/requester/list [post]
+// @ID				pkg/requester/publicapi/list
+// @Summary		Simply lists jobs.
+// @Description	Returns the first (sorted) #`max_jobs` jobs that belong to the `client_id` passed in the body payload (by default).
+// @Description	If `return_all` is set to true, it returns all jobs on the Bacalhau network.
+// @Description	If `id` is set, it returns only the job with that ID.
+// @Tags			Job
+// @Accept			json
+// @Produce		json
+// @Param			ListRequest	body		legacymodels.ListRequest	true	"Set `return_all` to `true` to return all jobs on the network (may degrade performance, use with care!)."
+// @Success		200			{object}	legacymodels.ListResponse
+// @Failure		400			{object}	string
+// @Failure		500			{object}	string
+// @Router			/api/v1/requester/list [post]
 //
 //nolint:lll
 func (s *Endpoint) list(c echo.Context) error {
diff --git a/pkg/publicapi/endpoint/requester/endpoints_logs.go b/pkg/publicapi/endpoint/requester/endpoints_logs.go
index 18ac061b3c..83d028de58 100644
--- a/pkg/publicapi/endpoint/requester/endpoints_logs.go
+++ b/pkg/publicapi/endpoint/requester/endpoints_logs.go
@@ -26,20 +26,20 @@ type Msg struct {
 	ErrorMessage string
 }
 
-//	@ID				pkg/requester/publicapi/logs
-//	@Summary		Displays the logs for a current job/execution
-//	@Description	Shows the output from the job specified by `id` as long as that job belongs to `client_id`.
-//	@Description	The output will be continuous until either, the client disconnects or the execution completes.
-//	@Tags			Job
-//	@Accept			json
-//	@Produce		json
-//	@Param			LogRequest	body		legacymodels.LogRequest	true	" "
-//	@Success		200			{object}	string
-//	@Failure		400			{object}	string
-//	@Failure		401			{object}	string
-//	@Failure		403			{object}	string
-//	@Failure		500			{object}	string
-//	@Router			/api/v1/requester/logs [post]
+// @ID				pkg/requester/publicapi/logs
+// @Summary		Displays the logs for a current job/execution
+// @Description	Shows the output from the job specified by `id` as long as that job belongs to `client_id`.
+// @Description	The output will be continuous until either, the client disconnects or the execution completes.
+// @Tags			Job
+// @Accept			json
+// @Produce		json
+// @Param			LogRequest	body		legacymodels.LogRequest	true	" "
+// @Success		200			{object}	string
+// @Failure		400			{object}	string
+// @Failure		401			{object}	string
+// @Failure		403			{object}	string
+// @Failure		500			{object}	string
+// @Router			/api/v1/requester/logs [post]
 //
 //nolint:funlen,gocyclo
 func (s *Endpoint) logs(c echo.Context) error {
diff --git a/pkg/publicapi/endpoint/requester/endpoints_result.go b/pkg/publicapi/endpoint/requester/endpoints_result.go
index a2e52a7c36..392907b8a6 100644
--- a/pkg/publicapi/endpoint/requester/endpoints_result.go
+++ b/pkg/publicapi/endpoint/requester/endpoints_result.go
@@ -13,16 +13,16 @@ import (
 	oteltrace "go.opentelemetry.io/otel/trace"
 )
 
-//	@ID			pkg/requester/publicapi/results
-//	@Summary	Returns the results of the job-id specified in the body payload.
-//	@Tags		Job
-//	@Accept		json
-//	@Produce	json
-//	@Param		StateRequest	body		legacymodels.StateRequest	true	" "
-//	@Success	200				{object}	legacymodels.ResultsResponse
-//	@Failure	400				{object}	string
-//	@Failure	500				{object}	string
-//	@Router		/api/v1/requester/results [post]
+// @ID			pkg/requester/publicapi/results
+// @Summary	Returns the results of the job-id specified in the body payload.
+// @Tags		Job
+// @Accept		json
+// @Produce	json
+// @Param		StateRequest	body		legacymodels.StateRequest	true	" "
+// @Success	200				{object}	legacymodels.ResultsResponse
+// @Failure	400				{object}	string
+// @Failure	500				{object}	string
+// @Router		/api/v1/requester/results [post]
 func (s *Endpoint) results(c echo.Context) error {
 	var stateReq legacymodels.StateRequest
 	if err := c.Bind(&stateReq); err != nil {
diff --git a/pkg/publicapi/endpoint/requester/endpoints_submit.go b/pkg/publicapi/endpoint/requester/endpoints_submit.go
index d57b12ec0c..709a786777 100644
--- a/pkg/publicapi/endpoint/requester/endpoints_submit.go
+++ b/pkg/publicapi/endpoint/requester/endpoints_submit.go
@@ -15,16 +15,16 @@ import (
 	oteltrace "go.opentelemetry.io/otel/trace"
 )
 
-//	@ID			pkg/requester/publicapi/submit
-//	@Summary	Submits a new job to the network.
-//	@Tags		Job
-//	@Accept		json
-//	@Produce	json
-//	@Param		SubmitRequest	body		legacymodels.SubmitRequest	true	" "
-//	@Success	200				{object}	legacymodels.SubmitResponse
-//	@Failure	400				{object}	string
-//	@Failure	500				{object}	string
-//	@Router		/api/v1/requester/submit [post]
+// @ID			pkg/requester/publicapi/submit
+// @Summary	Submits a new job to the network.
+// @Tags		Job
+// @Accept		json
+// @Produce	json
+// @Param		SubmitRequest	body		legacymodels.SubmitRequest	true	" "
+// @Success	200				{object}	legacymodels.SubmitResponse
+// @Failure	400				{object}	string
+// @Failure	500				{object}	string
+// @Router		/api/v1/requester/submit [post]
 func (s *Endpoint) submit(c echo.Context) error {
 	ctx := c.Request().Context()
 
diff --git a/pkg/publicapi/endpoint/shared/endpoint.go b/pkg/publicapi/endpoint/shared/endpoint.go
index 399cbeb859..46f8bbd5e2 100644
--- a/pkg/publicapi/endpoint/shared/endpoint.go
+++ b/pkg/publicapi/endpoint/shared/endpoint.go
@@ -64,15 +64,15 @@ func (e *Endpoint) id(c echo.Context) error {
 	return c.String(http.StatusOK, e.nodeID)
 }
 
-//	@ID				peers
-//	@Summary		Returns the peers connected to the host via the transport layer.
-//	@Description	As described in the [architecture docs](https://docs.bacalhau.org/about-bacalhau/architecture),
-//	@Description	each node is connected to a number of peer nodes.
-//	@Tags			Utils
-//	@Produce		json
-//	@Success		200	{object}	[]peer.AddrInfo
-//	@Failure		500	{object}	string
-//	@Router			/api/v1/peers [get]
+// @ID				peers
+// @Summary		Returns the peers connected to the host via the transport layer.
+// @Description	As described in the [architecture docs](https://docs.bacalhau.org/about-bacalhau/architecture),
+// @Description	each node is connected to a number of peer nodes.
+// @Tags			Utils
+// @Produce		json
+// @Success		200	{object}	[]peer.AddrInfo
+// @Failure		500	{object}	string
+// @Router			/api/v1/peers [get]
 func (e *Endpoint) peers(c echo.Context) error {
 	var peerInfos []peer.AddrInfo
 	for _, p := range e.peerStore.Peers() {
diff --git a/pkg/repo/util.go b/pkg/repo/util.go
index 2d28fac0b6..1648d0d420 100644
--- a/pkg/repo/util.go
+++ b/pkg/repo/util.go
@@ -54,7 +54,7 @@ func validateRepoConfig(cfg types.BacalhauConfig) error {
 	if exists, err := fileExists(cfg.Node.ExecutorPluginPath); err != nil {
 		return err
 	} else if !exists {
-		return fmt.Errorf("executor plugin path does not exist at: %q", cfg.User.Libp2pKeyPath)
+		return fmt.Errorf("executor plugin path does not exist at: %q", cfg.Node.ExecutorPluginPath)
 	}
 
 	if exists, err := fileExists(cfg.Node.ComputeStoragePath); err != nil {
diff --git a/pkg/requester/mocks.go b/pkg/requester/mocks.go
index b7280afb47..74ef786aa6 100644
--- a/pkg/requester/mocks.go
+++ b/pkg/requester/mocks.go
@@ -1,6 +1,10 @@
 // Code generated by MockGen. DO NOT EDIT.
 // Source: types.go
-
+//
+// Generated by this command:
+//
+//	mockgen --source types.go --destination mocks.go --package requester
+//
 // Package requester is a generated GoMock package.
 package requester
 
@@ -45,7 +49,7 @@ func (m *MockEndpoint) CancelJob(arg0 context.Context, arg1 CancelJobRequest) (C
 }
 
 // CancelJob indicates an expected call of CancelJob.
-func (mr *MockEndpointMockRecorder) CancelJob(arg0, arg1 interface{}) *gomock.Call {
+func (mr *MockEndpointMockRecorder) CancelJob(arg0, arg1 any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CancelJob", reflect.TypeOf((*MockEndpoint)(nil).CancelJob), arg0, arg1)
 }
@@ -60,7 +64,7 @@ func (m *MockEndpoint) ReadLogs(arg0 context.Context, arg1 ReadLogsRequest) (Rea
 }
 
 // ReadLogs indicates an expected call of ReadLogs.
-func (mr *MockEndpointMockRecorder) ReadLogs(arg0, arg1 interface{}) *gomock.Call {
+func (mr *MockEndpointMockRecorder) ReadLogs(arg0, arg1 any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadLogs", reflect.TypeOf((*MockEndpoint)(nil).ReadLogs), arg0, arg1)
 }
@@ -75,7 +79,7 @@ func (m *MockEndpoint) SubmitJob(arg0 context.Context, arg1 model.JobCreatePaylo
 }
 
 // SubmitJob indicates an expected call of SubmitJob.
-func (mr *MockEndpointMockRecorder) SubmitJob(arg0, arg1 interface{}) *gomock.Call {
+func (mr *MockEndpointMockRecorder) SubmitJob(arg0, arg1 any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubmitJob", reflect.TypeOf((*MockEndpoint)(nil).SubmitJob), arg0, arg1)
 }
diff --git a/pkg/s3/client.go b/pkg/s3/client.go
index a99efec1d4..385eb379eb 100644
--- a/pkg/s3/client.go
+++ b/pkg/s3/client.go
@@ -92,6 +92,7 @@ func (s *ClientProvider) GetClient(endpoint, region string) *ClientWrapper {
 		s3Config.Region = region
 	}
 	if endpoint != "" {
+		//nolint:staticcheck // SA1019: aws.EndpointResolverWithOptionsFunc is deprecated: Use aws.EndpointResolverFunc instead
 		s3Config.EndpointResolverWithOptions =
 			aws.EndpointResolverWithOptionsFunc(func(service, resolvedRegion string, options ...any) (aws.Endpoint, error) {
 				if region != "" {
diff --git a/pkg/storage/copy/copy.go b/pkg/storage/copy/copy.go
index 59640e361a..4fdfbeac1a 100644
--- a/pkg/storage/copy/copy.go
+++ b/pkg/storage/copy/copy.go
@@ -37,11 +37,14 @@ func CopyOversize(
 	specs []*models.InputSource,
 	srcType, dstType string,
 	maxSingle, maxTotal datasize.ByteSize,
-) (modified bool, err error) {
+) (bool, error) {
+	var modified bool
+	var err error
+
 	srcStorage, err := provider.Get(ctx, srcType)
 	if err != nil {
 		err = errors.Wrapf(err, "failed to get %s storage provider", srcType)
-		return
+		return false, err
 	}
 
 	specsizes := make([]specSize, 0, len(specs))
@@ -53,7 +56,7 @@ func CopyOversize(
 		size, rerr := srcStorage.GetVolumeSize(ctx, *spec)
 		if rerr != nil {
 			err = errors.Wrapf(rerr, "failed to read spec %v", spec)
-			return
+			return modified, err
 		}
 		specsizes = append(specsizes, specSize{artifact: spec, size: datasize.ByteSize(size)})
 	}
diff --git a/pkg/storage/ipfs/storage.go b/pkg/storage/ipfs/storage.go
index d05f1b6cce..5f68a7d0d4 100644
--- a/pkg/storage/ipfs/storage.go
+++ b/pkg/storage/ipfs/storage.go
@@ -2,6 +2,7 @@ package ipfs
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"os"
 	"path/filepath"
@@ -48,7 +49,8 @@ func (s *StorageProvider) HasStorageLocally(ctx context.Context, volume models.I
 
 func (s *StorageProvider) GetVolumeSize(ctx context.Context, volume models.InputSource) (uint64, error) {
 	// we wrap this in a timeout because if the CID is not present on the network this seems to hang
-	ctx, cancel := context.WithTimeout(ctx, config.GetVolumeSizeRequestTimeout())
+	timeoutDuration := config.GetVolumeSizeRequestTimeout()
+	ctx, cancel := context.WithTimeout(ctx, timeoutDuration)
 	defer cancel()
 
 	source, err := DecodeSpec(volume.Source)
@@ -56,10 +58,23 @@ func (s *StorageProvider) GetVolumeSize(ctx context.Context, volume models.Input
 		return 0, err
 	}
 
-	return s.ipfsClient.GetCidSize(ctx, source.CID)
+	size, err := s.ipfsClient.GetCidSize(ctx, source.CID)
+	if err != nil {
+		// we failed to find the content before the context timeout
+		if errors.Is(err, context.DeadlineExceeded) {
+			return 0, fmt.Errorf("IPFS storage provider was unable to retrieve content %q before timeout %s: %w",
+				source.CID,
+				timeoutDuration, err)
+		}
+		return 0, fmt.Errorf("IPFS storage provider was unable to retrieve content %q: %w", source.CID, err)
+	}
+	return size, nil
 }
 
-func (s *StorageProvider) PrepareStorage(ctx context.Context, storageDirectory string, storageSpec models.InputSource) (storage.StorageVolume, error) {
+func (s *StorageProvider) PrepareStorage(
+	ctx context.Context,
+	storageDirectory string,
+	storageSpec models.InputSource) (storage.StorageVolume, error) {
 	source, err := DecodeSpec(storageSpec.Source)
 	if err != nil {
 		return storage.StorageVolume{}, err
diff --git a/pkg/storage/noop/noop.go b/pkg/storage/noop/noop.go
index 95f265093b..4125b829f9 100644
--- a/pkg/storage/noop/noop.go
+++ b/pkg/storage/noop/noop.go
@@ -10,8 +10,10 @@ import (
 type StorageHandlerIsInstalled func(ctx context.Context) (bool, error)
 type StorageHandlerHasStorageLocally func(ctx context.Context, volume models.InputSource) (bool, error)
 type StorageHandlerGetVolumeSize func(ctx context.Context, volume models.InputSource) (uint64, error)
-type StorageHandlerPrepareStorage func(ctx context.Context, storageDir string, storageSpec models.InputSource) (storage.StorageVolume, error)
-type StorageHandlerCleanupStorage func(ctx context.Context, storageSpec models.InputSource, volume storage.StorageVolume) error
+type StorageHandlerPrepareStorage func(
+	ctx context.Context, storageDir string, storageSpec models.InputSource) (storage.StorageVolume, error)
+type StorageHandlerCleanupStorage func(
+	ctx context.Context, storageSpec models.InputSource, volume storage.StorageVolume) error
 type StorageHandlerUpload func(ctx context.Context, localPath string) (models.SpecConfig, error)
 
 type StorageConfigExternalHooks struct {
@@ -75,7 +77,10 @@ func (s *NoopStorage) GetVolumeSize(ctx context.Context, volume models.InputSour
 	return 0, nil
 }
 
-func (s *NoopStorage) PrepareStorage(ctx context.Context, storageDir string, storageSpec models.InputSource) (storage.StorageVolume, error) {
+func (s *NoopStorage) PrepareStorage(
+	ctx context.Context,
+	storageDir string,
+	storageSpec models.InputSource) (storage.StorageVolume, error) {
 	if s.Config.ExternalHooks.PrepareStorage != nil {
 		handler := s.Config.ExternalHooks.PrepareStorage
 		return handler(ctx, storageDir, storageSpec)
diff --git a/pkg/storage/repo/storage.go b/pkg/storage/repo/storage.go
index 343f51ca91..f21835089b 100644
--- a/pkg/storage/repo/storage.go
+++ b/pkg/storage/repo/storage.go
@@ -58,7 +58,10 @@ func (sp *StorageProvider) GetVolumeSize(context.Context, models.InputSource) (u
 }
 
 //nolint:gocyclo
-func (sp *StorageProvider) PrepareStorage(ctx context.Context, storageDirectory string, storageSpec models.InputSource) (storage.StorageVolume, error) {
+func (sp *StorageProvider) PrepareStorage(
+	ctx context.Context,
+	storageDirectory string,
+	storageSpec models.InputSource) (storage.StorageVolume, error) {
 	_, span := system.GetTracer().Start(ctx, "pkg/storage/repo/repo.PrepareStorage")
 	defer span.End()
 
diff --git a/pkg/storage/s3/storage.go b/pkg/storage/s3/storage.go
index 653fc50308..fcb68172ee 100644
--- a/pkg/storage/s3/storage.go
+++ b/pkg/storage/s3/storage.go
@@ -88,7 +88,10 @@ func (s *StorageProvider) GetVolumeSize(ctx context.Context, volume models.Input
 	return size, nil
 }
 
-func (s *StorageProvider) PrepareStorage(ctx context.Context, storageDirectory string, storageSpec models.InputSource) (storage.StorageVolume, error) {
+func (s *StorageProvider) PrepareStorage(
+	ctx context.Context,
+	storageDirectory string,
+	storageSpec models.InputSource) (storage.StorageVolume, error) {
 	source, err := s3helper.DecodeSourceSpec(storageSpec.Source)
 	if err != nil {
 		return storage.StorageVolume{}, err
diff --git a/pkg/storage/tracing/tracing.go b/pkg/storage/tracing/tracing.go
index 225b3f019f..b66c05fd1e 100644
--- a/pkg/storage/tracing/tracing.go
+++ b/pkg/storage/tracing/tracing.go
@@ -43,7 +43,10 @@ func (t *tracingStorage) GetVolumeSize(ctx context.Context, spec models.InputSou
 	return t.delegate.GetVolumeSize(ctx, spec)
 }
 
-func (t *tracingStorage) PrepareStorage(ctx context.Context, storageDirectory string, spec models.InputSource) (storage.StorageVolume, error) {
+func (t *tracingStorage) PrepareStorage(
+	ctx context.Context,
+	storageDirectory string,
+	spec models.InputSource) (storage.StorageVolume, error) {
 	ctx, span := system.NewSpan(ctx, system.GetTracer(), fmt.Sprintf("%s.PrepareStorage", t.name))
 	defer span.End()
 
diff --git a/pkg/storage/url/urldownload/storage.go b/pkg/storage/url/urldownload/storage.go
index a95764963a..577200629d 100644
--- a/pkg/storage/url/urldownload/storage.go
+++ b/pkg/storage/url/urldownload/storage.go
@@ -81,7 +81,10 @@ func (sp *StorageProvider) GetVolumeSize(context.Context, models.InputSource) (u
 }
 
 // PrepareStorage will download the file from the URL
-func (sp *StorageProvider) PrepareStorage(ctx context.Context, storageDirectory string, storageSpec models.InputSource) (storage.StorageVolume, error) {
+func (sp *StorageProvider) PrepareStorage(
+	ctx context.Context,
+	storageDirectory string,
+	storageSpec models.InputSource) (storage.StorageVolume, error) {
 	source, err := DecodeSpec(storageSpec.Source)
 	if err != nil {
 		return storage.StorageVolume{}, err
diff --git a/pkg/translation/translation.go b/pkg/translation/translation.go
new file mode 100644
index 0000000000..a8ff2fd611
--- /dev/null
+++ b/pkg/translation/translation.go
@@ -0,0 +1,118 @@
+// Package translation provides interfaces for translating from a Job to a
+// different Job. This allow us to accept more job types than we have
+// executors as we translate from the abstract type to the concrete executor.
+//
+// When presented with a Job, this package iterates through the tasks
+// belonging to the job to determine whether any of the tasks have an
+// Engine type that is not one of the core executors (docker or wasm).
+// If it does not, then it returns immediately.
+//
+// For the discovered tasks, the TranslatorProvider is asked to provide an
+// implementation of the Translator interface based on the task's engine type.
+// The newly obtained Translator processes the task and returns a new task
+// with a known engine type (docker or wasm). Depending on where the
+// translation occurs, extra work might result in the generation of a derived
+// job.
+
+package translation
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/bacalhau-project/bacalhau/pkg/lib/provider"
+	"github.com/bacalhau-project/bacalhau/pkg/models"
+	"github.com/bacalhau-project/bacalhau/pkg/translation/translators"
+	"github.com/bacalhau-project/bacalhau/pkg/util/idgen"
+	"github.com/hashicorp/go-multierror"
+)
+
+// Translator defines what functions are required for a component that
+// is able to translate from one job to another. It is important that
+// implementors ensure that their implementation is reentrant - which
+// means it should not use any mutable state after initialisation.
+type Translator interface {
+	provider.Providable
+
+	Translate(*models.Task) (*models.Task, error)
+}
+
+// TranslatorProvider is an alias for `provider.Provider[Translator]`
+type TranslatorProvider interface {
+	provider.Provider[Translator]
+}
+
+// NewStandardTranslatorsProvider returns a TranslatorProvider which maps names
+// to implementations of the Translator interface
+func NewStandardTranslatorsProvider() TranslatorProvider {
+	return provider.NewMappedProvider(map[string]Translator{
+		"python": &translators.PythonTranslator{},
+		"duckdb": &translators.DuckDBTranslator{},
+	})
+}
+
+// Translate attempts to translate from one job to another, based on the engine type
+// of the tasks in the job. After ensuring that each of the tasks is either a default
+// (docker, wasm) or available via the provider, then a new Job is cloned from the
+// original and the individual tasks updated.
+func Translate(ctx context.Context, provider TranslatorProvider, original *models.Job) (*models.Job, error) {
+	if shouldTr, err := ShouldTranslate(ctx, provider, original.Tasks); err != nil {
+		return nil, err
+	} else {
+		// Nothing for us to do so we should return immediately
+		if !shouldTr {
+			return nil, nil
+		}
+	}
+
+	newJob := original.Copy()
+	newJob.ID = idgen.NewJobID()
+
+	errs := new(multierror.Error)
+
+	for i := range newJob.Tasks {
+		task := newJob.Tasks[i]
+		kind := task.Engine.Type
+
+		if models.IsDefaultEngineType(kind) {
+			continue // and leave this task in place
+		}
+
+		if translator, err := provider.Get(ctx, kind); err != nil {
+			errs = multierror.Append(errs, err)
+		} else {
+			t, err := translator.Translate(task)
+			if err != nil {
+				errs = multierror.Append(errs, err)
+				continue
+			}
+
+			// Copy the newly translated task over the top of the task
+			// that was copied from the original job
+			newJob.Tasks[i] = t
+		}
+	}
+
+	return newJob, errs.ErrorOrNil()
+}
+
+// ShouldTranslate works out whether we need to carry on with translation, that is
+// are there any engine types specified that are not a default engine and we know
+// how to translate.  If not, then we can exit early.
+func ShouldTranslate(ctx context.Context, provider TranslatorProvider, tasks []*models.Task) (bool, error) {
+	errs := new(multierror.Error)
+	needTranslationCount := 0
+
+	for i := range tasks {
+		kind := tasks[i].Engine.Type
+		if provider.Has(ctx, kind) {
+			needTranslationCount += 1
+		} else if kind == models.EngineDocker || kind == models.EngineWasm || kind == models.EngineNoop {
+			continue
+		} else {
+			errs = multierror.Append(errs, fmt.Errorf("unknown task type identified in translation: '%s'", kind))
+		}
+	}
+
+	return needTranslationCount > 0, errs.ErrorOrNil()
+}
diff --git a/pkg/translation/translators/duckdb.go b/pkg/translation/translators/duckdb.go
new file mode 100644
index 0000000000..fec6c9f785
--- /dev/null
+++ b/pkg/translation/translators/duckdb.go
@@ -0,0 +1,54 @@
+package translators
+
+import (
+	"context"
+
+	"github.com/bacalhau-project/bacalhau/pkg/models"
+	"github.com/bacalhau-project/bacalhau/pkg/util"
+)
+
+const DuckDBImage = "bacalhauproject/exec-duckdb:0.1"
+
+type DuckDBTranslator struct{}
+
+func (d *DuckDBTranslator) IsInstalled(context.Context) (bool, error) {
+	return true, nil
+}
+
+func (d *DuckDBTranslator) Translate(original *models.Task) (*models.Task, error) {
+	dkrSpec, err := d.dockerEngine(original.Engine)
+	if err != nil {
+		return nil, err
+	}
+
+	builder := original.
+		ToBuilder().
+		Meta(models.MetaTranslatedBy, "translators/duckdb").
+		Engine(dkrSpec)
+
+	return builder.BuildOrDie(), nil
+}
+
+func (d *DuckDBTranslator) dockerEngine(origin *models.SpecConfig) (*models.SpecConfig, error) {
+	// It'd be nice to use pkg/executor/docker/types/EngineSpec here, but it
+	// would mean adding a dependency on yet another package.
+	cmd := origin.Params["Command"].(string)
+	args, err := util.InterfaceToStringArray(origin.Params["Arguments"])
+	if err != nil {
+		return nil, err
+	}
+
+	params := []string{}
+
+	params = append(params, cmd)
+	params = append(params, args...)
+
+	spec := models.NewSpecConfig(models.EngineDocker)
+	spec.Params["Image"] = DuckDBImage
+	spec.Params["Entrypoint"] = []string{}
+	spec.Params["Parameters"] = params
+	spec.Params["EnvironmentVariables"] = []string{}
+	spec.Params["WorkingDirectory"] = ""
+
+	return spec, nil
+}
diff --git a/pkg/translation/translators/errors.go b/pkg/translation/translators/errors.go
new file mode 100644
index 0000000000..fa50464ca7
--- /dev/null
+++ b/pkg/translation/translators/errors.go
@@ -0,0 +1,7 @@
+package translators
+
+import "fmt"
+
+func ErrMissingParameters(trs string) error {
+	return fmt.Errorf("missing parameters in task for '%s' translator", trs)
+}
diff --git a/pkg/translation/translators/python.go b/pkg/translation/translators/python.go
new file mode 100644
index 0000000000..6ad4d27a52
--- /dev/null
+++ b/pkg/translation/translators/python.go
@@ -0,0 +1,103 @@
+package translators
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/bacalhau-project/bacalhau/pkg/models"
+	"github.com/bacalhau-project/bacalhau/pkg/util"
+	"golang.org/x/exp/maps"
+)
+
+// PythonPackageDomains lists all of the domains that might be needed to install
+// dependencies at runtime.
+var PythonPackageDomains = []string{
+	"pypi.python.org",
+	"pypi.org",
+	"pythonhosted.org",
+	"files.pythonhosted.org",
+	"repo.anaconda.com",
+	"repo.continuum.io",
+	"conda.anaconda.org",
+}
+
+// SupportedPythonVersions maps the python version to the docker image that
+// provides support for that version.
+var SupportedPythonVersions = map[string]string{
+	"3.11": "bacalhauproject/exec-python-3.11:0.5",
+}
+
+type PythonTranslator struct{}
+
+func (p *PythonTranslator) IsInstalled(context.Context) (bool, error) {
+	return true, nil
+}
+
+func (p *PythonTranslator) Translate(original *models.Task) (*models.Task, error) {
+	dkrSpec, err := p.dockerEngine(original.Engine)
+	if err != nil {
+		return nil, err
+	}
+
+	builder := original.
+		ToBuilder().
+		Meta(models.MetaTranslatedBy, "translators/python").
+		Engine(dkrSpec)
+
+	original.Network = &models.NetworkConfig{
+		Type:    models.NetworkHTTP,
+		Domains: PythonPackageDomains,
+	}
+
+	return builder.BuildOrDie(), nil
+}
+
+func (p *PythonTranslator) dockerEngine(origin *models.SpecConfig) (*models.SpecConfig, error) {
+	// It'd be nice to use pkg/executor/docker/types/EngineSpec here, but it
+	// would mean adding a dependency on yet another package.
+	cmd := origin.Params["Command"].(string)
+	args, err := util.InterfaceToStringArray(origin.Params["Arguments"])
+	if err != nil {
+		return nil, err
+	}
+
+	versionString := "3.11" // Default version
+	version := origin.Params["Version"]
+	if version != nil {
+		versionString = version.(string)
+	}
+
+	image, err := getImageName(versionString)
+	if err != nil {
+		return nil, err
+	}
+
+	params := []string{
+		"/build/launcher.py", "--",
+	}
+
+	params = append(params, cmd)
+	params = append(params, args...)
+
+	spec := models.NewSpecConfig(models.EngineDocker)
+	spec.Params["Image"] = image
+	spec.Params["Entrypoint"] = []string{}
+	spec.Params["Parameters"] = params
+	spec.Params["EnvironmentVariables"] = []string{}
+	spec.Params["WorkingDirectory"] = ""
+
+	return spec, nil
+}
+
+func getImageName(version string) (string, error) {
+	image, found := SupportedPythonVersions[version]
+	if !found {
+		supported := ""
+		versions := maps.Keys(SupportedPythonVersions)
+		for i := range versions {
+			supported += fmt.Sprintf("  * %s\n", versions[i])
+		}
+		return "", fmt.Errorf("unsupported python version: %s\nsupported versions are:\n%s", version, supported)
+	}
+	return image, nil
+}
diff --git a/pkg/util/conversion.go b/pkg/util/conversion.go
new file mode 100644
index 0000000000..216f30e440
--- /dev/null
+++ b/pkg/util/conversion.go
@@ -0,0 +1,30 @@
+package util
+
+import "fmt"
+
+// InterfaceToStringArray converts an interface{} that we know is a []string
+// to that []string via []interface{}. This is useful when we have a map[string]interface{}
+// and we want to get the []string{} out of it.
+func InterfaceToStringArray(source interface{}) ([]string, error) {
+	if source == nil {
+		return nil, nil
+	}
+
+	// // If it is already a string array, then return it.
+	// strArray, ok := source.([]string)
+	// if ok {
+	// 	return strArray, nil
+	// }
+
+	sourceArray, ok := source.([]interface{})
+	if !ok {
+		return nil, fmt.Errorf("expected []interface{} but got %T", source)
+	}
+
+	result := make([]string, len(sourceArray))
+	for i, v := range sourceArray {
+		result[i] = fmt.Sprint(v)
+	}
+
+	return result, nil
+}
diff --git a/webui/src/context/TableSettingsContext.tsx b/webui/src/context/TableSettingsContext.tsx
index 66efb1c56b..7520ad0c35 100644
--- a/webui/src/context/TableSettingsContext.tsx
+++ b/webui/src/context/TableSettingsContext.tsx
@@ -3,7 +3,6 @@ import React, { useState, useContext, useEffect, ReactNode } from "react";
 // Combined table settings interface
 export interface TableSettings {
   // Jobs Table
-  showJobId?: boolean;
   showJobName?: boolean;
   showCreated?: boolean;
   showProgram?: boolean;
@@ -12,7 +11,6 @@ export interface TableSettings {
   showStatus?: boolean;
   // Nodes Table
   showNodeId?: boolean;
-  showNodeName?: boolean;
   showNodeType?: boolean;
   showEnv?: boolean;
   showInputs?: boolean;
@@ -28,7 +26,6 @@ interface TableSettingsContextType {
 
 const defaultState: TableSettings = {
   // Jobs Table
-  showJobId: true,
   showJobName: true,
   showCreated: true,
   showProgram: true,
@@ -37,7 +34,6 @@ const defaultState: TableSettings = {
   showStatus: true,
   // Nodes Table
   showNodeId: true,
-  showNodeName: true,
   showNodeType: true,
   showEnv: true,
   showInputs: true,
diff --git a/webui/src/helpers/helperFunctions.ts b/webui/src/helpers/helperFunctions.ts
index 08494490ab..6535342c81 100644
--- a/webui/src/helpers/helperFunctions.ts
+++ b/webui/src/helpers/helperFunctions.ts
@@ -11,5 +11,22 @@ export function fromTimestamp(timestamp: number): Date {
 }
 
 export function getShortenedJobID(jobID: string) {
-  return jobID.split("-")[0];
+  const parts = jobID.split("-");
+  if (parts[0].length > 1) {
+    return parts[0];
+  } else {
+    return parts[0] + "-" + parts[1];
+  }
+}
+
+export function createLabelArray(label: { [key: string]: string }): string[] {
+  const labelArray: string[] = [];
+  for (const [key, value] of Object.entries(label)) {
+    if (value === "") {
+      labelArray.push(key);
+    } else {
+      labelArray.push(`${key}: ${value}`);
+    }
+  }
+  return labelArray;
 }
diff --git a/webui/src/helpers/jobInterfaces.ts b/webui/src/helpers/jobInterfaces.ts
index 38d8f60686..a77d58d3e3 100644
--- a/webui/src/helpers/jobInterfaces.ts
+++ b/webui/src/helpers/jobInterfaces.ts
@@ -77,7 +77,6 @@ export interface Resources {
 }
 
 export interface ParsedJobData {
-  id: string;
   longId: string;
   name: string;
   createdAt: Date;
diff --git a/webui/src/helpers/nodeInterfaces.ts b/webui/src/helpers/nodeInterfaces.ts
index 91fdcb4e8d..3ead173d28 100644
--- a/webui/src/helpers/nodeInterfaces.ts
+++ b/webui/src/helpers/nodeInterfaces.ts
@@ -55,7 +55,6 @@ export interface BacalhauVersion {
 
 export interface ParsedNodeData {
   id: string;
-  name: string;
   type: string;
   environment: string;
   inputs: string[];
diff --git a/webui/src/pages/JobsDashboard/JobsTable/JobsTable.module.scss b/webui/src/pages/JobsDashboard/JobsTable/JobsTable.module.scss
index 89c0eeb12d..80c4444956 100644
--- a/webui/src/pages/JobsDashboard/JobsTable/JobsTable.module.scss
+++ b/webui/src/pages/JobsDashboard/JobsTable/JobsTable.module.scss
@@ -36,7 +36,7 @@
             padding: 8px;  // This adds some padding inside the cells.
         }
 
-        .name, .program {
+        .program {
             width: 200px;
 
             @include small {
@@ -52,7 +52,7 @@
             }
         }
 
-        .jobID, .status, .action, .dateCreated, .label, .jobType {
+        .name, .status, .action, .dateCreated, .label, .jobType {
             width: 100px;
 
             @include small {
diff --git a/webui/src/pages/JobsDashboard/JobsTable/JobsTable.tsx b/webui/src/pages/JobsDashboard/JobsTable/JobsTable.tsx
index beae322e6d..d93d6ab638 100644
--- a/webui/src/pages/JobsDashboard/JobsTable/JobsTable.tsx
+++ b/webui/src/pages/JobsDashboard/JobsTable/JobsTable.tsx
@@ -8,6 +8,7 @@ import {
   capitalizeFirstLetter,
   fromTimestamp,
   getShortenedJobID,
+  createLabelArray,
 } from "../../../helpers/helperFunctions";
 import { Job, ParsedJobData } from "../../../helpers/jobInterfaces";
 import TableSettingsContext from "../../../context/TableSettingsContext";
@@ -32,12 +33,17 @@ function parseData(jobs: Job[]): ParsedJobData[] {
     if (!job.Tasks || job.Tasks.length === 0) {
       throw new Error(`Job with ID: ${job.ID} has no tasks.`);
     }
-
     const firstTask = job.Tasks[0];
     const jobType = job.Type ?? "batch";
+    const jobShortID = getShortenedJobID(job.ID);
+    const jobName = job.Name;
 
+    if (jobType === "batch") {
+      job.Name = jobShortID;
+    } else {
+      job.Name = jobName;
+    }
     return {
-      id: getShortenedJobID(job.ID),
       longId: job.ID,
       name: job.Name,
       createdAt: fromTimestamp(job.CreateTime),
@@ -50,18 +56,6 @@ function parseData(jobs: Job[]): ParsedJobData[] {
   });
 }
 
-function createLabelArray(label: { [key: string]: string }): string[] {
-  const labelArray: string[] = [];
-  for (const [key, value] of Object.entries(label)) {
-    if (value === "") {
-      labelArray.push(key);
-    } else {
-      labelArray.push(`${key}: ${value}`);
-    }
-  }
-  return labelArray;
-}
-
 const JobsTable: React.FC<TableProps> = ({ data }) => {
   const { settings } = useContext(TableSettingsContext);
   const parsedData = parseData(data);
@@ -71,8 +65,7 @@ const JobsTable: React.FC<TableProps> = ({ data }) => {
       <table>
         <thead>
           <tr>
-            {settings.showJobId && <th className={styles.jobID}>Job ID</th>}
-            {settings.showJobName && <th>Name</th>}
+            {settings.showJobName && <th className={styles.jobName}>Job</th>}
             {settings.showCreated && (
               <th className={styles.dateCreated}>Created</th>
             )}
@@ -85,9 +78,6 @@ const JobsTable: React.FC<TableProps> = ({ data }) => {
         <tbody>
           {parsedData.map((jobData, index) => (
             <tr key={index}>
-              {settings.showJobId && (
-                <td className={styles.id}>{jobData.id}</td>
-              )}
               {settings.showJobName && (
                 <td className={styles.name}>{jobData.name}</td>
               )}
@@ -107,7 +97,11 @@ const JobsTable: React.FC<TableProps> = ({ data }) => {
                 <td className={styles.jobType}>{jobData.jobType}</td>
               )}
               {settings.showLabel && (
-                <td className={styles.label}>{jobData.label}</td>
+                <td className={styles.label}>
+                  {jobData.label.map((label) => (
+                    <Label text={label} color={"grey"} />
+                  ))}
+                </td>
               )}
               {settings.showStatus && (
                 <td className={styles.status}>
diff --git a/webui/src/pages/NodesDashboard/NodesTable/NodesTable.tsx b/webui/src/pages/NodesDashboard/NodesTable/NodesTable.tsx
index a27f60760c..1bc4faee57 100644
--- a/webui/src/pages/NodesDashboard/NodesTable/NodesTable.tsx
+++ b/webui/src/pages/NodesDashboard/NodesTable/NodesTable.tsx
@@ -15,7 +15,6 @@ function parseData(nodes: Node[]): ParsedNodeData[] {
 
     return {
       id: node.PeerInfo.ID,
-      name: node.Labels.name ? node.Labels.name : node.PeerInfo.ID,
       type: node.NodeType,
       environment: node.Labels.env,
       inputs: inputs,
@@ -35,8 +34,7 @@ const NodesTable: React.FC<TableProps> = ({ data }) => {
       <table>
         <thead>
           <tr>
-            {settings.showNodeId && <th>Node ID</th>}
-            {settings.showNodeName && <th>Name</th>}
+            {settings.showNodeId && <th>Node</th>}
             {settings.showNodeType && <th>Type</th>}
             {settings.showEnv && <th>Environment</th>}
             {settings.showInputs && <th>Inputs From</th>}
@@ -51,9 +49,6 @@ const NodesTable: React.FC<TableProps> = ({ data }) => {
               {settings.showNodeId && (
                 <td className={styles.id}>{nodeData.id}</td>
               )}
-              {settings.showNodeName && (
-                <td className={styles.name}>{nodeData.name}</td>
-              )}
               {settings.showNodeType && (
                 <td className={styles.type}>{nodeData.type}</td>
               )}
diff --git a/webui/src/pages/Settings/Settings.tsx b/webui/src/pages/Settings/Settings.tsx
index de4b8ceee1..086e384aca 100644
--- a/webui/src/pages/Settings/Settings.tsx
+++ b/webui/src/pages/Settings/Settings.tsx
@@ -30,7 +30,6 @@ const Settings = () => {
   };
 
   const jobsOptions: { label: string; key: keyof TableSettings }[] = [
-    { label: "Job ID", key: "showJobId" },
     { label: "Name", key: "showJobName" },
     { label: "Created", key: "showCreated" },
     { label: "Program", key: "showProgram" },
@@ -41,7 +40,6 @@ const Settings = () => {
 
   const nodesOptions: { label: string; key: keyof TableSettings }[] = [
     { label: "Node ID", key: "showNodeId" },
-    { label: "Name", key: "showNodeName" },
     { label: "Type", key: "showNodeType" },
     { label: "Environment", key: "showEnv" },
     { label: "Inputs In", key: "showInputs" },
diff --git a/webui/webui.go b/webui/webui.go
index 9accf31008..931e9b42fb 100644
--- a/webui/webui.go
+++ b/webui/webui.go
@@ -4,6 +4,7 @@ import (
 	"bytes"
 	"context"
 	"embed"
+	"fmt"
 	"html/template"
 	"io/fs"
 	"net"
@@ -36,8 +37,8 @@ func IndexPage(host, port, base string) ([]byte, error) {
 	return output.Bytes(), err
 }
 
-func ListenAndServe(ctx context.Context, host, port, base string) error {
-	indexPage, err := IndexPage(host, port, base)
+func ListenAndServe(ctx context.Context, host, apiPort, apiPath string, listenPort int) error {
+	indexPage, err := IndexPage(host, apiPort, apiPath)
 	if err != nil {
 		return err
 	}
@@ -51,7 +52,7 @@ func ListenAndServe(ctx context.Context, host, port, base string) error {
 	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		match, err := fs.Glob(files, strings.TrimLeft(r.URL.Path, "/"))
 		if r.URL.Path == "/" || r.URL.Path == "/index.html" {
-			w.Write(indexPage)
+			_, _ = w.Write(indexPage)
 		} else if len(match) > 0 && err == nil {
 			// Serve a static file that matches the request path, if it exists
 			// We need to check this manually to enable the below behavior
@@ -62,11 +63,12 @@ func ListenAndServe(ctx context.Context, host, port, base string) error {
 			// (e.g. `/jobs`) which the user can then successfully refresh,
 			// share or bookmark. If this wasn't here, the user would have to
 			// enter the app manually at `/` every time.
-			w.Write(indexPage)
+			_, _ = w.Write(indexPage)
 		}
 	})
 
 	server := &http.Server{
+		Addr:              fmt.Sprintf(":%d", listenPort),
 		Handler:           &handler,
 		ReadTimeout:       time.Minute,
 		WriteTimeout:      time.Minute,


Test patch:
diff --git a/cmd/cli/create/create_test.go b/cmd/cli/create/create_test.go
index 4d7049dcbe..8964b55eb5 100644
--- a/cmd/cli/create/create_test.go
+++ b/cmd/cli/create/create_test.go
@@ -80,9 +80,12 @@ func (s *CreateSuite) TestCreateGenericSubmitBetter() {
 		},
 	}
 
+	// Let's do this once
+	canRunS3Test := s3helper.CanRunS3Test()
+
 	for _, tc := range tests {
 		s.Run(tc.Name, func() {
-			if tc.Fixture.RequiresS3() && !s3helper.CanRunS3Test() {
+			if tc.Fixture.RequiresS3() && !canRunS3Test {
 				// Skip the S3 tests if we have no AWS credentials installed
 				s.T().Skip("No valid AWS credentials found")
 			}
@@ -97,8 +100,6 @@ func (s *CreateSuite) TestCreateGenericSubmitBetter() {
 				"--api-port", fmt.Sprint(s.Port),
 			)
 
-			fmt.Println(tc.Fixture.Data)
-
 			require.NoError(s.T(), err, "Error submitting job")
 			testutils.GetJobFromTestOutputLegacy(ctx, s.T(), s.Client, out)
 		})
diff --git a/cmd/cli/exec/exec_test.go b/cmd/cli/exec/exec_test.go
index 65116655e4..1a983c0a22 100644
--- a/cmd/cli/exec/exec_test.go
+++ b/cmd/cli/exec/exec_test.go
@@ -43,11 +43,11 @@ var testcases []testCase = []testCase{
 	{
 		// bacalhau exec python --version=3.10 -- -c "import this"
 		name:                  "zen of python",
-		cmdLine:               []string{"python", "--version=3.10", "--", "-c", "\"import this\""},
-		expectedUnknownArgs:   []string{"--version=3.10", "-c=\"import this\""},
+		cmdLine:               []string{"python", "--version=3.10", "--", "-c", "import this"},
+		expectedUnknownArgs:   []string{"--version=3.10", "-c=import this"},
 		expectedErrMsg:        "",
 		jobCommand:            "python",
-		jobArguments:          []string{"-c", `"import this"`},
+		jobArguments:          []string{"-c", "'import this'"},
 		numInlinedAttachments: 0,
 		numTotalAttachments:   0,
 	},
@@ -91,7 +91,7 @@ var testcases []testCase = []testCase{
 		expectedUnknownArgs:   []string{},
 		expectedErrMsg:        "",
 		jobCommand:            "duckdb",
-		jobArguments:          []string{"select * from /inputs/test.csv"},
+		jobArguments:          []string{"'select * from /inputs/test.csv'"},
 		numInlinedAttachments: 0,
 		numTotalAttachments:   1,
 	},
diff --git a/cmd/cli/serve/serve_test.go b/cmd/cli/serve/serve_test.go
index a5efc4c237..a8d92b9cba 100644
--- a/cmd/cli/serve/serve_test.go
+++ b/cmd/cli/serve/serve_test.go
@@ -11,14 +11,15 @@ import (
 	"testing"
 	"time"
 
-	"github.com/bacalhau-project/bacalhau/pkg/publicapi/client"
-	clientv2 "github.com/bacalhau-project/bacalhau/pkg/publicapi/client/v2"
-	apitest "github.com/bacalhau-project/bacalhau/pkg/publicapi/test"
 	"github.com/multiformats/go-multiaddr"
 	"github.com/phayes/freeport"
 	"github.com/stretchr/testify/suite"
 	"golang.org/x/sync/errgroup"
 
+	"github.com/bacalhau-project/bacalhau/pkg/publicapi/client"
+	clientv2 "github.com/bacalhau-project/bacalhau/pkg/publicapi/client/v2"
+	apitest "github.com/bacalhau-project/bacalhau/pkg/publicapi/test"
+
 	"github.com/bacalhau-project/bacalhau/pkg/docker"
 	"github.com/bacalhau-project/bacalhau/pkg/lib/marshaller"
 
diff --git a/pkg/config/configenv/test.go b/pkg/config/configenv/test.go
index 4eb141e178..bd1f2b8770 100644
--- a/pkg/config/configenv/test.go
+++ b/pkg/config/configenv/test.go
@@ -35,6 +35,7 @@ var Testing = types.BacalhauConfig{
 		},
 		DownloadURLRequestTimeout: types.Duration(300 * time.Second),
 		VolumeSizeRequestTimeout:  types.Duration(2 * time.Minute),
+		NodeInfoStoreTTL:          types.Duration(10 * time.Minute),
 		DownloadURLRequestRetries: 3,
 		LoggingMode:               logger.LogModeDefault,
 		Type:                      []string{"requester"},
@@ -56,9 +57,17 @@ var Testing = types.BacalhauConfig{
 			SwarmAddresses: []string{
 				"/ip4/0.0.0.0/tcp/1235/p2p/QmcWJnVXJ82DKJq8ED79LADR4ZBTnwgTK7yn6JQbNVMbbC",
 			},
+			Profile:                "flatfs",
+			SwarmListenAddresses:   []string{"/ip4/0.0.0.0/tcp/0", "/ip6/::1/tcp/0"},
+			GatewayListenAddresses: []string{"/ip4/0.0.0.0/tcp/0", "/ip6/::1/tcp/0"},
+			APIListenAddresses:     []string{"/ip4/0.0.0.0/tcp/0", "/ip6/::1/tcp/0"},
 		},
 		Compute:   TestingComputeConfig,
 		Requester: TestingRequesterConfig,
+		WebUI: types.WebUIConfig{
+			Enabled: false,
+			Port:    80,
+		},
 	},
 }
 
diff --git a/pkg/downloader/ipfs/downloader_test.go b/pkg/downloader/ipfs/downloader_test.go
index 68d32e5613..6dc6a3def5 100644
--- a/pkg/downloader/ipfs/downloader_test.go
+++ b/pkg/downloader/ipfs/downloader_test.go
@@ -130,9 +130,13 @@ func TestDownloadFromPrivateSwarm(t *testing.T) {
 	config.Set(cfg)
 
 	t.Run("download failure without swarm key", func(t *testing.T) {
+		// This fails by timing out, but does so after 2minutes. We should give it
+		// 5 seconds to find the file, which is plenty given success takes ms.
+		cTimeout, cancel := context.WithTimeout(ctx, 5*time.Second)
+
 		outputDir := t.TempDir()
 		ipfsDownloader := NewIPFSDownloader(cm)
-		resultPath, err := ipfsDownloader.FetchResult(ctx, downloader.DownloadItem{
+		resultPath, err := ipfsDownloader.FetchResult(cTimeout, downloader.DownloadItem{
 			Result: &models.SpecConfig{
 				Type: models.StorageSourceIPFS,
 				Params: ipfssource.Source{
@@ -141,6 +145,8 @@ func TestDownloadFromPrivateSwarm(t *testing.T) {
 			},
 			ParentPath: outputDir,
 		})
+		cancel()
+
 		require.Error(t, err)
 		require.NoFileExists(t, filepath.Join(outputDir, cid))
 		require.Equal(t, "", resultPath)
diff --git a/pkg/ipfs/config_test.go b/pkg/ipfs/config_test.go
new file mode 100644
index 0000000000..0ce060ae45
--- /dev/null
+++ b/pkg/ipfs/config_test.go
@@ -0,0 +1,106 @@
+//go:build unit || !integration
+
+package ipfs
+
+import (
+	"testing"
+
+	"github.com/ipfs/kubo/config"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+
+	"github.com/bacalhau-project/bacalhau/pkg/config/types"
+)
+
+func TestSetSwarmListenAddresses(t *testing.T) {
+	expected := []string{
+		"/ip4/10.0.0.1/tcp/1234",
+		"/ip4/10.0.0.2/tcp/1234",
+	}
+
+	ipfscfg, err := buildIPFSConfig(types.IpfsConfig{
+		PrivateInternal:      false,
+		SwarmListenAddresses: expected,
+	})
+	require.NoError(t, err)
+
+	assert.ElementsMatch(t, ipfscfg.Addresses.Swarm, expected)
+	assert.ElementsMatch(t, ipfscfg.Addresses.Gateway, []string{"/ip4/0.0.0.0/tcp/0", "/ip6/::1/tcp/0"})
+	assert.ElementsMatch(t, ipfscfg.Addresses.API, []string{"/ip4/0.0.0.0/tcp/0", "/ip6/::1/tcp/0"})
+}
+
+func TestSetGatewayListenAddresses(t *testing.T) {
+	expected := []string{
+		"/ip4/10.0.0.1/tcp/1234",
+		"/ip4/10.0.0.2/tcp/1234",
+	}
+
+	ipfscfg, err := buildIPFSConfig(types.IpfsConfig{
+		PrivateInternal:        false,
+		GatewayListenAddresses: expected,
+	})
+	require.NoError(t, err)
+
+	assert.ElementsMatch(t, ipfscfg.Addresses.Gateway, expected)
+	assert.ElementsMatch(t, ipfscfg.Addresses.Swarm, []string{"/ip4/0.0.0.0/tcp/0", "/ip6/::1/tcp/0"})
+	assert.ElementsMatch(t, ipfscfg.Addresses.API, []string{"/ip4/0.0.0.0/tcp/0", "/ip6/::1/tcp/0"})
+}
+
+func TestSetAPIListenAddresses(t *testing.T) {
+	expected := []string{
+		"/ip4/10.0.0.1/tcp/1234",
+		"/ip4/10.0.0.2/tcp/1234",
+	}
+
+	ipfscfg, err := buildIPFSConfig(types.IpfsConfig{
+		PrivateInternal:    false,
+		APIListenAddresses: expected,
+	})
+	require.NoError(t, err)
+
+	assert.ElementsMatch(t, ipfscfg.Addresses.API, expected)
+	assert.ElementsMatch(t, ipfscfg.Addresses.Swarm, []string{"/ip4/0.0.0.0/tcp/0", "/ip6/::1/tcp/0"})
+	assert.ElementsMatch(t, ipfscfg.Addresses.Gateway, []string{"/ip4/0.0.0.0/tcp/0", "/ip6/::1/tcp/0"})
+}
+
+func TestPrivateConfig(t *testing.T) {
+	ipfscfg, err := buildIPFSConfig(types.IpfsConfig{
+		PrivateInternal: true,
+	})
+	require.NoError(t, err)
+
+	assert.ElementsMatch(t, ipfscfg.Addresses.Swarm, []string{"/ip4/127.0.0.1/tcp/0"})
+	assert.ElementsMatch(t, ipfscfg.Addresses.API, []string{"/ip4/0.0.0.0/tcp/0", "/ip6/::1/tcp/0"})
+	assert.ElementsMatch(t, ipfscfg.Addresses.Gateway, []string{"/ip4/0.0.0.0/tcp/0", "/ip6/::1/tcp/0"})
+	assert.Equal(t, config.AutoNATServiceDisabled, ipfscfg.AutoNAT.ServiceMode)
+	assert.Equal(t, config.False, ipfscfg.Swarm.EnableHolePunching)
+	assert.Equal(t, config.False, ipfscfg.Swarm.RelayClient.Enabled)
+	assert.Equal(t, config.False, ipfscfg.Swarm.RelayService.Enabled)
+	assert.Equal(t, config.False, ipfscfg.Swarm.Transports.Network.Relay)
+}
+
+func TestWithPublicSwarm(t *testing.T) {
+	expected := []string{"/ip4/127.0.0.1/tcp/34441/p2p/QmRgRvyNXV79vBMKv8tJVwwScAjeTn2F7QU3W4xSZTmEjq"}
+	ipfscfg, err := buildIPFSConfig(types.IpfsConfig{
+		SwarmAddresses: expected,
+	})
+	require.NoError(t, err)
+
+	swarmPeers, err := ParsePeersString(expected)
+	require.NoError(t, err)
+	assert.Equal(t, ipfscfg.Peering, config.Peering{Peers: swarmPeers})
+}
+
+func TestWithPrivateSwarm(t *testing.T) {
+	expected := []string{"/ip4/127.0.0.1/tcp/34441/p2p/QmRgRvyNXV79vBMKv8tJVwwScAjeTn2F7QU3W4xSZTmEjq"}
+	ipfscfg, err := buildIPFSConfig(types.IpfsConfig{
+		SwarmAddresses: expected,
+		SwarmKeyPath:   "/some/path/to/key",
+	})
+	require.NoError(t, err)
+
+	swarmPeers, err := ParsePeersString(expected)
+	require.NoError(t, err)
+	assert.Equal(t, ipfscfg.Peering, config.Peering{Peers: swarmPeers})
+	assert.Equal(t, ipfscfg.Bootstrap, expected)
+}
diff --git a/pkg/orchestrator/endpoint_test.go b/pkg/orchestrator/endpoint_test.go
index eb923e5007..9e5addf494 100644
--- a/pkg/orchestrator/endpoint_test.go
+++ b/pkg/orchestrator/endpoint_test.go
@@ -21,6 +21,7 @@ import (
 	"github.com/bacalhau-project/bacalhau/pkg/orchestrator"
 	"github.com/bacalhau-project/bacalhau/pkg/orchestrator/transformer"
 	"github.com/bacalhau-project/bacalhau/pkg/system"
+	"github.com/bacalhau-project/bacalhau/pkg/translation"
 	"github.com/stretchr/testify/suite"
 	gomock "go.uber.org/mock/gomock"
 )
@@ -82,6 +83,7 @@ func (s *EndpointSuite) TestInlinePinnerTransformInSubmit() {
 			transformer.JobFn(transformer.IDGenerator),
 			transformer.NewInlineStoragePinner(storageProviders),
 		},
+		TaskTranslator: translation.NewStandardTranslatorsProvider(),
 	})
 
 	sb := strings.Builder{}
@@ -96,11 +98,11 @@ func (s *EndpointSuite) TestInlinePinnerTransformInSubmit() {
 			Name: "testjob",
 			Type: "batch",
 			Tasks: []*models.Task{
-				&models.Task{
+				{
 					Name:   "Task 1",
 					Engine: &models.SpecConfig{Type: models.EngineNoop},
 					InputSources: []*models.InputSource{
-						&models.InputSource{
+						{
 							Source: &models.SpecConfig{
 								Type: models.StorageSourceInline,
 								Params: map[string]interface{}{
diff --git a/pkg/publicapi/test/util_test.go b/pkg/publicapi/test/util_test.go
index 4203eac0a9..2a8336a571 100644
--- a/pkg/publicapi/test/util_test.go
+++ b/pkg/publicapi/test/util_test.go
@@ -3,6 +3,7 @@ package test
 import (
 	"context"
 	"testing"
+	"time"
 
 	"github.com/phayes/freeport"
 	"github.com/stretchr/testify/require"
@@ -55,6 +56,7 @@ func setupNodeForTestWithConfig(t *testing.T, apiCfg publicapi.Config) (*node.No
 		DependencyInjector:        devstack.NewNoopNodeDependencyInjector(),
 		NodeInfoPublisherInterval: node.TestNodeInfoPublishConfig,
 		FsRepo:                    fsRepo,
+		NodeInfoStoreTTL:          10 * time.Minute,
 	}
 
 	n, err := node.NewNode(ctx, nodeConfig)
diff --git a/pkg/test/scenario/suite.go b/pkg/test/scenario/suite.go
index ec783b7d31..dc18c62efe 100644
--- a/pkg/test/scenario/suite.go
+++ b/pkg/test/scenario/suite.go
@@ -117,7 +117,9 @@ func (s *ScenarioRunner) setupStack(config *StackConfig) (*devstack.DevStack, *s
 //
 // Spin up a devstack, execute the job, check the results, and tear down the
 // devstack.
-func (s *ScenarioRunner) RunScenario(scenario Scenario) (resultsDir string) {
+func (s *ScenarioRunner) RunScenario(scenario Scenario) string {
+	var resultsDir string
+
 	spec := scenario.Spec
 	docker.EngineSpecRequiresDocker(s.T(), spec.EngineSpec)
 
@@ -162,7 +164,7 @@ func (s *ScenarioRunner) RunScenario(scenario Scenario) (resultsDir string) {
 
 	// exit if the test expects submission to fail as no further assertions can be made
 	if submitError != nil {
-		return
+		return resultsDir
 	}
 
 	s.T().Log("Waiting for job")
diff --git a/pkg/test/teststack/stack.go b/pkg/test/teststack/stack.go
index a1a070e27f..76a4267a0f 100644
--- a/pkg/test/teststack/stack.go
+++ b/pkg/test/teststack/stack.go
@@ -8,7 +8,6 @@ import (
 	"testing"
 	"time"
 
-	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 
 	"github.com/bacalhau-project/bacalhau/pkg/lib/provider"
@@ -49,7 +48,7 @@ func Setup(
 	t testing.TB,
 	opts ...devstack.ConfigOption,
 ) *devstack.DevStack {
-	//NB: if a test suite has defined a repo use it, otherwise make one.
+	// NB: if a test suite has defined a repo use it, otherwise make one.
 	repoPath := os.Getenv("BACALHAU_DIR")
 	var fsRepo *repo.FsRepo
 	if repoPath != "" {
@@ -75,9 +74,10 @@ func Setup(
 
 	// Wait for nodes to have announced their presence.
 	//nolint:gomnd
-	assert.Eventually(t, func() bool {
-		return allNodesDiscovered(t, stack)
-	}, 10*time.Second, 100*time.Millisecond, "failed to discover all nodes")
+	require.Eventually(t,
+		func() bool {
+			return allNodesDiscovered(t, stack)
+		}, 10*time.Second, 100*time.Millisecond, "failed to discover all nodes")
 
 	return stack
 }
diff --git a/pkg/translation/translation_test.go b/pkg/translation/translation_test.go
new file mode 100644
index 0000000000..d71a694efb
--- /dev/null
+++ b/pkg/translation/translation_test.go
@@ -0,0 +1,200 @@
+//go:build unit || !integration
+
+package translation_test
+
+import (
+	"context"
+	"testing"
+
+	"github.com/bacalhau-project/bacalhau/pkg/models"
+	"github.com/bacalhau-project/bacalhau/pkg/translation"
+	"github.com/stretchr/testify/suite"
+)
+
+type TranslationTestSuite struct {
+	suite.Suite
+	ctx      context.Context
+	provider translation.TranslatorProvider
+}
+
+func TestTranslationTestSuite(t *testing.T) {
+	suite.Run(t, new(TranslationTestSuite))
+}
+
+func (s *TranslationTestSuite) SetupSuite() {
+	s.ctx = context.Background()
+	s.provider = translation.NewStandardTranslatorsProvider()
+}
+
+var testcases = []struct {
+	name     string
+	spec     *models.SpecConfig
+	expected *models.SpecConfig
+}{
+	{
+		name: "python",
+		spec: &models.SpecConfig{
+			Type: "python",
+			Params: map[string]interface{}{
+				"Command":   "python",
+				"Arguments": []interface{}{"-c", "print('Hello, world!')"},
+			},
+		},
+		expected: &models.SpecConfig{
+			Type: "docker",
+			Params: map[string]interface{}{
+				"Image":      "bacalhauproject/exec-python-3.11:0.5",
+				"Entrypoint": []string{},
+				"Parameters": []string{
+					"/build/launcher.py", "--", "python", "-c", "print('Hello, world!')",
+				},
+				"EnvironmentVariables": []string{},
+				"WorkingDirectory":     "",
+			},
+		},
+	},
+	{
+		name: "python with spaces",
+		spec: &models.SpecConfig{
+			Type: "python",
+			Params: map[string]interface{}{
+				"Command":   "python",
+				"Arguments": []interface{}{"-c", `"import this"`},
+			},
+		},
+		expected: &models.SpecConfig{
+			Type: "docker",
+			Params: map[string]interface{}{
+				"Image":      "bacalhauproject/exec-python-3.11:0.5",
+				"Entrypoint": []string{},
+				"Parameters": []string{
+					"/build/launcher.py", "--", "python", "-c", `"import this"`,
+				},
+				"EnvironmentVariables": []string{},
+				"WorkingDirectory":     "",
+			},
+		},
+	},
+}
+
+func (s *TranslationTestSuite) TestTranslate() {
+	for _, tc := range testcases {
+		s.Run(tc.name, func() {
+			job := &models.Job{
+				ID: tc.name,
+				Tasks: []*models.Task{
+					{
+						Name:   "task1",
+						Engine: tc.spec,
+					},
+				},
+			}
+
+			translated, err := translation.Translate(s.ctx, s.provider, job)
+			s.Require().NoError(err)
+
+			s.Require().Equal(tc.expected, translated.Task().Engine)
+		})
+	}
+}
+
+func (s *TranslationTestSuite) TestTranslateWithInvalidEngine() {
+	job := &models.Job{
+		ID: "invalid_engine",
+		Tasks: []*models.Task{
+			{
+				Name: "task1",
+				Engine: &models.SpecConfig{
+					Type: "invalid",
+				},
+			},
+		},
+	}
+
+	_, err := translation.Translate(s.ctx, s.provider, job)
+	s.Require().Error(err)
+}
+
+func (s *TranslationTestSuite) TestTranslateWithDefaultEngine() {
+	job := &models.Job{
+		ID: "invalid_engine",
+		Tasks: []*models.Task{
+			{
+				Name: "task1",
+				Engine: &models.SpecConfig{
+					Type: "docker",
+				},
+			},
+		},
+	}
+
+	translated, err := translation.Translate(s.ctx, s.provider, job)
+	s.Require().NoError(err)
+	s.Require().Nil(translated)
+}
+
+func (s *TranslationTestSuite) TestTranslateWithMixedEngines() {
+	job := &models.Job{
+		ID: "invalid_engine",
+		Tasks: []*models.Task{
+			{
+				Name: "task1",
+				Engine: &models.SpecConfig{
+					Type: "docker",
+				},
+			},
+			{
+				Name: "task2",
+				Engine: &models.SpecConfig{
+					Type: "duckdb",
+					Params: map[string]interface{}{
+						"Command":   "duckdb",
+						"Arguments": []interface{}{"-csv", "-c", "select * from table;"},
+					},
+				},
+			},
+		},
+	}
+
+	translated, err := translation.Translate(s.ctx, s.provider, job)
+	s.Require().NoError(err)
+	s.Require().NotNil(translated)
+
+	// Before
+	s.Require().Equal("docker", job.Tasks[0].Engine.Type)
+	s.Require().Equal("duckdb", job.Tasks[1].Engine.Type)
+
+	// After
+	s.Require().Equal("docker", translated.Tasks[0].Engine.Type)
+	s.Require().Equal("docker", translated.Tasks[1].Engine.Type)
+}
+
+func (s *TranslationTestSuite) TestShouldTranslateWithDefaultEngine() {
+	tasks := []*models.Task{
+		{
+			Name: "task1",
+			Engine: &models.SpecConfig{
+				Type: "docker",
+			},
+		},
+	}
+
+	should, err := translation.ShouldTranslate(s.ctx, s.provider, tasks)
+	s.Require().NoError(err)
+	s.Require().False(should)
+}
+
+func (s *TranslationTestSuite) TestShouldTranslateWithNonDefaultEngine() {
+	tasks := []*models.Task{
+		{
+			Name: "task1",
+			Engine: &models.SpecConfig{
+				Type: "python",
+			},
+		},
+	}
+
+	should, err := translation.ShouldTranslate(s.ctx, s.provider, tasks)
+	s.Require().NoError(err)
+	s.Require().True(should)
+}
diff --git a/pkg/util/conversion_test.go b/pkg/util/conversion_test.go
new file mode 100644
index 0000000000..1a3ec4f735
--- /dev/null
+++ b/pkg/util/conversion_test.go
@@ -0,0 +1,93 @@
+//go:build unit || !integration
+
+package util_test
+
+import (
+	"testing"
+
+	"github.com/bacalhau-project/bacalhau/pkg/util"
+	"github.com/stretchr/testify/require"
+)
+
+func TestInterfaceToStringArray(t *testing.T) {
+	testcases := []struct {
+		name        string
+		source      interface{}
+		expected    []string
+		shouldError bool
+	}{
+		{
+			name:        "nil",
+			source:      nil,
+			expected:    nil,
+			shouldError: false,
+		},
+		{
+			name:        "empty",
+			source:      []interface{}{},
+			expected:    []string{},
+			shouldError: false,
+		},
+		{
+			name:        "string",
+			source:      []interface{}{"foo"},
+			expected:    []string{"foo"},
+			shouldError: false,
+		},
+		{
+			name:        "int",
+			source:      []interface{}{1},
+			expected:    []string{"1"},
+			shouldError: false,
+		},
+		{
+			name:        "float",
+			source:      []interface{}{1.1},
+			expected:    []string{"1.1"},
+			shouldError: false,
+		},
+		{
+			name:        "bool",
+			source:      []interface{}{true},
+			expected:    []string{"true"},
+			shouldError: false,
+		},
+		{
+			name:        "mixed",
+			source:      []interface{}{"foo", 1, 1.1, true},
+			expected:    []string{"foo", "1", "1.1", "true"},
+			shouldError: false,
+		},
+		{
+			name:        "map",
+			source:      map[string]interface{}{"foo": "bar"},
+			expected:    nil,
+			shouldError: true,
+		},
+		{
+			name:        "string array",
+			source:      []interface{}{"foo", "bar"},
+			expected:    []string{"foo", "bar"},
+			shouldError: false,
+		},
+		{
+			name:        "int array",
+			source:      []interface{}{1, 2},
+			expected:    []string{"1", "2"},
+			shouldError: false,
+		},
+	}
+
+	for _, tc := range testcases {
+		t.Run(tc.name, func(t *testing.T) {
+			actual, err := util.InterfaceToStringArray(tc.source)
+			if tc.shouldError {
+				require.Error(t, err)
+				return
+			}
+
+			require.NoError(t, err)
+			require.Equal(t, tc.expected, actual)
+		})
+	}
+}
diff --git a/testdata/job-noop.json b/testdata/job-noop.json
index 2348d04cdd..630966c6d7 100644
--- a/testdata/job-noop.json
+++ b/testdata/job-noop.json
@@ -9,22 +9,8 @@
             "Memory": "",
             "Disk": ""
         },
-        "Inputs": [
-            {
-                "StorageSource": "ipfs",
-                "Name": "",
-                "CID": "QmeeEB1YMrG6K8z43VdsdoYmQV46gAPQCHotZs9pwusCm9",
-                "Path": "/input"
-            }
-        ],
-        "Outputs": [
-            {
-                "StorageSource": "ipfs",
-                "Name": "output_custom",
-                "CID": "",
-                "Path": "/output_custom"
-            }
-        ],
+        "Inputs": [],
+        "Outputs": [],
         "Annotations": []
     }
 }
\ No newline at end of file