diff --git a/cmd/wfctl/main_test.go b/cmd/wfctl/main_test.go index bdba56c8..3c3b3224 100644 --- a/cmd/wfctl/main_test.go +++ b/cmd/wfctl/main_test.go @@ -245,6 +245,65 @@ func TestRunValidateValid(t *testing.T) { } } +func TestRunValidateAllowsInfraSecretPseudoModules(t *testing.T) { + dir := t.TempDir() + cfg := ` +modules: + - name: required-secrets + type: secrets.requires + config: + requires: + - key: EXTERNAL_API_TOKEN + - name: generated-secrets + type: secrets.generate + config: + generate: + - key: DATABASE_URL + type: infra_output + source: database.uri +` + path := writeTestConfig(t, dir, "infra-secrets.yaml", cfg) + if err := runValidate([]string{"--allow-no-entry-points", path}); err != nil { + t.Fatalf("expected secrets pseudo-modules to validate, got: %v", err) + } +} + +func TestRunValidateRejectsInfraSecretPseudoModulesByDefault(t *testing.T) { + dir := t.TempDir() + cfg := ` +modules: + - name: required-secrets + type: secrets.requires + config: + requires: + - key: EXTERNAL_API_TOKEN + - name: server + type: http.server + config: + address: ":8080" +pipelines: + ping: + trigger: + type: http + config: + path: /ping + method: GET + steps: + - name: log + type: step.log + config: + message: pong +` + path := writeTestConfig(t, dir, "app-with-infra-secret.yaml", cfg) + err := runValidate([]string{path}) + if err == nil { + t.Fatal("expected default validation to reject infra-only secrets pseudo-modules") + } + if !strings.Contains(err.Error(), `unknown module type "secrets.requires"`) { + t.Fatalf("expected unknown secrets.requires module type error, got: %v", err) + } +} + func TestRunValidateInvalid(t *testing.T) { dir := t.TempDir() path := writeTestConfig(t, dir, "invalid.yaml", invalidConfig) diff --git a/cmd/wfctl/validate.go b/cmd/wfctl/validate.go index adbbfb8c..f863ff7a 100644 --- a/cmd/wfctl/validate.go +++ b/cmd/wfctl/validate.go @@ -181,6 +181,16 @@ func validateFile(cfgPath string, strict, skipUnknownTypes, allowNoEntryPoints, opts = append(opts, schema.WithAllowNoEntryPoints()) } + if allowNoEntryPoints && !skipUnknownTypes { + // Infra pseudo-modules are consumed by wfctl infra plan/align/bootstrap, + // not by the runtime engine, so keep them scoped to infra-style CLI + // validation rather than adding them to the engine's core registry. + opts = append(opts, + schema.WithExtraModuleTypes("secrets.generate"), + schema.WithExtraModuleTypes("secrets.requires"), + ) + } + // Pass legacy DO module types through schema validation so the actionable // migration error fires below instead of a generic "unknown module type". for t := range legacydo.ModuleTypes {