From 17b6b9d49075de102ebcb2109069520fe0a81bd9 Mon Sep 17 00:00:00 2001 From: Jon Langevin Date: Sat, 6 Jun 2026 03:46:21 -0400 Subject: [PATCH] fix(wfctl): accept provider contract arrays --- cmd/wfctl/plugin_audit.go | 11 ++-- cmd/wfctl/plugin_validate_contract_test.go | 61 ++++++++++++++++++++++ 2 files changed, 68 insertions(+), 4 deletions(-) diff --git a/cmd/wfctl/plugin_audit.go b/cmd/wfctl/plugin_audit.go index 7e119b30e..d896d4311 100644 --- a/cmd/wfctl/plugin_audit.go +++ b/cmd/wfctl/plugin_audit.go @@ -417,8 +417,8 @@ func loadPluginContractDescriptors(repoPath string, manifest map[string]any, opt contractPath := filepath.Join(repoPath, "plugin.contracts.json") data, err := os.ReadFile(contractPath) if err == nil { - var file pluginContractDescriptorFile - if err := json.Unmarshal(data, &file); err != nil { + fileContracts, err := parsePluginContractDescriptorBytes(data) + if err != nil { findings = append(findings, planFinding{ Path: contractPath, Level: strictContractFindingLevel(opts), @@ -427,8 +427,7 @@ func loadPluginContractDescriptors(repoPath string, manifest map[string]any, opt }) return descriptors, contractPath, true, findings } - applyDescriptorSetRef(file.Contracts, file.DescriptorSetRef) - descriptors = append(descriptors, file.Contracts...) + descriptors = append(descriptors, fileContracts...) return descriptors, contractPath, true, findings } if os.IsNotExist(err) { @@ -448,6 +447,10 @@ func parsePluginContractDescriptors(raw any) ([]pluginContractDescriptor, error) if err != nil { return nil, err } + return parsePluginContractDescriptorBytes(data) +} + +func parsePluginContractDescriptorBytes(data []byte) ([]pluginContractDescriptor, error) { var descriptors []pluginContractDescriptor if err := json.Unmarshal(data, &descriptors); err == nil { return descriptors, nil diff --git a/cmd/wfctl/plugin_validate_contract_test.go b/cmd/wfctl/plugin_validate_contract_test.go index 73e324dc4..bd3932537 100644 --- a/cmd/wfctl/plugin_validate_contract_test.go +++ b/cmd/wfctl/plugin_validate_contract_test.go @@ -124,6 +124,67 @@ func TestRunPluginValidateContract_MessageContractRuntimeProfile(t *testing.T) { } } +func TestRunPluginValidateContract_ProviderDescriptorArrayIsIgnoredByStrictContracts(t *testing.T) { + dir := t.TempDir() + if err := os.WriteFile(filepath.Join(dir, "plugin.json"), []byte(`{ + "name": "workflow-plugin-provider-contracts", + "version": "0.1.0", + "author": "Workflow", + "description": "provider descriptor array compatibility", + "license": "MIT", + "type": "external", + "tier": "community", + "minEngineVersion": "0.57.4", + "capabilities": { + "stepTypes": ["step.provider_contracts"] + }, + "contracts": [ + { + "id": "product-capture.browser.v1", + "path": "contracts/product-capture-provider.json", + "schema": "schemas/product-capture-provider.schema.json" + } + ] +}`), 0644); err != nil { + t.Fatalf("write plugin manifest: %v", err) + } + if err := os.WriteFile(filepath.Join(dir, "plugin.contracts.json"), []byte(`[ + { + "id": "product-capture.browser.v1", + "path": "contracts/product-capture-provider.json", + "schema": "schemas/product-capture-provider.schema.json" + } +]`), 0644); err != nil { + t.Fatalf("write plugin contracts: %v", err) + } + if err := os.MkdirAll(filepath.Join(dir, "cmd", "plugin"), 0755); err != nil { + t.Fatalf("mkdir cmd/plugin: %v", err) + } + if err := os.WriteFile(filepath.Join(dir, "cmd", "plugin", "main.go"), []byte(`package main + +import sdk "github.com/GoCodeAlone/workflow/plugin/external/sdk" + +func main() { + version := sdk.ResolveBuildVersion("0.1.0") + _ = sdk.IaCServeOptions{BuildVersion: version} +} +`), 0644); err != nil { + t.Fatalf("write main.go: %v", err) + } + if err := os.WriteFile(filepath.Join(dir, ".goreleaser.yaml"), []byte(`builds: + - main: ./cmd/plugin + ldflags: + - -s -w -X main.Version={{.Version}} +`), 0644); err != nil { + t.Fatalf("write goreleaser config: %v", err) + } + + err := runPluginValidateContract([]string{"--for-publish", "--tag", "v0.1.0", dir}) + if err != nil { + t.Fatalf("expected provider descriptor contracts array to be ignored by strict plugin-contract validation, got %v", err) + } +} + func TestRunPluginValidateContract_MessageContractGoreleaserOnlyRuntimeSurface(t *testing.T) { dir := t.TempDir() if err := os.WriteFile(filepath.Join(dir, "plugin.json"), []byte(`{