From f98ff522bb3b3a063d368a1a4a63a3459bfd9d6c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 13 May 2026 05:56:35 +0000 Subject: [PATCH 1/3] Initial plan From 86e0ffb106ae75d70ca115d54f332e322bc2d64d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 13 May 2026 06:15:09 +0000 Subject: [PATCH 2/3] feat: distinguish legacy-host-load from typed-iac conformance evidence - Add PluginCompatibilityModeLegacyHostLoad = "legacy-host-load" constant with documentation that it is advisory-only and never satisfies IaC registry readiness - Update ValidateCompatibilityEvidence to accept legacy-host-load mode (valid to store/name) while keeping typed-iac as the only mode satisfying IaC compatibility decisions - Add manifestAdvertisesIaCProvider helper to registry_compatibility.go - Enforce in updateRegistryCompatibilityIndex: IaC provider manifests (capabilities.iacProvider.name != "") must supply typed-iac evidence; legacy-host-load is rejected with an actionable error - Update findCompatibilityEvidence comment to document that typed-iac-only filtering is intentional (legacy-host-load never matches) - Add tests: legacy-host-load valid in model, rejected for IaC manifests, ignored by resolver even with RequiredFromEngine set - Update docs/WFCTL.md to document both conformance modes Agent-Logs-Url: https://github.com/GoCodeAlone/workflow/sessions/27f85922-fcfd-4094-87aa-ca4e3fd211a8 Co-authored-by: intel352 <77607+intel352@users.noreply.github.com> --- cmd/wfctl/deploy_providers.go | 4 +- cmd/wfctl/plugin_compat_model.go | 21 ++- cmd/wfctl/plugin_compat_model_test.go | 48 +++++++ cmd/wfctl/plugin_compat_resolver.go | 3 + cmd/wfctl/plugin_compat_resolver_test.go | 31 ++++ cmd/wfctl/registry_compatibility.go | 24 ++++ cmd/wfctl/registry_compatibility_test.go | 176 +++++++++++++++++++++++ docs/WFCTL.md | 17 ++- plugin/external/sdk/iacserver_test.go | 4 +- 9 files changed, 323 insertions(+), 5 deletions(-) diff --git a/cmd/wfctl/deploy_providers.go b/cmd/wfctl/deploy_providers.go index 395c96a9..42f96eb1 100644 --- a/cmd/wfctl/deploy_providers.go +++ b/cmd/wfctl/deploy_providers.go @@ -86,8 +86,8 @@ var resolveIaCProvider = discoverAndLoadIaCProvider // double parse — and either may be empty without affecting the // other. type iacPluginManifest struct { - Name string `json:"name"` - Version string `json:"version"` + Name string `json:"name"` + Version string `json:"version"` Capabilities struct { IaCProvider struct { Name string `json:"name"` diff --git a/cmd/wfctl/plugin_compat_model.go b/cmd/wfctl/plugin_compat_model.go index 831915b6..776d6ef4 100644 --- a/cmd/wfctl/plugin_compat_model.go +++ b/cmd/wfctl/plugin_compat_model.go @@ -18,8 +18,21 @@ import ( var errInvalidRegistrySHA256 = errors.New("invalid sha256") const ( + // PluginCompatibilityModeTypedIaC is the conformance mode that proves a + // plugin registers pb.IaCProviderRequired and passes the Workflow go-plugin + // handshake. This is the only mode that satisfies typed-IaC registry + // readiness for manifests advertising iacProvider capability. PluginCompatibilityModeTypedIaC = "typed-iac" + // PluginCompatibilityModeLegacyHostLoad is the advisory-only mode for + // smoke evidence produced by legacy host-load checks (e.g. sdk.Serve + // module plugins that can load and expose metadata/contracts but have not + // migrated to sdk.ServeIaCPlugin / pb.IaCProviderRequired). This mode is + // NEVER sufficient to satisfy typed-IaC registry readiness; it must not be + // used to gate install/update decisions for plugins that advertise + // iacProvider capability. + PluginCompatibilityModeLegacyHostLoad = "legacy-host-load" + PluginCompatibilityStatusPass = "pass" PluginCompatibilityStatusFail = "fail" @@ -234,7 +247,13 @@ func ValidateCompatibilityEvidence(ev PluginCompatibilityEvidence) (PluginCompat ev.WfctlVersion = canonical } } - if ev.Mode != PluginCompatibilityModeTypedIaC { + switch ev.Mode { + case PluginCompatibilityModeTypedIaC: + // typed-iac is the only mode that satisfies IaC registry readiness. + case PluginCompatibilityModeLegacyHostLoad: + // legacy-host-load is advisory only; it must not gate IaC installs. + // See manifestAdvertisesIaCProvider / updateRegistryCompatibilityIndex. + default: return ev, fmt.Errorf("unsupported compatibility mode %q", ev.Mode) } if ev.Status != PluginCompatibilityStatusPass && ev.Status != PluginCompatibilityStatusFail { diff --git a/cmd/wfctl/plugin_compat_model_test.go b/cmd/wfctl/plugin_compat_model_test.go index bddb96a0..58fc846d 100644 --- a/cmd/wfctl/plugin_compat_model_test.go +++ b/cmd/wfctl/plugin_compat_model_test.go @@ -218,3 +218,51 @@ func TestPluginCompatEvidenceValidation(t *testing.T) { t.Fatalf("marshal normalized evidence: %v", err) } } + +// TestPluginCompatLegacyHostLoadModeIsValidButAdvisory verifies that evidence +// with mode=legacy-host-load is accepted by ValidateCompatibilityEvidence +// (so it can be stored/named) but that its mode constant is distinct from +// typed-iac. The resolver must never select legacy-host-load evidence for IaC +// readiness checks; this test confirms it round-trips correctly. +func TestPluginCompatLegacyHostLoadModeIsValidButAdvisory(t *testing.T) { + ev := PluginCompatibilityEvidence{ + Plugin: "workflow-plugin-test", + Version: "v0.1.0", + EngineVersion: "v0.51.2", + Mode: PluginCompatibilityModeLegacyHostLoad, + Status: PluginCompatibilityStatusPass, + OS: "linux", + Arch: "amd64", + } + got, err := ValidateCompatibilityEvidence(ev) + if err != nil { + t.Fatalf("ValidateCompatibilityEvidence(legacy-host-load): %v", err) + } + if got.Mode != PluginCompatibilityModeLegacyHostLoad { + t.Fatalf("mode = %q, want legacy-host-load", got.Mode) + } + if got.EvidenceDigest == "" { + t.Fatalf("legacy-host-load evidence missing digest: %#v", got) + } + // Confirm legacy-host-load and typed-iac are distinct constants. + if PluginCompatibilityModeLegacyHostLoad == PluginCompatibilityModeTypedIaC { + t.Fatal("legacy-host-load and typed-iac must be distinct mode constants") + } +} + +// TestPluginCompatLegacyHostLoadRejectsUnknownMode verifies that unknown +// mode strings are still rejected. +func TestPluginCompatLegacyHostLoadRejectsUnknownMode(t *testing.T) { + ev := PluginCompatibilityEvidence{ + Plugin: "workflow-plugin-test", + Version: "v0.1.0", + EngineVersion: "v0.51.2", + Mode: "host-smoke", + Status: PluginCompatibilityStatusPass, + OS: "linux", + Arch: "amd64", + } + if _, err := ValidateCompatibilityEvidence(ev); err == nil { + t.Fatal("ValidateCompatibilityEvidence(unknown mode) succeeded, want error") + } +} diff --git a/cmd/wfctl/plugin_compat_resolver.go b/cmd/wfctl/plugin_compat_resolver.go index ce44a568..f3ec7aae 100644 --- a/cmd/wfctl/plugin_compat_resolver.go +++ b/cmd/wfctl/plugin_compat_resolver.go @@ -211,6 +211,9 @@ func findCompatibilityEvidence(evidence []PluginCompatibilityEvidence, engine st var rangeMatch *PluginCompatibilityEvidence for i := range evidence { ev := evidence[i] + // Only typed-iac evidence satisfies registry readiness checks. + // legacy-host-load evidence is advisory only and is intentionally + // excluded here — it must never satisfy IaC compatibility decisions. if ev.Mode != PluginCompatibilityModeTypedIaC || ev.OS != goos || ev.Arch != goarch { continue } diff --git a/cmd/wfctl/plugin_compat_resolver_test.go b/cmd/wfctl/plugin_compat_resolver_test.go index 84c1b57f..797b2af9 100644 --- a/cmd/wfctl/plugin_compat_resolver_test.go +++ b/cmd/wfctl/plugin_compat_resolver_test.go @@ -185,6 +185,37 @@ func TestPluginCompatResolverPseudoLocalVersionIsAdvisory(t *testing.T) { } } +func TestPluginCompatResolverLegacyHostLoadEvidenceNeverSatisfiesIaC(t *testing.T) { + // An index containing only legacy-host-load evidence must behave as if no + // compatible evidence exists. The resolver already filters by typed-iac mode + // inside findCompatibilityEvidence; this test confirms legacy-host-load does + // not sneak through for a first-party registry with RequiredFromEngine set. + legacyEv, err := ValidateCompatibilityEvidence(PluginCompatibilityEvidence{ + Plugin: "workflow-plugin-test", + Version: "v0.2.0", + EngineVersion: "v0.51.2", + Mode: PluginCompatibilityModeLegacyHostLoad, + Status: PluginCompatibilityStatusPass, + OS: "darwin", + Arch: "arm64", + ArchiveSHA256: testArchiveSHA256, + }) + if err != nil { + t.Fatalf("ValidateCompatibilityEvidence(legacy-host-load): %v", err) + } + + idx := resolverIndex(resolverRecord("v0.2.0", legacyEv)) + idx.EvidencePolicy.RequiredFromEngine = "v0.51.0" + + _, resolveErr := ResolvePluginCompatibility(idx, nil, resolverOptions()) + if resolveErr == nil { + t.Fatal("expected missing required evidence error when only legacy-host-load evidence is present") + } + if !strings.Contains(resolveErr.Error(), "missing required compatibility evidence") { + t.Fatalf("error = %v, want missing evidence context", resolveErr) + } +} + func resolverOptions() PluginCompatResolverOptions { return PluginCompatResolverOptions{ EngineVersion: "v0.51.2", diff --git a/cmd/wfctl/registry_compatibility.go b/cmd/wfctl/registry_compatibility.go index 2b954272..33f7d8fc 100644 --- a/cmd/wfctl/registry_compatibility.go +++ b/cmd/wfctl/registry_compatibility.go @@ -122,6 +122,19 @@ func updateRegistryCompatibilityIndex(opts registryCompatibilityUpdateOptions) e if ev.Version != version { return fmt.Errorf("evidence version %s does not match --version %s", ev.Version, version) } + // IaC provider manifests require typed-iac conformance evidence only. + // legacy-host-load evidence is advisory/legacy and cannot satisfy + // typed-IaC registry readiness; reject it at index-update time so that + // the registry index never contains evidence that looks valid but would + // be silently ignored by the resolver. + if manifestAdvertisesIaCProvider(manifest) && ev.Mode != PluginCompatibilityModeTypedIaC { + return fmt.Errorf( + "plugin %q advertises iacProvider capability: only typed-iac conformance evidence satisfies IaC registry readiness; "+ + "evidence %q has mode=%q (advisory/legacy only). "+ + "Run: wfctl plugin conformance --mode typed-iac --artifact to generate valid evidence", + pluginName, path, ev.Mode, + ) + } if err := validateEvidenceArchiveMatchesDownload(ev, manifest); err != nil { return err } @@ -378,6 +391,17 @@ func compatibilityIndexIsStale(index *PluginVersionIndex, latestEngine string) b return newest == "" || semver.Compare(newest, latestEngine) < 0 } +// manifestAdvertisesIaCProvider returns true when a registry manifest declares +// an iacProvider capability with a non-empty provider name. These plugins must +// supply typed-iac conformance evidence; legacy-host-load evidence is rejected +// at index-update time for such manifests. +func manifestAdvertisesIaCProvider(m *RegistryManifest) bool { + return m != nil && + m.Capabilities != nil && + m.Capabilities.IaCProvider != nil && + m.Capabilities.IaCProvider.Name != "" +} + func atomicWriteFile(path string, data []byte, perm os.FileMode) error { dir := filepath.Dir(path) if err := os.MkdirAll(dir, 0o750); err != nil { diff --git a/cmd/wfctl/registry_compatibility_test.go b/cmd/wfctl/registry_compatibility_test.go index 593dbeb1..3a058ed3 100644 --- a/cmd/wfctl/registry_compatibility_test.go +++ b/cmd/wfctl/registry_compatibility_test.go @@ -289,6 +289,131 @@ func TestRegistryCompatibilityUpdateDerivesPassRange(t *testing.T) { } } +func TestRegistryCompatibilityUpdateRejectsLegacyHostLoadForIaCManifest(t *testing.T) { + // An IaC provider manifest must reject legacy-host-load evidence at index + // update time because legacy-host-load is advisory only and cannot satisfy + // typed-IaC registry readiness. + registryDir := prepareIaCCompatibilityRegistry(t, "workflow-plugin-testcloud", "v0.1.0", testArchiveSHA256) + evPath := writeCompatibilityEvidence(t, registryDir, PluginCompatibilityEvidence{ + Plugin: "workflow-plugin-testcloud", + Version: "v0.1.0", + EngineVersion: "v0.51.2", + Mode: PluginCompatibilityModeLegacyHostLoad, + Status: PluginCompatibilityStatusPass, + OS: "darwin", + Arch: "arm64", + ArchiveSHA256: testArchiveSHA256, + }) + + err := runPluginRegistry([]string{ + "compatibility", "update", + "--registry-dir", registryDir, + "--plugin", "workflow-plugin-testcloud", + "--version", "v0.1.0", + "--evidence", evPath, + }) + if err == nil { + t.Fatal("expected legacy-host-load rejection for IaC manifest") + } + if !strings.Contains(err.Error(), "iacProvider") { + t.Fatalf("error = %v, want iacProvider context", err) + } + if !strings.Contains(err.Error(), "typed-iac") { + t.Fatalf("error = %v, want typed-iac context", err) + } + if !strings.Contains(err.Error(), "legacy-host-load") || !strings.Contains(err.Error(), "advisory") { + t.Fatalf("error = %v, want legacy-host-load advisory context", err) + } +} + +func TestRegistryCompatibilityUpdateAcceptsTypedIaCForIaCManifest(t *testing.T) { + // An IaC provider manifest must accept typed-iac evidence — the only mode + // that satisfies IaC registry readiness. + registryDir := prepareIaCCompatibilityRegistry(t, "workflow-plugin-testcloud", "v0.1.0", testArchiveSHA256) + evPath := writeCompatibilityEvidence(t, registryDir, PluginCompatibilityEvidence{ + Plugin: "workflow-plugin-testcloud", + Version: "v0.1.0", + EngineVersion: "v0.51.2", + Mode: PluginCompatibilityModeTypedIaC, + Status: PluginCompatibilityStatusPass, + OS: "darwin", + Arch: "arm64", + ArchiveSHA256: testArchiveSHA256, + }) + + if err := runPluginRegistry([]string{ + "compatibility", "update", + "--registry-dir", registryDir, + "--plugin", "workflow-plugin-testcloud", + "--version", "v0.1.0", + "--evidence", evPath, + }); err != nil { + t.Fatalf("compatibility update with typed-iac for IaC manifest: %v", err) + } + idx := readCompatibilityIndex(t, registryDir, "workflow-plugin-testcloud") + if len(idx.Versions) != 1 || len(idx.Versions[0].Compatibility) != 1 { + t.Fatalf("unexpected index: %#v", idx) + } + if idx.Versions[0].Compatibility[0].Mode != PluginCompatibilityModeTypedIaC { + t.Fatalf("evidence mode = %q, want typed-iac", idx.Versions[0].Compatibility[0].Mode) + } +} + +func TestRegistryCompatibilityUpdateAcceptsLegacyHostLoadForNonIaCManifest(t *testing.T) { + // A non-IaC manifest (no iacProvider capability) must accept legacy-host-load + // evidence — this mode is valid for advisory/legacy checks on module plugins. + registryDir := prepareCompatibilityRegistry(t, "workflow-plugin-test", "v0.1.0", testArchiveSHA256) + evPath := writeCompatibilityEvidence(t, registryDir, PluginCompatibilityEvidence{ + Plugin: "workflow-plugin-test", + Version: "v0.1.0", + EngineVersion: "v0.51.2", + Mode: PluginCompatibilityModeLegacyHostLoad, + Status: PluginCompatibilityStatusPass, + OS: "darwin", + Arch: "arm64", + ArchiveSHA256: testArchiveSHA256, + }) + + if err := runPluginRegistry([]string{ + "compatibility", "update", + "--registry-dir", registryDir, + "--plugin", "workflow-plugin-test", + "--version", "v0.1.0", + "--evidence", evPath, + }); err != nil { + t.Fatalf("compatibility update with legacy-host-load for non-IaC manifest: %v", err) + } + idx := readCompatibilityIndex(t, registryDir, "workflow-plugin-test") + if len(idx.Versions) != 1 || len(idx.Versions[0].Compatibility) != 1 { + t.Fatalf("unexpected index: %#v", idx) + } + if idx.Versions[0].Compatibility[0].Mode != PluginCompatibilityModeLegacyHostLoad { + t.Fatalf("evidence mode = %q, want legacy-host-load", idx.Versions[0].Compatibility[0].Mode) + } +} + +func TestManifestAdvertisesIaCProvider(t *testing.T) { + cases := []struct { + name string + m *RegistryManifest + want bool + }{ + {name: "nil", m: nil, want: false}, + {name: "no capabilities", m: &RegistryManifest{}, want: false}, + {name: "nil iacProvider", m: &RegistryManifest{Capabilities: &RegistryCapabilities{}}, want: false}, + {name: "empty iacProvider name", m: &RegistryManifest{Capabilities: &RegistryCapabilities{IaCProvider: &RegistryIaCProvider{}}}, want: false}, + {name: "with iacProvider name", m: &RegistryManifest{Capabilities: &RegistryCapabilities{IaCProvider: &RegistryIaCProvider{Name: "testcloud"}}}, want: true}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got := manifestAdvertisesIaCProvider(tc.m) + if got != tc.want { + t.Fatalf("manifestAdvertisesIaCProvider = %v, want %v", got, tc.want) + } + }) + } +} + func prepareCompatibilityRegistry(t *testing.T, plugin, version, archiveSHA string) string { t.Helper() dir := t.TempDir() @@ -296,6 +421,57 @@ func prepareCompatibilityRegistry(t *testing.T, plugin, version, archiveSHA stri return dir } +// prepareIaCCompatibilityRegistry creates a registry directory with a manifest +// that advertises iacProvider capability. Used to test the enforcement that +// only typed-iac evidence is accepted for IaC provider plugins. +func prepareIaCCompatibilityRegistry(t *testing.T, plugin, version, archiveSHA string) string { + t.Helper() + dir := t.TempDir() + writeIaCManifest(t, dir, plugin, version, archiveSHA) + return dir +} + +func writeIaCManifest(t *testing.T, registryDir, plugin, version, archiveSHA string) { + t.Helper() + manifest := RegistryManifest{ + Name: plugin, + Version: version, + Author: "workflow", + Description: "test IaC provider plugin", + Type: "external", + Tier: "first_party", + MinEngineVersion: "v0.50.0", + Downloads: []PluginDownload{{ + OS: "darwin", + Arch: "arm64", + URL: "https://example.invalid/plugin.tar.gz", + SHA256: archiveSHA, + }, { + OS: "linux", + Arch: "amd64", + URL: "https://example.invalid/plugin-linux.tar.gz", + SHA256: archiveSHA, + }}, + Capabilities: &RegistryCapabilities{ + IaCProvider: &RegistryIaCProvider{ + Name: "testcloud", + ResourceTypes: []string{"testcloud.instance"}, + }, + }, + } + data, err := json.MarshalIndent(manifest, "", " ") + if err != nil { + t.Fatalf("marshal IaC manifest: %v", err) + } + path := filepath.Join(registryDir, "plugins", plugin, "manifest.json") + if err := os.MkdirAll(filepath.Dir(path), 0o750); err != nil { + t.Fatalf("mkdir manifest dir: %v", err) + } + if err := os.WriteFile(path, append(data, '\n'), 0o600); err != nil { + t.Fatalf("write IaC manifest: %v", err) + } +} + func writeManifest(t *testing.T, registryDir, plugin, version, archiveSHA string) { t.Helper() writeManifestWithDownloads(t, registryDir, plugin, version, []PluginDownload{{ diff --git a/docs/WFCTL.md b/docs/WFCTL.md index 1f47ea34..a258f131 100644 --- a/docs/WFCTL.md +++ b/docs/WFCTL.md @@ -556,7 +556,7 @@ wfctl plugin conformance --artifact [options] | Flag | Default | Description | |------|---------|-------------| -| `--mode` | `typed-iac` | Conformance mode. Currently checks strict typed IaC plugin launch/contract compatibility | +| `--mode` | `typed-iac` | Conformance mode. `typed-iac` checks strict typed IaC plugin launch/contract compatibility and is the only mode that satisfies typed-IaC registry readiness for manifests advertising `iacProvider` capability. | | `--artifact` | _(none)_ | Release artifact tar.gz to test instead of a local plugin directory | | `--build-package` | `.` | Go package to build when testing a source directory, for example `./cmd/plugin` | | `--engine-version` | build version or `WFCTL_ENGINE_VERSION` | Workflow engine version recorded in evidence | @@ -564,6 +564,21 @@ wfctl plugin conformance --artifact [options] | `--output` | _(none)_ | Write JSON evidence to a file | | `--timeout` | `30s` | Plugin launch/check timeout | +**Conformance modes and compatibility evidence:** + +| Mode | Description | Satisfies IaC readiness? | +|------|-------------|--------------------------| +| `typed-iac` | Verifies the plugin binary completes the Workflow go-plugin handshake and registers `pb.IaCProviderRequired` and typed services. | **Yes** — required for manifests with `iacProvider` capability in first-party registries. | +| `legacy-host-load` | Advisory-only smoke evidence for legacy `sdk.Serve` module plugins that can load and expose metadata/contracts but have not migrated to `sdk.ServeIaCPlugin`. | **No** — never satisfies typed-IaC registry readiness; rejected at index-update time for IaC provider manifests. | + +A plugin can pass legacy host-load and still fail `wfctl plugin conformance --mode typed-iac` with: + +``` +error: iac: plugin uses legacy InvokeService dispatch removed in workflow v1.0.0 +``` + +`plugin-registry compatibility update` for a manifest advertising `iacProvider` capability will reject `legacy-host-load` evidence with an actionable error. + Local directory evidence is useful during development. Registry enforcement should use artifact evidence so `archiveSHA256` can be matched against the registry manifest download checksum. ```bash diff --git a/plugin/external/sdk/iacserver_test.go b/plugin/external/sdk/iacserver_test.go index d14a9ebd..43895d69 100644 --- a/plugin/external/sdk/iacserver_test.go +++ b/plugin/external/sdk/iacserver_test.go @@ -226,7 +226,9 @@ func TestRegisterAllIaCProviderServices_PluginServiceAlreadyRegistered_NoPanic(t // Pre-register PluginService (simulates a mixed sdk.Serve + IaC plugin). // Use an embedded-by-value stub so the pattern is idiomatic Go and not // a pointer-to-unimplemented (which the generated gRPC code warns against). - type minimalPluginSvc struct{ pb.UnimplementedPluginServiceServer } + type minimalPluginSvc struct { + pb.UnimplementedPluginServiceServer + } pb.RegisterPluginServiceServer(grpcSrv, &minimalPluginSvc{}) // RegisterAllIaCProviderServices must not panic on double-registration. if err := sdk.RegisterAllIaCProviderServices(grpcSrv, &fullProviderStub{}); err != nil { From eb0b4b4033b5696cfa3b4792e4a306abe22ff393 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 13 May 2026 22:28:03 +0000 Subject: [PATCH 3/3] docs: clarify legacy-host-load is an evidence/index mode, not a CLI --mode flag The docs table previously listed legacy-host-load in a "Conformance modes" section that could be misread as a valid --mode flag for wfctl plugin conformance. The CLI hard-rejects any mode other than typed-iac. Rename the section to "Compatibility evidence modes" and explicitly state: - wfctl plugin conformance only generates typed-iac evidence - legacy-host-load is an evidence/index mode from legacy tooling; it cannot be passed as --mode to the conformance command - The How-produced column now makes the source of each evidence mode clear Agent-Logs-Url: https://github.com/GoCodeAlone/workflow/sessions/27e4f5c0-448f-4c10-aff6-10dc8c66a108 Co-authored-by: intel352 <77607+intel352@users.noreply.github.com> --- docs/WFCTL.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/WFCTL.md b/docs/WFCTL.md index a258f131..6fa8ddcc 100644 --- a/docs/WFCTL.md +++ b/docs/WFCTL.md @@ -556,7 +556,7 @@ wfctl plugin conformance --artifact [options] | Flag | Default | Description | |------|---------|-------------| -| `--mode` | `typed-iac` | Conformance mode. `typed-iac` checks strict typed IaC plugin launch/contract compatibility and is the only mode that satisfies typed-IaC registry readiness for manifests advertising `iacProvider` capability. | +| `--mode` | `typed-iac` | Conformance mode. Only `typed-iac` is accepted; it checks strict typed IaC plugin launch/contract compatibility and is the only mode that satisfies typed-IaC registry readiness for manifests advertising `iacProvider` capability. | | `--artifact` | _(none)_ | Release artifact tar.gz to test instead of a local plugin directory | | `--build-package` | `.` | Go package to build when testing a source directory, for example `./cmd/plugin` | | `--engine-version` | build version or `WFCTL_ENGINE_VERSION` | Workflow engine version recorded in evidence | @@ -564,12 +564,14 @@ wfctl plugin conformance --artifact [options] | `--output` | _(none)_ | Write JSON evidence to a file | | `--timeout` | `30s` | Plugin launch/check timeout | -**Conformance modes and compatibility evidence:** +**Compatibility evidence modes** (stored in the index; distinct from the `--mode` CLI flag): -| Mode | Description | Satisfies IaC readiness? | -|------|-------------|--------------------------| -| `typed-iac` | Verifies the plugin binary completes the Workflow go-plugin handshake and registers `pb.IaCProviderRequired` and typed services. | **Yes** — required for manifests with `iacProvider` capability in first-party registries. | -| `legacy-host-load` | Advisory-only smoke evidence for legacy `sdk.Serve` module plugins that can load and expose metadata/contracts but have not migrated to `sdk.ServeIaCPlugin`. | **No** — never satisfies typed-IaC registry readiness; rejected at index-update time for IaC provider manifests. | +`wfctl plugin conformance` only generates `typed-iac` evidence. The `legacy-host-load` mode is an evidence/index mode that appears in compatibility indexes for older host-load smoke checks; it cannot be generated by the conformance command and is rejected as insufficient for manifests advertising `iacProvider` capability. + +| Evidence mode | How produced | Satisfies IaC readiness? | +|---------------|-------------|--------------------------| +| `typed-iac` | `wfctl plugin conformance --mode typed-iac` | **Yes** — required for manifests with `iacProvider` capability in first-party registries. | +| `legacy-host-load` | Legacy tooling (not `wfctl plugin conformance`) | **No** — advisory only; never satisfies typed-IaC registry readiness; rejected at index-update time for IaC provider manifests. | A plugin can pass legacy host-load and still fail `wfctl plugin conformance --mode typed-iac` with: