From 546a68965f455d55cd89159312f9560d91e8c41a Mon Sep 17 00:00:00 2001 From: Jon Langevin Date: Sun, 24 May 2026 03:55:59 -0400 Subject: [PATCH] feat: add volunteer science catalog --- .github/workflows/ci.yml | 7 +- .github/workflows/release.yml | 10 +- .github/workflows/scaffold-rename-test.yml | 63 ---------- .goreleaser.yaml | 8 +- CLAUDE.md | 46 +++----- README.md | 110 +++--------------- catalog/folding.go | 37 ++++++ catalog/protocol_aliases.go | 20 ++++ cmd/scaffold-workflow-plugin-iac/main.go | 23 ---- cmd/scaffold-workflow-plugin/main.go | 19 --- cmd/workflow-plugin-volunteer-science/main.go | 14 +++ go.mod | 11 +- go.sum | 3 + internal/iacserver.go | 30 ----- internal/plugin.go | 57 ++------- internal/plugin_test.go | 71 ++++++++++- plugin.json | 51 ++++---- scripts/rename-from-scaffold.sh | 71 ----------- 18 files changed, 228 insertions(+), 423 deletions(-) delete mode 100644 .github/workflows/scaffold-rename-test.yml create mode 100644 catalog/folding.go create mode 100644 catalog/protocol_aliases.go delete mode 100644 cmd/scaffold-workflow-plugin-iac/main.go delete mode 100644 cmd/scaffold-workflow-plugin/main.go create mode 100644 cmd/workflow-plugin-volunteer-science/main.go delete mode 100644 internal/iacserver.go delete mode 100755 scripts/rename-from-scaffold.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc918f5..6b0087c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,9 +29,6 @@ jobs: go-version-file: go.mod - uses: GoCodeAlone/setup-wfctl@v1 with: - version: v0.62.0 - # workflow#762: scaffold carries type="scaffold" + TEMPLATE.* placeholders. - # Old `plugin validate --strict-contracts` (wfctl v0.20.1) rejects both; - # validate-contract (v0.62.0) checks scaffold-compatible contract surface. - - name: Validate plugin contract (scaffold-aware) + version: v0.63.1 + - name: Validate plugin contract run: wfctl plugin validate-contract . diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7dbedb8..b8574e8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -42,10 +42,10 @@ jobs: run: | RUNNER_ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/') BIN=$(jq -r --arg arch "$RUNNER_ARCH" \ - '[.[] | select(.type=="Binary" and .goos=="linux" and .goarch==$arch and (.name|startswith("scaffold-workflow-plugin")))] | .[0].path // ""' \ + '[.[] | select(.type=="Binary" and .goos=="linux" and .goarch==$arch and (.name|startswith("workflow-plugin-volunteer-science")))] | .[0].path // ""' \ dist/artifacts.json) if [ -z "$BIN" ] || [ "$BIN" = "null" ]; then - echo "::warning::No matching linux/$RUNNER_ARCH scaffold binary in dist/artifacts.json; skipping verify-capabilities" + echo "::warning::No matching linux/$RUNNER_ARCH workflow-plugin-volunteer-science binary in dist/artifacts.json; skipping verify-capabilities" jq '.[] | {name, type, goos, goarch, path}' dist/artifacts.json exit 0 fi @@ -56,7 +56,5 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: gh release edit "${{ github.ref_name }}" --draft=false --repo "${{ github.repository }}" - # NOTE: scaffold-workflow-plugin does NOT notify workflow-registry on release - # — it's a scaffold repo, NOT an installable plugin. The notify-workflow-registry - # job is intentionally omitted (and registry-side type-allowlist defense - # in wfctl plugin registry-sync rejects type:"scaffold" if it ever leaks in). + # Registry publication is intentionally separate from binary release until + # workflow-compute consumes provider catalog plugins from the registry. diff --git a/.github/workflows/scaffold-rename-test.yml b/.github/workflows/scaffold-rename-test.yml deleted file mode 100644 index a9c99cc..0000000 --- a/.github/workflows/scaffold-rename-test.yml +++ /dev/null @@ -1,63 +0,0 @@ -name: Scaffold rename test - -on: - push: - branches: [main] - pull_request: - -# Verifies scripts/rename-from-scaffold.sh produces a buildable plugin in -# both --mode iac and --mode non-iac. Includes a nested-fixture file to -# exercise the find-based bulk-sed (catches globstar regressions per -# workflow#762 plan C-P4 guard). -jobs: - rename-non-iac: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - uses: actions/setup-go@v6 - with: - go-version-file: go.mod - - name: Add nested fixture file - run: | - mkdir -p internal/nested/sub - cat > internal/nested/sub/test.go <<'EOF' - // Fixture file deeper than one level - exercises the rename script's - // find loop to verify imports in nested packages get rewritten. - package sub - import _ "github.com/GoCodeAlone/scaffold-workflow-plugin/internal" - EOF - - name: Rename to test-plugin (non-iac) + build - run: | - cp -r . /tmp/scaffold-copy - cd /tmp/scaffold-copy - bash scripts/rename-from-scaffold.sh test-plugin --mode non-iac - go build ./... - test -d cmd/workflow-plugin-test-plugin - test ! -d cmd/scaffold-workflow-plugin - test ! -d cmd/scaffold-workflow-plugin-iac - test "$(jq -r .type plugin.json)" = "external" - test "$(jq -r .name plugin.json)" = "workflow-plugin-test-plugin" - - rename-iac: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - uses: actions/setup-go@v6 - with: - go-version-file: go.mod - - name: Add nested fixture file - run: | - mkdir -p internal/nested/sub - cat > internal/nested/sub/test.go <<'EOF' - package sub - import _ "github.com/GoCodeAlone/scaffold-workflow-plugin/internal" - EOF - - name: Rename to test-plugin (iac) + build - run: | - cp -r . /tmp/scaffold-copy - cd /tmp/scaffold-copy - bash scripts/rename-from-scaffold.sh test-plugin --mode iac - go build ./... - test -d cmd/workflow-plugin-test-plugin - test ! -d cmd/scaffold-workflow-plugin - test ! -d cmd/scaffold-workflow-plugin-iac diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 346721e..69ebec3 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -6,9 +6,9 @@ before: - "sed -i.bak 's/\"version\": \".*\"/\"version\": \"{{ .Version }}\"/' plugin.json && rm -f plugin.json.bak" builds: - - id: scaffold-workflow-plugin - main: ./cmd/scaffold-workflow-plugin - binary: scaffold-workflow-plugin + - id: workflow-plugin-volunteer-science + main: ./cmd/workflow-plugin-volunteer-science + binary: workflow-plugin-volunteer-science env: - CGO_ENABLED=0 goos: @@ -19,7 +19,7 @@ builds: - amd64 - arm64 ldflags: - - -s -w -X main.version={{.Version}} -X github.com/GoCodeAlone/scaffold-workflow-plugin/internal.Version={{.Version}} + - -s -w -X main.version={{.Version}} -X github.com/GoCodeAlone/workflow-plugin-volunteer-science/internal.Version={{.Version}} archives: - formats: [tar.gz] diff --git a/CLAUDE.md b/CLAUDE.md index 7f19626..c3ce27f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,6 +1,7 @@ -# CLAUDE.md — Workflow Plugin Template +# CLAUDE.md — workflow-plugin-volunteer-science -External gRPC plugin for the GoCodeAlone/workflow engine. +External gRPC plugin for volunteer science provider catalog metadata consumed +by Workflow and `workflow-compute`. ## Build & Test @@ -9,40 +10,19 @@ go build ./... go test ./... -v -race -count=1 ``` -## Cross-compile for deployment +## Cross-compile ```sh -GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -ldflags="-s -w" -o scaffold-workflow-plugin ./cmd/scaffold-workflow-plugin/ +GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -ldflags="-s -w" -o workflow-plugin-volunteer-science ./cmd/workflow-plugin-volunteer-science/ ``` ## Structure -- `cmd/scaffold-workflow-plugin/main.go` — Plugin entry point (calls `sdk.Serve`) -- `internal/plugin.go` — Plugin manifest, module factories, step factories -- `internal/` — All module and step implementations -- `plugin.json` — Capability manifest for the workflow registry -- `.goreleaser.yaml` — GoReleaser v2 config for cross-platform releases -- `.github/workflows/ci.yml` — CI on push/PR (build + test) -- `.github/workflows/release.yml` — Release on v* tag push (GoReleaser) - -## Adding a Module Type - -1. Create `internal/module_example.go` implementing the module -2. Register in `internal/plugin.go` ModuleFactories() -3. Add to `plugin.json` capabilities.moduleTypes -4. Add tests in `internal/module_example_test.go` - -## Adding a Step Type - -1. Create `internal/step_example.go` implementing the step -2. Register in `internal/plugin.go` StepFactories() -3. Add to `plugin.json` capabilities.stepTypes -4. Add tests in `internal/step_example_test.go` - -## Releasing - -```sh -git tag v0.1.0 -git push origin v0.1.0 -``` -GoReleaser builds cross-platform binaries and creates a GitHub Release automatically. +- `cmd/workflow-plugin-volunteer-science/main.go` — external plugin entrypoint +- `internal/plugin.go` — Workflow plugin manifest +- `catalog/folding.go` — public Folding@home provider catalog metadata +- `catalog/types.go` — public JSON-compatible provider catalog types +- `plugin.json` — registry-facing plugin manifest +- `.goreleaser.yaml` — GoReleaser v2 config for releases +- `.github/workflows/ci.yml` — build, test, vet, and plugin contract validation +- `.github/workflows/release.yml` — tagged release pipeline diff --git a/README.md b/README.md index 0022421..0ebd630 100644 --- a/README.md +++ b/README.md @@ -1,103 +1,29 @@ -# scaffold-workflow-plugin +# workflow-plugin-volunteer-science -This is a SCAFFOLD repo. It is NOT an installable plugin. +Public Workflow plugin for volunteer science provider catalog metadata used by +`workflow-compute`. The catalog package intentionally avoids private module +dependencies so this public plugin can build on public CI and be imported by +future public tooling. -Use it to create a new workflow plugin via GitHub's "Use this template" button. +The plugin currently owns shape-only upstream-client requirements for +Folding@home. Real-client promotion requires digest-pinned FAHClient images and +stored version evidence before provider contracts can claim real upstream +conformance. -(A future `wfctl plugin init --from-scaffold` subcommand is tracked at -[workflow#762](https://github.com/GoCodeAlone/workflow/issues/762) but -not yet implemented; use the GitHub UI path below.) +## Build & Test -## After creating your new repo from this template - -1. **Enable GitHub Actions**: Settings → Actions → "I understand my workflows, enable them". - New repos created from a template ship with workflows DISABLED by - default; you must enable them once before any release.yml run can - succeed. - -2. **Run the rename script**: - - ```bash - bash scripts/rename-from-scaffold.sh --mode [iac|non-iac] - ``` - - This: - - Picks the IaC or non-IaC main.go variant; deletes the other. - - Renames `cmd/scaffold-workflow-plugin*/` → `cmd/workflow-plugin-/`. - - Updates `go.mod` module path. - - Bulk-sed of `scaffold-workflow-plugin` → `workflow-plugin-` - across `.go`/`.yaml`/`.md`/`plugin.json` files. - - Resets `plugin.json.type` from `"scaffold"` to `"external"`; sets `.name`. - - Removes the rename script itself + scaffold-rename-test workflow. - -3. **Edit `plugin.json`**: replace `TEMPLATE.module` / `TEMPLATE.step` / - `TEMPLATE.resource` placeholder capabilities with your plugin's actual - types. Update `minEngineVersion` if you depend on a newer workflow. - -4. **Implement your plugin** in `internal/`: - - **non-IaC mode**: extend `internal/plugin.go`'s `NewPlugin()` with - real ModuleFactories / StepFactories / TriggerFactories. Delete - `internal/iacserver.go` (unused in non-IaC mode). - - **IaC mode**: replace `internal/iacserver.go`'s stub - `pb.UnimplementedIaCProviderRequiredServer` embed with your real - IaC provider implementation (Initialize, Plan, Destroy, etc.). - Delete `internal/plugin.go`'s NewPlugin (unused in IaC mode). - -5. **Commit + tag**: - - ```bash - git add -A && git commit -m "feat: initial plugin scaffold from scaffold-workflow-plugin" - git tag v0.1.0 && git push origin main v0.1.0 - ``` - - `release.yml`'s `wfctl plugin validate-contract --for-publish` gate - verifies your tag (must be release-grade semver `^v\d+\.\d+\.\d+$`) - and contract (capabilities populated, minEngineVersion set, main.go - wires `sdk.ResolveBuildVersion`, goreleaser ldflag present). - -## Modes - -- `--mode non-iac` (default): for module/step/trigger plugins that use - `sdk.Serve`. Suitable for MOST plugins. -- `--mode iac`: for IaC provider plugins that use `sdk.ServeIaCPlugin` - and satisfy `pb.IaCProviderRequiredServer`. Use ONLY if your plugin - provisions infrastructure (cloud resources, databases, etc.). - -## What's pre-baked in (workflow#758 + #762 compliance) - -- `plugin.json.version = "0.0.0"` sentinel (release tag injected at build - time via goreleaser). -- `internal/plugin.go`'s `var Version = "0.0.0"` (ldflag-injected at - release; surfaced through `sdk.ResolveBuildVersion`). -- `release.yml` pre-build + post-build `wfctl plugin validate-contract` - gates. -- No `sync-plugin-version.yml` (the discarded sync mechanism is not - shipped in scaffolds; goreleaser's `before:` hook rewrites - `plugin.json.version` from the tag at release time). -- `sdk.WithBuildVersion(sdk.ResolveBuildVersion(internal.Version))` - wired in main.go so the binary surfaces its release version through - `GetManifest` at runtime. -- `setup-wfctl@v1 with version: v0.62.0` pinned for the release pipeline. - -## Build & test (during plugin development) - -```bash +```sh go build ./... go test ./... -race -count=1 ``` -## Releasing +## Release -```bash -git tag v0.1.0 && git push origin v0.1.0 +```sh +git tag v0.1.0 +git push origin v0.1.0 ``` -`release.yml` runs `wfctl plugin validate-contract --for-publish`, -goreleaser builds cross-platform binaries, and the post-build gate -verifies the shipped tarball's `plugin.json` carries the tag. - -## References - -- Plugin release contract: [docs/PLUGIN_RELEASE_GATES.md](https://github.com/GoCodeAlone/workflow/blob/main/docs/PLUGIN_RELEASE_GATES.md) -- Plugin version discipline: [workflow#758](https://github.com/GoCodeAlone/workflow/issues/758) -- Registry sync subcommand: [workflow#762](https://github.com/GoCodeAlone/workflow/issues/762) +The release workflow validates `plugin.json`, builds cross-platform binaries +with GoReleaser, and verifies the runtime plugin manifest against the shipped +contract metadata. diff --git a/catalog/folding.go b/catalog/folding.go new file mode 100644 index 0000000..4991961 --- /dev/null +++ b/catalog/folding.go @@ -0,0 +1,37 @@ +package catalog + +const volunteerSciencePluginID = "workflow-plugin-volunteer-science" + +func FoldingUpstreamClientRequirement() ProviderUpstreamClientRequirement { + runtime := DefaultProviderRuntimeProfile("service-sandboxed-container", ExecutionSandboxedContainer, ProofArtifactHash) + return ProviderUpstreamClientRequirement{ + ProtocolVersion: Version, + PluginID: volunteerSciencePluginID, + ProviderID: "folding-at-home", + ContractID: "folding-at-home.client.v1", + Version: "v1.0.0", + RuntimeProfileID: runtime.ID, + ConformanceProfile: "upstream-client-v1", + DefaultConformance: UpstreamClientConformanceShapeOnly, + RealClientConformance: UpstreamClientConformanceRealClient, + UpstreamClientName: "FAHClient", + VersionProbeCommand: []string{"FAHClient", "--version"}, + ImagePolicy: ProviderUpstreamImagePolicy{ + DigestPinnedImageRequired: true, + RecommendedImageRef: "foldingathome/fah-gpu@sha256:", + KnownImageRefs: []string{ + "foldingathome/fah-gpu@sha256:", + "foldingathome/fah-gpu-amd@sha256:", + }, + }, + RequiredEvidence: []string{ + "digest-pinned OCI image reference", + "artifact:// provider conformance evidence with sha256 digest", + "upstream-client-v1 version probe artifact", + }, + Notes: []string{ + "Folding@home real-client promotion requires a digest-pinned FAHClient image and stored version evidence", + "shape-only remains the default until real upstream-client-v1 evidence is attached to the provider contract", + }, + } +} diff --git a/catalog/protocol_aliases.go b/catalog/protocol_aliases.go new file mode 100644 index 0000000..5d5a32a --- /dev/null +++ b/catalog/protocol_aliases.go @@ -0,0 +1,20 @@ +package catalog + +import core "github.com/GoCodeAlone/workflow-plugin-compute-core/protocol" + +const ( + Version = core.Version + + ExecutionSandboxedContainer = core.ExecutionSandboxedContainer + ProofArtifactHash = core.ProofArtifactHash + + UpstreamClientConformanceShapeOnly = core.UpstreamClientConformanceShapeOnly + UpstreamClientConformanceRealClient = core.UpstreamClientConformanceRealClient +) + +type ( + ProviderUpstreamClientRequirement = core.ProviderUpstreamClientRequirement + ProviderUpstreamImagePolicy = core.ProviderUpstreamImagePolicy +) + +var DefaultProviderRuntimeProfile = core.DefaultProviderRuntimeProfile diff --git a/cmd/scaffold-workflow-plugin-iac/main.go b/cmd/scaffold-workflow-plugin-iac/main.go deleted file mode 100644 index 2942e32..0000000 --- a/cmd/scaffold-workflow-plugin-iac/main.go +++ /dev/null @@ -1,23 +0,0 @@ -// Command scaffold-workflow-plugin-iac is the IaC variant of the workflow -// plugin scaffold. Use this entrypoint when the plugin provisions -// infrastructure (cloud resources, etc.) — it serves the typed -// pb.IaCProvider* surface required by wfctl infra apply/plan/destroy. -// -// Instantiators run `bash scripts/rename-from-scaffold.sh --mode iac` -// to copy this file to cmd/workflow-plugin-/main.go and delete -// the non-IaC variant (cmd/scaffold-workflow-plugin/). -// -// Non-IaC plugins use cmd/scaffold-workflow-plugin/main.go instead. The -// rename script's --mode flag selects which entrypoint survives. -package main - -import ( - "github.com/GoCodeAlone/scaffold-workflow-plugin/internal" - sdk "github.com/GoCodeAlone/workflow/plugin/external/sdk" -) - -func main() { - sdk.ServeIaCPlugin(internal.NewIaCServer(), sdk.IaCServeOptions{ - BuildVersion: sdk.ResolveBuildVersion(internal.Version), - }) -} diff --git a/cmd/scaffold-workflow-plugin/main.go b/cmd/scaffold-workflow-plugin/main.go deleted file mode 100644 index ed4c56b..0000000 --- a/cmd/scaffold-workflow-plugin/main.go +++ /dev/null @@ -1,19 +0,0 @@ -// Command scaffold-workflow-plugin is the NON-IaC variant of the workflow -// plugin scaffold. It runs as a subprocess and communicates with the host -// workflow engine via the go-plugin gRPC protocol. -// -// Instantiators run `bash scripts/rename-from-scaffold.sh --mode non-iac` -// to copy this file to cmd/workflow-plugin-/main.go and delete -// the IaC variant (cmd/scaffold-workflow-plugin-iac/). -package main - -import ( - "github.com/GoCodeAlone/scaffold-workflow-plugin/internal" - sdk "github.com/GoCodeAlone/workflow/plugin/external/sdk" -) - -func main() { - sdk.Serve(internal.NewPlugin(), - sdk.WithBuildVersion(sdk.ResolveBuildVersion(internal.Version)), - ) -} diff --git a/cmd/workflow-plugin-volunteer-science/main.go b/cmd/workflow-plugin-volunteer-science/main.go new file mode 100644 index 0000000..b435b43 --- /dev/null +++ b/cmd/workflow-plugin-volunteer-science/main.go @@ -0,0 +1,14 @@ +// Command workflow-plugin-volunteer-science runs as an external workflow plugin +// and exposes volunteer science provider catalog metadata. +package main + +import ( + "github.com/GoCodeAlone/workflow-plugin-volunteer-science/internal" + sdk "github.com/GoCodeAlone/workflow/plugin/external/sdk" +) + +func main() { + sdk.Serve(internal.NewPlugin(), + sdk.WithBuildVersion(sdk.ResolveBuildVersion(internal.Version)), + ) +} diff --git a/go.mod b/go.mod index 51e255f..036c446 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,14 @@ -module github.com/GoCodeAlone/scaffold-workflow-plugin +module github.com/GoCodeAlone/workflow-plugin-volunteer-science go 1.26.0 -require github.com/GoCodeAlone/workflow v0.62.0 +require ( + github.com/GoCodeAlone/workflow v0.62.0 + github.com/GoCodeAlone/workflow-plugin-compute-core v0.0.0-20260524080452-6589b503feb7 +) require ( + github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/BurntSushi/toml v1.6.0 // indirect github.com/DataDog/datadog-go/v5 v5.8.3 // indirect github.com/GoCodeAlone/go-plugin v1.7.0 // indirect @@ -37,7 +41,6 @@ require ( github.com/cloudevents/sdk-go/v2 v2.16.2 // indirect github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect - github.com/containerd/log v0.1.0 // indirect github.com/danieljoos/wincred v1.2.3 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/distribution/reference v0.6.0 // indirect @@ -92,8 +95,6 @@ require ( github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect - github.com/moby/sys/sequential v0.6.0 // indirect - github.com/moby/term v0.5.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect diff --git a/go.sum b/go.sum index 2b69e90..fd162be 100644 --- a/go.sum +++ b/go.sum @@ -14,6 +14,8 @@ github.com/GoCodeAlone/modular/modules/eventbus/v2 v2.8.0 h1:buYs0TGNbAZgtTq1Qb+ github.com/GoCodeAlone/modular/modules/eventbus/v2 v2.8.0/go.mod h1:329flAKmwrPq2JEwu9iltWv6A83H/Di82Xze+kvdKDw= github.com/GoCodeAlone/workflow v0.62.0 h1:emFkTomDpVmBcEfw7quRO4V/J4qDsWNx/CrBdlGqkfg= github.com/GoCodeAlone/workflow v0.62.0/go.mod h1:659GGDrw3QJ7b625y9rf8QhKIpt1VCoEG0MxKu5tGQs= +github.com/GoCodeAlone/workflow-plugin-compute-core v0.0.0-20260524080452-6589b503feb7 h1:xO0Gf//l3iDEgaw/7ihn2k4js2djvUNB7fVsxtzsbDM= +github.com/GoCodeAlone/workflow-plugin-compute-core v0.0.0-20260524080452-6589b503feb7/go.mod h1:1T6uCpUWPCNk6XPYgKq5CL/7LkD24MphKYsYVzF4jnI= github.com/GoCodeAlone/yaegi v0.17.2 h1:WK6Y6e0t1a6U7r+S2dN3CGWW1PizYD3zO0zneToZPxM= github.com/GoCodeAlone/yaegi v0.17.2/go.mod h1:z5Pr6Wse6QJcQvpgxTxzMAevFarH0N37TG88Y9dprx0= github.com/IBM/sarama v1.47.0 h1:GcQFEd12+KzfPYeLgN69Fh7vLCtYRhVIx0rO4TZO318= @@ -415,6 +417,7 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/internal/iacserver.go b/internal/iacserver.go deleted file mode 100644 index 2cb6b66..0000000 --- a/internal/iacserver.go +++ /dev/null @@ -1,30 +0,0 @@ -package internal - -import ( - pb "github.com/GoCodeAlone/workflow/plugin/external/proto" -) - -// IaCServer is the IaC-mode stub for the scaffold. Embeds -// pb.UnimplementedIaCProviderRequiredServer so all required RPCs (Initialize, -// Name, Version, Capabilities, Plan, Destroy, Status, Import, ResolveSizing, -// BootstrapStateBackend) return codes.Unimplemented by default. -// -// Instantiators using `bash scripts/rename-from-scaffold.sh --mode iac` -// replace this stub with their real IaC provider implementation. The -// rename script removes cmd/scaffold-workflow-plugin/ in IaC mode, so the -// non-IaC NewPlugin() entrypoint is gone — only the IaC server remains. -// -// To implement additional optional IaC contracts, embed the corresponding -// Unimplemented*Server type: -// - pb.UnimplementedIaCProviderServer -// - pb.UnimplementedIaCProviderLogCaptureServer -// - pb.UnimplementedIaCProviderFinalizerServer -type IaCServer struct { - pb.UnimplementedIaCProviderRequiredServer -} - -// NewIaCServer constructs the IaC-mode plugin server. Called from -// cmd/scaffold-workflow-plugin-iac/main.go. -func NewIaCServer() *IaCServer { - return &IaCServer{} -} diff --git a/internal/plugin.go b/internal/plugin.go index 9e2ec07..09cb352 100644 --- a/internal/plugin.go +++ b/internal/plugin.go @@ -1,72 +1,31 @@ -// Package internal implements the scaffold-workflow-plugin plugin. +// Package internal implements the workflow-plugin-volunteer-science plugin. package internal import ( - "fmt" - sdk "github.com/GoCodeAlone/workflow/plugin/external/sdk" ) // Version is set at build time via -ldflags -// "-X github.com/GoCodeAlone/scaffold-workflow-plugin/internal.Version=X.Y.Z". +// "-X github.com/GoCodeAlone/workflow-plugin-volunteer-science/internal.Version=X.Y.Z". // Default is a bare semver so plugin loaders that validate semver accept // unreleased dev builds; goreleaser overrides with the real release tag. var Version = "0.0.0" -// TEMPLATEPlugin implements sdk.PluginProvider and optionally -// sdk.ModuleProvider, sdk.StepProvider, sdk.TriggerProvider, etc. -type TEMPLATEPlugin struct{} +// VolunteerSciencePlugin exposes volunteer science provider catalog metadata. +type VolunteerSciencePlugin struct{} // NewPlugin returns a new plugin instance. main.go calls sdk.Serve(NewPlugin()). func NewPlugin() sdk.PluginProvider { - return &TEMPLATEPlugin{} + return &VolunteerSciencePlugin{} } // Manifest returns the plugin metadata used by the workflow engine for // discovery and capability negotiation. -func (p *TEMPLATEPlugin) Manifest() sdk.PluginManifest { +func (p *VolunteerSciencePlugin) Manifest() sdk.PluginManifest { return sdk.PluginManifest{ - Name: "scaffold-workflow-plugin", + Name: "workflow-plugin-volunteer-science", Version: Version, Author: "GoCodeAlone", - Description: "TEMPLATE plugin for the workflow engine", - } -} - -// ModuleTypes returns the module type names this plugin provides. -// Remove this method if the plugin does not provide any modules. -func (p *TEMPLATEPlugin) ModuleTypes() []string { - return []string{ - // "example.module_type", - } -} - -// CreateModule creates a module instance of the given type. -// Remove this method if the plugin does not provide any modules. -func (p *TEMPLATEPlugin) CreateModule(typeName, name string, config map[string]any) (sdk.ModuleInstance, error) { - switch typeName { - // case "example.module_type": - // return newExampleModule(name, config) - default: - return nil, fmt.Errorf("TEMPLATE: unknown module type %q", typeName) - } -} - -// StepTypes returns the step type names this plugin provides. -// Remove this method if the plugin does not provide any steps. -func (p *TEMPLATEPlugin) StepTypes() []string { - return []string{ - // "step.example_action", - } -} - -// CreateStep creates a step instance of the given type. -// Remove this method if the plugin does not provide any steps. -func (p *TEMPLATEPlugin) CreateStep(typeName, name string, config map[string]any) (sdk.StepInstance, error) { - switch typeName { - // case "step.example_action": - // return newExampleStep(name, config), nil - default: - return nil, fmt.Errorf("TEMPLATE: unknown step type %q", typeName) + Description: "Volunteer science provider catalog plugin for workflow-compute.", } } diff --git a/internal/plugin_test.go b/internal/plugin_test.go index c05d370..13b1955 100644 --- a/internal/plugin_test.go +++ b/internal/plugin_test.go @@ -1,9 +1,14 @@ package internal_test import ( + "encoding/json" + "os" + "slices" + "strings" "testing" - "github.com/GoCodeAlone/scaffold-workflow-plugin/internal" + "github.com/GoCodeAlone/workflow-plugin-volunteer-science/catalog" + "github.com/GoCodeAlone/workflow-plugin-volunteer-science/internal" sdk "github.com/GoCodeAlone/workflow/plugin/external/sdk" ) @@ -22,4 +27,68 @@ func TestManifest_HasRequiredFields(t *testing.T) { if m.Description == "" { t.Error("manifest Description is empty") } + if strings.Contains(m.Description, "TEMPLATE") || strings.Contains(strings.ToLower(m.Description), "scaffold") { + t.Fatalf("manifest still carries scaffold placeholder text: %q", m.Description) + } +} + +func TestPluginJSON_AdvertisesProviderCatalogOnly(t *testing.T) { + data, err := os.ReadFile("../plugin.json") + if err != nil { + t.Fatalf("read plugin.json: %v", err) + } + var manifest struct { + Name string `json:"name"` + Description string `json:"description"` + Type string `json:"type"` + Private bool `json:"private"` + Keywords []string `json:"keywords"` + Capabilities struct { + ConfigProvider bool `json:"configProvider"` + ModuleTypes []string `json:"moduleTypes"` + StepTypes []string `json:"stepTypes"` + TriggerTypes []string `json:"triggerTypes"` + } `json:"capabilities"` + } + if err := json.Unmarshal(data, &manifest); err != nil { + t.Fatalf("parse plugin.json: %v", err) + } + if manifest.Name != "workflow-plugin-volunteer-science" || manifest.Type != "external" || manifest.Private { + t.Fatalf("unexpected plugin identity: %+v", manifest) + } + joined := strings.Join(append(manifest.Keywords, manifest.Description), " ") + if strings.Contains(joined, "TEMPLATE") || strings.Contains(strings.ToLower(joined), "scaffold") { + t.Fatalf("plugin.json still carries scaffold placeholder text: %s", joined) + } + if manifest.Capabilities.ConfigProvider || + len(manifest.Capabilities.ModuleTypes) != 0 || + len(manifest.Capabilities.StepTypes) != 0 || + len(manifest.Capabilities.TriggerTypes) != 0 { + t.Fatalf("volunteer science provider catalog should not advertise runtime capabilities: %+v", manifest.Capabilities) + } + if !slices.Contains(manifest.Keywords, "provider-catalog") || !slices.Contains(manifest.Keywords, "workflow-compute") { + t.Fatalf("provider-catalog keywords missing: %+v", manifest.Keywords) + } +} + +func TestFoldingUpstreamClientRequirement_DocumentsImagePolicy(t *testing.T) { + req := catalog.FoldingUpstreamClientRequirement() + if err := req.Validate(); err != nil { + t.Fatalf("folding requirement invalid: %v", err) + } + if req.ProtocolVersion != catalog.Version || req.PluginID != "workflow-plugin-volunteer-science" { + t.Fatalf("folding requirement identity: %+v", req) + } + if req.ProviderID != "folding-at-home" || req.UpstreamClientName != "FAHClient" || req.ImagePolicy.RecommendedImageRef != "foldingathome/fah-gpu@sha256:" { + t.Fatalf("folding upstream requirement: %+v", req) + } + if req.DefaultConformance != catalog.UpstreamClientConformanceShapeOnly || req.RealClientConformance != catalog.UpstreamClientConformanceRealClient { + t.Fatalf("folding conformance policy: %+v", req) + } + if req.ImagePolicy.OperatorSuppliedImageRequired { + t.Fatalf("folding should advertise its known image family: %+v", req.ImagePolicy) + } + if len(req.ImagePolicy.KnownImageRefs) < 2 || req.ImagePolicy.KnownImageRefs[1] != "foldingathome/fah-gpu-amd@sha256:" { + t.Fatalf("folding upstream requirement should include CPU/GPU image-family guidance: %+v", req.ImagePolicy) + } } diff --git a/plugin.json b/plugin.json index 5f86b2b..033fe76 100644 --- a/plugin.json +++ b/plugin.json @@ -1,24 +1,31 @@ { - "name": "scaffold-workflow-plugin", - "version": "0.0.0", - "description": "Scaffold repo for new workflow plugins. NOT an installable plugin — see README for the instantiation flow.", - "author": "GoCodeAlone", - "license": "MIT", - "type": "scaffold", - "tier": "community", - "private": false, - "minEngineVersion": "0.62.0", - "keywords": ["scaffold", "template"], - "homepage": "https://github.com/GoCodeAlone/scaffold-workflow-plugin", - "repository": "https://github.com/GoCodeAlone/scaffold-workflow-plugin", - "capabilities": { - "configProvider": false, - "moduleTypes": ["TEMPLATE.module"], - "stepTypes": ["TEMPLATE.step"], - "triggerTypes": [], - "iacProvider": { - "resourceTypes": ["TEMPLATE.resource"] - } - }, - "contracts": [] + "name": "workflow-plugin-volunteer-science", + "version": "0.0.0", + "description": "Volunteer science provider catalog plugin for workflow-compute.", + "author": "GoCodeAlone", + "license": "MIT", + "type": "external", + "tier": "community", + "private": false, + "minEngineVersion": "0.62.0", + "keywords": [ + "volunteer-science", + "workflow-compute", + "provider-catalog" + ], + "homepage": "https://github.com/GoCodeAlone/workflow-plugin-volunteer-science", + "repository": "https://github.com/GoCodeAlone/workflow-plugin-volunteer-science", + "dependencies": [ + { + "name": "workflow-plugin-compute-core", + "constraint": ">=0.0.0" + } + ], + "capabilities": { + "configProvider": false, + "moduleTypes": [], + "stepTypes": [], + "triggerTypes": [] + }, + "contracts": [] } diff --git a/scripts/rename-from-scaffold.sh b/scripts/rename-from-scaffold.sh deleted file mode 100755 index 6fcedbb..0000000 --- a/scripts/rename-from-scaffold.sh +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env bash -# Usage: bash scripts/rename-from-scaffold.sh [--mode iac|non-iac] -# -# Renames scaffold-workflow-plugin internals to workflow-plugin-: -# 1. Picks the IaC or non-IaC main.go variant; deletes the other. -# 2. Renames cmd/scaffold-workflow-plugin*/ → cmd/workflow-plugin-/. -# 3. Updates go.mod module path. -# 4. Bulk sed across .go/.yaml/.yml/.md/.json files (find-based; safe with -# paths containing spaces; doesn't rely on bash globstar). -# 5. Resets plugin.json: type "scaffold" → "external"; name → workflow-plugin-. -# 6. Removes the rename script itself + scaffold-rename-test workflow. -# -# Requires: jq. -# -# Tested by .github/workflows/scaffold-rename-test.yml which runs this against -# a tmp copy in both --mode iac and --mode non-iac, then `go build ./...`. -set -euo pipefail - -NEW_NAME="${1:?Usage: rename-from-scaffold.sh [--mode iac|non-iac]}" -MODE="non-iac" -if [[ "${2:-}" == "--mode" ]]; then - MODE="${3:?Mode required}" -fi -case "$MODE" in - iac|non-iac) ;; - *) echo "Mode must be iac or non-iac" >&2; exit 1 ;; -esac - -if ! command -v jq >/dev/null 2>&1; then - echo "error: jq is required" >&2 - exit 1 -fi - -REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -cd "$REPO_ROOT" - -# 1+2. Pick main.go variant; delete the other; rename to workflow-plugin-. -if [[ "$MODE" == "iac" ]]; then - rm -rf cmd/scaffold-workflow-plugin - mv cmd/scaffold-workflow-plugin-iac "cmd/workflow-plugin-$NEW_NAME" -else - rm -rf cmd/scaffold-workflow-plugin-iac - mv cmd/scaffold-workflow-plugin "cmd/workflow-plugin-$NEW_NAME" -fi - -# 3. go.mod -go mod edit -module "github.com/GoCodeAlone/workflow-plugin-$NEW_NAME" - -# 4. Bulk sed via find (safe for paths with spaces; no globstar dependency). -find . \( -name '*.go' -o -name '*.yaml' -o -name '*.yml' -o -name '*.md' -o -name 'plugin.json' \) \ - -not -path './vendor/*' -not -path './_worktrees/*' -not -path './.git/*' -print0 \ - | while IFS= read -r -d '' f; do - sed -i.bak "s|scaffold-workflow-plugin|workflow-plugin-$NEW_NAME|g" "$f" - rm -f "$f.bak" - done - -# 5. plugin.json: reset type + name (jq-based; idempotent). -tmp="$(mktemp)" -jq --arg name "workflow-plugin-$NEW_NAME" '.type = "external" | .name = $name' plugin.json > "$tmp" -mv "$tmp" plugin.json - -# 6. Remove the rename script itself + scaffold-rename-test workflow. -rm -f scripts/rename-from-scaffold.sh -rm -f .github/workflows/scaffold-rename-test.yml - -echo "Renamed to workflow-plugin-$NEW_NAME ($MODE mode)." -echo "Next steps:" -echo " 1. Review changes: git status / git diff" -echo " 2. Edit plugin.json: replace TEMPLATE.* placeholders with real capabilities" -echo " 3. Commit: git add -A && git commit -m 'feat: initial plugin scaffold'" -echo " 4. Tag: git tag v0.1.0 && git push origin main v0.1.0"