You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
workflow-registry/scripts/sync-versions.sh is bash + jq + gh CLI. workflow#758 added a strict-semver tag gate to it (PR #110, 940ecc405c) — but that regex is now duplicated with wfctl plugin validate-contract --for-publish (Layer 1). Two implementations of the same rule = drift risk.
Cycle-4 plan adversarial review flagged this as "tooling decision creep" (I1 in workflow-registry context); it was accepted pragmatically at the time to keep Layer 2 scoped down. Coming back to address it now.
Problems with the current bash sync
Regex duplication between sync-versions.sh and wfctl plugin validate-contract --for-publish. Both define ^v\d+\.\d+\.\d+$. Future changes (e.g. allowing -rc.N once ParseSemver supports it) require touching two places.
No test harness in workflow-registry repo. Layer 2 PR shipped without an automated test for the new gate — only an inline regex verification at PR time.
Can't reach the next gate. The deferred binary-vs-file capability freshness gate (workflow#758 cycle 4-A1 I3) requires spawning the just-released plugin binary and calling its GetContractRegistry RPC, then diffing against the committed plugin.json.capabilities. Bash + jq cannot do that; wfctl already has plugin-spawn infrastructure (plugin/external/manager.go).
Two source-of-truth problem.wfctl is the plugin-contract authority (it owns PluginManifest.Validate, ParseSemver, the gRPC client). Registry sync logic that knows about plugin manifests, version semantics, and capability shapes belongs in the same domain.
Long-tail of bespoke shell logic — fetch_plugin_json shells gh api, downloads_match_version is a custom jq filter, release_downloads parses JSON via bash. Each has been a small regression hazard.
Proposed direction
New wfctl registry sync subcommand. Walks plugins/<name>/manifest.json in a checked-out workflow-registry repo, performs the same tag-fetch / version-compare / capability-fetch / manifest-rewrite logic as sync-versions.sh today, plus:
Strict-semver tag gate (single regex; same source as wfctl plugin validate-contract --for-publish).
(Future, deferred I3) --verify-capabilities flag: pull the just-released plugin tarball, exec the binary, call GetContractRegistry, diff against committed plugin.json.capabilities. Reject if stale.
Binary-vs-file capability freshness gate at contract-check time (cycle 4-A1 I3). Natural fit for wfctl registry sync --verify-capabilities AND wfctl plugin validate-contract --for-publish --verify-binary (operator-side).
Full SemVer 2.0.0 prerelease support: requires concerted ParseSemver + sync-versions + wfctl install update. Naturally part of this scope if registry sync moves to Go.
Gap-repos: ~8 plugin repos lack release pipelines (agent, cms, compute, cloud-ui, data-protection, edge-compute, sandbox, waf). Separate from this migration but should be audited alongside.
Audit needed
Before brainstorming the design, an audit pass to inventory:
Every shell-script in workflow-registry/scripts/ (not just sync-versions.sh) — what else lives there, what calls it?
Existing wfctl plugin subcommand surface — what's the precedent for wfctl registry <verb> vs wfctl plugin registry-<verb>?
workflow-registry's CI invocations of these scripts — what's the actual contract Github Actions consume?
Any in-tree consumers besides CI (cron, manual runs by operators, downstream tooling)?
The 56 Layer 3b repos: per-repo variance preview so the fan-out doesn't hit surprises mid-sweep (DO/AWS/GCP/Azure/github pilot already revealed: Version var name varies, main.go path varies, IaC vs non-IaC split, capabilities-shape variance).
Acceptance criteria
wfctl registry sync subcommand lands with table-driven tests
workflow-registry/scripts/sync-versions.sh deleted (or reduced to a single-line wfctl invocation)
Same strict-semver tag gate sourced from one regex constant in Go
Context
workflow-registry/scripts/sync-versions.shis bash + jq + gh CLI. workflow#758 added a strict-semver tag gate to it (PR #110, 940ecc405c) — but that regex is now duplicated withwfctl plugin validate-contract --for-publish(Layer 1). Two implementations of the same rule = drift risk.Cycle-4 plan adversarial review flagged this as "tooling decision creep" (I1 in workflow-registry context); it was accepted pragmatically at the time to keep Layer 2 scoped down. Coming back to address it now.
Problems with the current bash sync
sync-versions.shandwfctl plugin validate-contract --for-publish. Both define^v\d+\.\d+\.\d+$. Future changes (e.g. allowing-rc.NonceParseSemversupports it) require touching two places.GetContractRegistryRPC, then diffing against the committedplugin.json.capabilities. Bash + jq cannot do that;wfctlalready has plugin-spawn infrastructure (plugin/external/manager.go).wfctlis the plugin-contract authority (it ownsPluginManifest.Validate,ParseSemver, the gRPC client). Registry sync logic that knows about plugin manifests, version semantics, and capability shapes belongs in the same domain.fetch_plugin_jsonshellsgh api,downloads_match_versionis a custom jq filter,release_downloadsparses JSON via bash. Each has been a small regression hazard.Proposed direction
New
wfctl registry syncsubcommand. Walksplugins/<name>/manifest.jsonin a checked-out workflow-registry repo, performs the same tag-fetch / version-compare / capability-fetch / manifest-rewrite logic assync-versions.shtoday, plus:wfctl plugin validate-contract --for-publish).--verify-capabilitiesflag: pull the just-released plugin tarball, exec the binary, callGetContractRegistry, diff against committedplugin.json.capabilities. Reject if stale.--applywrites back; absent it's dry-run.workflow-registry's GitHub Action becomes a one-line
wfctl registry sync --apply(or whatever flag set).Bundles with
This issue also bundles the related deferred work that all benefits from a Go-side migration:
Deferred from workflow#758
wfctl registry sync --verify-capabilitiesANDwfctl plugin validate-contract --for-publish --verify-binary(operator-side).ParseSemver+sync-versions+wfctl installupdate. Naturally part of this scope if registry sync moves to Go.Audit needed
Before brainstorming the design, an audit pass to inventory:
workflow-registry/scripts/(not just sync-versions.sh) — what else lives there, what calls it?pluginsubcommand surface — what's the precedent forwfctl registry <verb>vswfctl plugin registry-<verb>?Acceptance criteria
wfctl registry syncsubcommand lands with table-driven testsworkflow-registry/scripts/sync-versions.shdeleted (or reduced to a single-line wfctl invocation)--verify-capabilities(or equivalent) closes cycle 4-A1 I3References
docs/retros/2026-05-23-workflow-758-plugin-version-discipline.md