Skip to content

Migrate sync-versions.sh → wfctl registry sync subcommand (consolidates Layer 3b + deferred I3 capability gate) #762

Description

@intel352

Context

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

  1. 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.
  2. 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.
  3. 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).
  4. 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.
  5. Long-tail of bespoke shell logicfetch_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.
  • (Future) --apply writes 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

  • Layer 3b: apply plugin-version discipline to remaining 56 plugin repos (workflow#760). Mechanical fan-out; the canonical pattern shipped 2026-05-23. Audit list enumerated in Layer 3b: apply plugin-version discipline to remaining 56 plugin repos (workflow#758 follow-up) #760.
  • 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:

  1. Every shell-script in workflow-registry/scripts/ (not just sync-versions.sh) — what else lives there, what calls it?
  2. Existing wfctl plugin subcommand surface — what's the precedent for wfctl registry <verb> vs wfctl plugin registry-<verb>?
  3. workflow-registry's CI invocations of these scripts — what's the actual contract Github Actions consume?
  4. Any in-tree consumers besides CI (cron, manual runs by operators, downstream tooling)?
  5. 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
  • --verify-capabilities (or equivalent) closes cycle 4-A1 I3
  • Layer 3b sweep (56 repos) completes
  • Gap-repos audited; per-repo path forward documented
  • Full SemVer 2.0.0 evaluated: ship the parser update OR explicitly defer with rationale

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions